"use client";

import { useState } from "react";
import { createPortal } from "react-dom";
import * as ed from "@noble/ed25519";
import { sha512 } from "@noble/hashes/sha512";
import { cn } from "@/lib/utils";

// Configure ed25519 to use sha512
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));

// ============================================================================
// Types
// ============================================================================

export type SourceClass =
  | "Regulatory"
  | "Clinical"
  | "Observational"
  | "Expert"
  | "Community"
  | "Anecdotal";

type ObjectType = "Text" | "Number" | "Boolean" | "Reference";

interface ObjectValue {
  type: ObjectType;
  value: string | number | boolean;
}

interface SourceSpan {
  start: number;
  end: number;
  text: string;
}

interface ExtractedClaim {
  subject: string;
  predicate: string;
  object: ObjectValue;
  confidence: number;
  extraction_rationale: string;
  entity_aliases: string[];
  source_span?: SourceSpan;
}

export interface ExtractionOutput {
  claims: ExtractedClaim[];
  source: {
    url?: string;
    source_class: SourceClass;
    content_hash?: string;
  };
  meta: {
    total_claims: number;
    unique_subjects: number;
    extraction_notes?: string;
  };
}

interface ExtractionModalProps {
  result: ExtractionOutput;
  onClose: () => void;
  sourceClass: SourceClass;
}

// ============================================================================
// Constants
// ============================================================================

const API_URL = process.env.NEXT_PUBLIC_STEMEDB_API_URL || "http://127.0.0.1:18180";

const tierColors: Record<number, string> = {
  0: "bg-emerald-500/20 text-emerald-700 dark:text-emerald-300 border-emerald-500/30",
  1: "bg-blue-500/20 text-blue-700 dark:text-blue-300 border-blue-500/30",
  2: "bg-cyan-500/20 text-cyan-700 dark:text-cyan-300 border-cyan-500/30",
  3: "bg-amber-500/20 text-amber-700 dark:text-amber-300 border-amber-500/30",
  4: "bg-orange-500/20 text-orange-700 dark:text-orange-300 border-orange-500/30",
  5: "bg-red-500/20 text-red-700 dark:text-red-300 border-red-500/30",
};

const sourceClassToTier: Record<SourceClass, number> = {
  Regulatory: 0,
  Clinical: 1,
  Observational: 2,
  Expert: 3,
  Community: 4,
  Anecdotal: 5,
};

// ============================================================================
// Helpers
// ============================================================================

function toHex(bytes: Uint8Array): string {
  return Array.from(bytes)
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
}

function sha256Simple(data: string): Uint8Array {
  const encoder = new TextEncoder();
  const bytes = encoder.encode(data);
  const hash = new Uint8Array(32);
  for (let i = 0; i < bytes.length; i++) {
    hash[i % 32] ^= bytes[i];
    hash[(i + 1) % 32] = (hash[(i + 1) % 32] + bytes[i]) % 256;
  }
  return hash;
}

async function createAgent(name: string) {
  const seedHash = sha256Simple(`extract-claims-agent-${name}`);
  const privateKey = seedHash;
  const publicKey = await ed.getPublicKeyAsync(privateKey);
  return { name, privateKey, publicKey };
}

async function signAssertion(
  agent: { privateKey: Uint8Array; publicKey: Uint8Array },
  subject: string,
  predicate: string
): Promise<{ signature: string; timestamp: number }> {
  const timestamp = Math.floor(Date.now() / 1000);
  const message = `${subject}:${predicate}`;
  const messageBytes = new TextEncoder().encode(message);
  const signature = await ed.signAsync(messageBytes, agent.privateKey);
  return { signature: toHex(signature), timestamp };
}

// ============================================================================
// Components
// ============================================================================

function TierBadge({ tier }: { tier: number }) {
  return (
    <span
      className={cn(
        "font-mono font-semibold rounded border px-1.5 py-0.5 text-xs",
        tierColors[tier]
      )}
    >
      T{tier}
    </span>
  );
}

function ConfidenceBar({ confidence }: { confidence: number }) {
  const percent = Math.round(confidence * 100);
  return (
    <div className="flex items-center gap-2">
      <div className="h-1.5 bg-muted rounded-full overflow-hidden flex-1 max-w-[60px]">
        <div
          className={cn(
            "h-full rounded-full transition-all",
            confidence >= 0.8
              ? "bg-emerald-500"
              : confidence >= 0.6
              ? "bg-amber-500"
              : "bg-red-500"
          )}
          style={{ width: `${percent}%` }}
        />
      </div>
      <span className="text-xs font-mono text-muted-foreground w-8">
        {confidence.toFixed(2)}
      </span>
    </div>
  );
}

// ============================================================================
// Main Modal Component
// ============================================================================

export function ExtractionModal({ result, onClose, sourceClass }: ExtractionModalProps) {
  const [expandedClaim, setExpandedClaim] = useState<number | null>(null);
  const [selectedClaims, setSelectedClaims] = useState<Set<number>>(
    new Set(result.claims.map((_, i) => i))
  );
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [submitResult, setSubmitResult] = useState<{ success: number; failed: number } | null>(null);
  const [submitError, setSubmitError] = useState<string | null>(null);

  const tier = sourceClassToTier[sourceClass];

  const toggleClaim = (index: number) => {
    const newSelected = new Set(selectedClaims);
    if (newSelected.has(index)) {
      newSelected.delete(index);
    } else {
      newSelected.add(index);
    }
    setSelectedClaims(newSelected);
  };

  const selectAll = () => {
    setSelectedClaims(new Set(result.claims.map((_, i) => i)));
  };

  const selectNone = () => {
    setSelectedClaims(new Set());
  };

  const handleSubmit = async () => {
    if (selectedClaims.size === 0) return;

    setIsSubmitting(true);
    setSubmitError(null);
    setSubmitResult(null);

    try {
      const agent = await createAgent("community-extract");
      const sourceHash = result.source.content_hash || toHex(sha256Simple(JSON.stringify(result)));

      let success = 0;
      let failed = 0;

      for (const index of selectedClaims) {
        const claim = result.claims[index];
        const { signature, timestamp } = await signAssertion(
          agent,
          claim.subject,
          claim.predicate
        );

        const request = {
          subject: claim.subject,
          predicate: claim.predicate,
          object: claim.object,
          confidence: claim.confidence,
          source_hash: sourceHash,
          source_class: sourceClass,
          signatures: [
            {
              agent_id: toHex(agent.publicKey),
              signature,
              timestamp,
              version: 1,
            },
          ],
        };

        try {
          const response = await fetch(`${API_URL}/v1/assert`, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(request),
          });

          if (response.ok) {
            success++;
          } else {
            failed++;
          }
        } catch {
          failed++;
        }
      }

      setSubmitResult({ success, failed });
    } catch (error) {
      setSubmitError(error instanceof Error ? error.message : "Submission failed");
    } finally {
      setIsSubmitting(false);
    }
  };

  return createPortal(
    <>
      {/* Backdrop */}
      <div
        className="fixed inset-0 bg-black/50 z-[9998]"
        onClick={onClose}
      />

      {/* Modal */}
      <div className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-[800px] max-h-[85vh] z-[9999] bg-card border border-border rounded-lg shadow-xl flex flex-col">
        {/* Header */}
        <div className="px-4 py-3 border-b border-border bg-muted/30 rounded-t-lg flex-shrink-0">
          <div className="flex items-center justify-between">
            <div className="flex items-center gap-3">
              <span className="text-sm font-medium">
                Extracted Claims
              </span>
              <TierBadge tier={tier} />
              <span className="text-xs text-muted-foreground">
                {result.meta.total_claims} claims from {result.meta.unique_subjects} subjects
              </span>
            </div>
            <button
              onClick={onClose}
              className="p-1 text-muted-foreground hover:text-foreground transition-colors rounded hover:bg-muted"
            >
              ✕
            </button>
          </div>
        </div>

        {/* Selection controls */}
        <div className="px-4 py-2 border-b border-border flex items-center justify-between text-xs">
          <div className="flex items-center gap-3">
            <span className="text-muted-foreground">
              {selectedClaims.size} of {result.claims.length} selected
            </span>
            <button
              onClick={selectAll}
              className="text-blue-600 dark:text-blue-400 hover:underline"
            >
              Select all
            </button>
            <button
              onClick={selectNone}
              className="text-blue-600 dark:text-blue-400 hover:underline"
            >
              Select none
            </button>
          </div>
        </div>

        {/* Claims list */}
        <div className="flex-1 overflow-y-auto p-4">
          <div className="space-y-2">
            {result.claims.map((claim, i) => (
              <div
                key={i}
                className={cn(
                  "border rounded-lg transition-all",
                  selectedClaims.has(i)
                    ? "border-primary/50 bg-primary/5"
                    : "border-border hover:border-foreground/10 opacity-60"
                )}
              >
                {/* Claim header */}
                <div className="p-3 flex items-start gap-3">
                  <input
                    type="checkbox"
                    checked={selectedClaims.has(i)}
                    onChange={() => toggleClaim(i)}
                    className="mt-1 rounded"
                  />
                  <button
                    onClick={() => setExpandedClaim(expandedClaim === i ? null : i)}
                    className="flex-1 text-left"
                  >
                    <div className="flex items-center gap-2 flex-wrap">
                      <code className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded">
                        {claim.subject}
                      </code>
                      <span className="text-muted-foreground">/</span>
                      <code className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded">
                        {claim.predicate}
                      </code>
                    </div>
                    <div className="text-sm mt-1 text-foreground">
                      {claim.object.type === "Text" && (
                        <span>&ldquo;{String(claim.object.value)}&rdquo;</span>
                      )}
                      {claim.object.type !== "Text" && (
                        <span className="font-mono">{String(claim.object.value)}</span>
                      )}
                    </div>
                    <div className="flex items-center gap-4 mt-2">
                      <ConfidenceBar confidence={claim.confidence} />
                      <span className="text-xs text-muted-foreground">
                        {claim.object.type}
                      </span>
                    </div>
                  </button>
                  <span className="text-muted-foreground text-xs">
                    {expandedClaim === i ? "▼" : "▶"}
                  </span>
                </div>

                {/* Expanded details */}
                {expandedClaim === i && (
                  <div className="px-3 pb-3 border-t border-border pt-3 ml-8 space-y-2">
                    <div>
                      <div className="text-xs font-medium text-muted-foreground mb-1">
                        Extraction Rationale
                      </div>
                      <div className="text-xs text-foreground">
                        {claim.extraction_rationale}
                      </div>
                    </div>

                    {claim.entity_aliases.length > 0 && (
                      <div>
                        <div className="text-xs font-medium text-muted-foreground mb-1">
                          Entity Aliases
                        </div>
                        <div className="flex gap-1 flex-wrap">
                          {claim.entity_aliases.map((alias, j) => (
                            <span
                              key={j}
                              className="text-xs bg-muted px-1.5 py-0.5 rounded"
                            >
                              {alias}
                            </span>
                          ))}
                        </div>
                      </div>
                    )}

                    {claim.source_span && (
                      <div>
                        <div className="text-xs font-medium text-muted-foreground mb-1">
                          Source Span
                        </div>
                        <div className="text-xs text-foreground bg-muted/50 p-2 rounded italic">
                          &ldquo;{claim.source_span.text}&rdquo;
                        </div>
                        <div className="text-[10px] text-muted-foreground mt-1">
                          chars {claim.source_span.start}-{claim.source_span.end}
                        </div>
                      </div>
                    )}
                  </div>
                )}
              </div>
            ))}
          </div>
        </div>

        {/* Footer */}
        <div className="px-4 py-3 border-t border-border flex items-center justify-between flex-shrink-0">
          <div className="text-xs text-muted-foreground">
            Claims will be submitted to StemeDB at {API_URL}
          </div>
          <div className="flex items-center gap-3">
            {submitResult && (
              <span className="text-xs text-emerald-600 dark:text-emerald-400">
                {submitResult.success} submitted
                {submitResult.failed > 0 && (
                  <span className="text-red-600 dark:text-red-400 ml-2">
                    {submitResult.failed} failed
                  </span>
                )}
              </span>
            )}
            {submitError && (
              <span className="text-xs text-red-600 dark:text-red-400">
                {submitError}
              </span>
            )}
            <button
              onClick={onClose}
              className="px-3 py-1.5 text-sm rounded border border-border hover:bg-muted transition-colors"
            >
              Cancel
            </button>
            <button
              onClick={handleSubmit}
              disabled={selectedClaims.size === 0 || isSubmitting}
              className={cn(
                "px-4 py-1.5 text-sm rounded bg-primary text-primary-foreground transition-colors",
                selectedClaims.size === 0 || isSubmitting
                  ? "opacity-50 cursor-not-allowed"
                  : "hover:bg-primary/90"
              )}
            >
              {isSubmitting ? (
                <span className="flex items-center gap-2">
                  <span className="animate-spin w-3 h-3 border-2 border-current border-t-transparent rounded-full" />
                  Submitting...
                </span>
              ) : (
                `Submit ${selectedClaims.size} Claims`
              )}
            </button>
          </div>
        </div>
      </div>
    </>,
    document.body
  );
}
