"use client";
import { useEffect, useMemo, useState } from "react";
import { createPortal } from "react-dom";
import { X, UserPlus, Link2, MinusCircle, AlertTriangle, Check } from "lucide-react";
import { cn } from "@/lib/utils";
import type { ActressLookupResult } from "@/app/actions/actressLookup";

export type ActressDecision = "link" | "create" | "skip";

export interface IngestPreviewFile {
  original: string;
  targetFilename: string;
  code: string | null;
  actresses: string[];
}

/** Per-file → per-actress decisions. Keyed by `file.original`, then by
 *  `actressName.toLowerCase()`. The two-level shape keeps the same actress
 *  appearing on multiple covers independent — skipping Mitsuki on one cover
 *  must not silently skip her on every other cover sharing the name. */
export type ActressDecisions = Record<string, Record<string, ActressDecision>>;

export interface IngestPreviewProps {
  files: IngestPreviewFile[];
  lookup: ActressLookupResult[];
  onCancel: () => void;
  onSkipActresses: () => void;
  onConfirm: (decisions: ActressDecisions) => void;
}

const INITIAL_RENDER = 200;
const RENDER_INCREMENT = 200;

type StatusFilter = "all" | "ok" | "errors";

export function IngestPreviewDialog({ files, lookup, onCancel, onSkipActresses, onConfirm }: IngestPreviewProps) {
  const [renderLimit, setRenderLimit] = useState(INITIAL_RENDER);
  const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
  const lookupMap = useMemo(() => {
    const m = new Map<string, ActressLookupResult>();
    for (const r of lookup) m.set(r.name.toLowerCase(), r);
    return m;
  }, [lookup]);

  // A row is in "errors" when there's something the user should look at
  // before clicking Confirm. Today: missing code only — a file with no
  // canonical code imports successfully but has no stable URL handle. The
  // "new" pill on actresses is a state, not an error, and including it
  // would make every fresh-actress batch flash red.
  const isError = (f: IngestPreviewFile) => !f.code;
  const errorCount = useMemo(() => files.filter(isError).length, [files]);
  const okCount = files.length - errorCount;
  const filtered = useMemo(() => {
    if (statusFilter === "ok") return files.filter((f) => !isError(f));
    if (statusFilter === "errors") return files.filter(isError);
    return files;
  }, [files, statusFilter]);

  const [decisions, setDecisions] = useState<ActressDecisions>(() => {
    const init: ActressDecisions = {};
    for (const f of files) {
      if (f.actresses.length === 0) continue;
      const perFile: Record<string, ActressDecision> = {};
      for (const name of f.actresses) {
        const key = name.toLowerCase();
        const lr = lookupMap.get(key);
        perFile[key] = lr?.match ? "link" : "create";
      }
      init[f.original] = perFile;
    }
    return init;
  });

  useEffect(() => {
    const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onCancel(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onCancel]);

  useEffect(() => { setRenderLimit(INITIAL_RENDER); }, [statusFilter]);

  function setDecision(fileOriginal: string, nameKey: string, d: ActressDecision) {
    setDecisions((s) => ({
      ...s,
      [fileOriginal]: { ...(s[fileOriginal] ?? {}), [nameKey]: d },
    }));
  }

  // Total and per-bucket counts are computed across (file, actress) pairs,
  // so the same actress on N covers contributes N rows to the bulk state.
  const counts = useMemo(() => {
    let matched = 0, unmatched = 0;
    for (const f of files) {
      for (const name of f.actresses) {
        const lr = lookupMap.get(name.toLowerCase());
        if (lr?.match) matched++; else unmatched++;
      }
    }
    return { matched, unmatched, total: matched + unmatched };
  }, [files, lookupMap]);

  const bulkState = useMemo(() => {
    let matchedAllLink = counts.matched > 0;
    let unmatchedAllCreate = counts.unmatched > 0;
    let allSkip = counts.total > 0;
    for (const f of files) {
      const perFile = decisions[f.original] ?? {};
      for (const name of f.actresses) {
        const key = name.toLowerCase();
        const lr = lookupMap.get(key);
        const d = perFile[key];
        if (lr?.match) {
          if (d !== "link") matchedAllLink = false;
        } else {
          if (d !== "create") unmatchedAllCreate = false;
        }
        if (d !== "skip") allSkip = false;
      }
    }
    return { matchedAllLink, unmatchedAllCreate, allSkip };
  }, [files, decisions, lookupMap, counts]);

  function bulkApply(target: "matched" | "unmatched" | "all", decision: ActressDecision) {
    setDecisions((s) => {
      const next: ActressDecisions = { ...s };
      for (const f of files) {
        if (f.actresses.length === 0) continue;
        const perFile: Record<string, ActressDecision> = { ...(next[f.original] ?? {}) };
        let touched = false;
        for (const name of f.actresses) {
          const key = name.toLowerCase();
          const isMatched = !!lookupMap.get(key)?.match;
          if (target !== "all" && target === "matched" && !isMatched) continue;
          if (target !== "all" && target === "unmatched" && isMatched) continue;
          if (decision === "link" && !isMatched) continue;
          if (decision === "create" && isMatched) continue;
          perFile[key] = decision;
          touched = true;
        }
        if (touched) next[f.original] = perFile;
      }
      return next;
    });
  }

  if (typeof document === "undefined") return null;
  return createPortal(
    <div
      className="fixed inset-0 z-50 flex items-center justify-center px-4 py-6 bg-black/50 fade-in"
      onMouseDown={(e) => { if (e.target === e.currentTarget) onCancel(); }}
    >
      <div className="bg-[var(--color-bg-0)] border border-[var(--color-glass-border)] shadow-2xl rounded-2xl w-[min(720px,calc(100vw-32px))] max-h-[calc(100vh-48px)] flex flex-col">
        <div className="flex items-center justify-between p-5 border-b border-[var(--color-glass-border)]">
          <div>
            <div className="text-base font-medium">Confirm Import</div>
            <div className="text-xs text-[var(--color-fg-muted)] mt-0.5">
              {files.length} file{files.length === 1 ? "" : "s"} · review actress assignments below
            </div>
          </div>
          <button onClick={onCancel} className="text-[var(--color-fg-muted)] hover:text-[var(--color-fg)]">
            <X className="w-5 h-5" />
          </button>
        </div>

        {files.length > INITIAL_RENDER && (
          <div className="px-5 py-2 border-b border-[var(--color-glass-border)] text-[11px] text-[var(--color-fg-muted)] bg-[var(--color-cyan)]/5">
            Large batch — first {INITIAL_RENDER} of {files.length} files shown. Bulk actions still apply to <strong className="text-[var(--color-fg)]">all {files.length}</strong>.
          </div>
        )}

        <div className="px-5 py-3 border-b border-[var(--color-glass-border)] flex items-center gap-1.5 text-xs">
          <span className="font-mono text-[var(--color-fg-muted)] mr-1">Filter:</span>
          <FilterChip active={statusFilter === "all"} onClick={() => setStatusFilter("all")}>
            All {files.length}
          </FilterChip>
          <FilterChip
            active={statusFilter === "ok"}
            onClick={() => setStatusFilter("ok")}
            kind="ok"
            disabled={okCount === 0}
          >
            OK {okCount}
          </FilterChip>
          <FilterChip
            active={statusFilter === "errors"}
            onClick={() => setStatusFilter("errors")}
            kind="error"
            disabled={errorCount === 0}
          >
            Errors {errorCount}
          </FilterChip>
        </div>

        {counts.total > 0 && (
          <div className="px-5 py-3 border-b border-[var(--color-glass-border)] flex flex-wrap items-center gap-2 text-xs">
            <span className="font-mono text-[var(--color-fg-muted)] mr-1">Bulk:</span>
            {counts.matched > 0 && (
              <BulkButton active={bulkState.matchedAllLink} onClick={() => bulkApply("matched", "link")}>
                Link all matched ({counts.matched})
              </BulkButton>
            )}
            {counts.unmatched > 0 && (
              <BulkButton active={bulkState.unmatchedAllCreate} onClick={() => bulkApply("unmatched", "create")}>
                Create all new ({counts.unmatched})
              </BulkButton>
            )}
            <BulkButton active={bulkState.allSkip} onClick={() => bulkApply("all", "skip")}>
              Skip all
            </BulkButton>
          </div>
        )}

        <div className="overflow-y-auto p-5 space-y-4">
          {filtered.length === 0 && (
            <div className="text-center py-8 text-sm text-[var(--color-fg-muted)] italic">
              No files match this filter.
            </div>
          )}
          {filtered.slice(0, renderLimit).map((f) => (
            <div
              key={f.original}
              className="rounded-xl border border-[var(--color-glass-border)] bg-[var(--color-bg-1)]/30 p-3"
              style={{ contentVisibility: "auto", containIntrinsicSize: "0 120px" } as React.CSSProperties}
            >
              <div className="flex items-start justify-between gap-3 mb-2">
                <div className="min-w-0">
                  <div className="text-sm font-medium font-mono truncate">{f.targetFilename}</div>
                  <div className="text-[11px] text-[var(--color-fg-muted)] truncate">from {f.original}</div>
                </div>
                {f.code ? (
                  <span className="shrink-0 text-[10px] uppercase tracking-wider font-mono px-2 py-0.5 rounded-full bg-[var(--color-cyan)]/15 text-[var(--color-cyan)] border border-[var(--color-cyan)]/30">
                    {f.code}
                  </span>
                ) : (
                  <span className="shrink-0 text-[10px] uppercase tracking-wider font-mono px-2 py-0.5 rounded-full bg-[var(--color-coral)]/15 text-[var(--color-coral)] border border-[var(--color-coral)]/30 flex items-center gap-1">
                    <AlertTriangle className="w-3 h-3" /> No code
                  </span>
                )}
              </div>

              {f.actresses.length > 0 ? (
                <div className="space-y-1.5">
                  {f.actresses.map((name) => {
                    const key = name.toLowerCase();
                    const lr = lookupMap.get(key);
                    const decision = decisions[f.original]?.[key] ?? "skip";
                    return (
                      <div key={key} className="flex items-center gap-2 text-sm">
                        <div className="flex-1 min-w-0 truncate">{name}</div>
                        {lr?.match ? (
                          <Pill kind="match">match: {lr.match.name}</Pill>
                        ) : (
                          <Pill kind="new">new</Pill>
                        )}
                        <DecisionButtons
                          available={lr?.match ? ["link", "skip"] : ["create", "skip"]}
                          value={decision}
                          onChange={(d) => setDecision(f.original, key, d)}
                        />
                      </div>
                    );
                  })}
                </div>
              ) : (
                <div className="text-xs text-[var(--color-fg-muted)] italic">No actresses parsed.</div>
              )}
            </div>
          ))}
          {filtered.length > renderLimit && (
            <button
              type="button"
              onClick={() => setRenderLimit((n) => n + RENDER_INCREMENT)}
              className="w-full text-xs px-3 py-2 rounded-lg border border-dashed border-[var(--color-glass-border)] text-[var(--color-fg-dim)] hover:text-[var(--color-fg)] hover:bg-[var(--color-glass)]"
            >
              Showing {renderLimit} of {filtered.length} files — load {Math.min(RENDER_INCREMENT, filtered.length - renderLimit)} more
            </button>
          )}
        </div>

        <div className="flex items-center gap-2 p-4 border-t border-[var(--color-glass-border)]">
          <button
            onClick={onCancel}
            className="text-sm px-3 py-2 rounded-lg glass glass-hover"
          >
            Cancel import
          </button>
          <button
            onClick={onSkipActresses}
            className="text-sm px-3 py-2 rounded-lg glass glass-hover"
            title="Import the files but don't attach any actresses"
          >
            Skip actress assignment
          </button>
          <div className="flex-1" />
          <button
            onClick={() => onConfirm(decisions)}
            className="text-sm px-4 py-2 rounded-lg bg-[var(--color-cyan)] text-black font-medium"
          >
            Confirm import
          </button>
        </div>
      </div>
    </div>,
    document.body,
  );
}

function FilterChip({
  active,
  onClick,
  disabled,
  kind,
  children,
}: {
  active: boolean;
  onClick: () => void;
  disabled?: boolean;
  kind?: "ok" | "error";
  children: React.ReactNode;
}) {
  const accent =
    kind === "ok"
      ? "var(--color-mint)"
      : kind === "error"
      ? "var(--color-coral)"
      : "var(--color-cyan)";
  return (
    <button
      type="button"
      onClick={onClick}
      disabled={disabled}
      className={cn(
        "px-2.5 py-1 rounded-md text-xs border transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
        active
          ? "text-black border-transparent font-medium"
          : "border-[var(--color-glass-border)] text-[var(--color-fg-dim)] hover:text-[var(--color-fg)] hover:bg-[var(--color-glass)]",
      )}
      style={active && !disabled ? { background: accent } : undefined}
    >
      {children}
    </button>
  );
}

function BulkButton({
  active,
  onClick,
  children,
}: {
  active: boolean;
  onClick: () => void;
  children: React.ReactNode;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      className={cn(
        "flex items-center gap-1.5 px-2.5 py-1 rounded-md border transition-colors",
        active
          ? "bg-[var(--color-cyan)] text-black border-transparent font-medium"
          : "border-[var(--color-glass-border)] text-[var(--color-fg-dim)] hover:text-[var(--color-fg)] hover:bg-[var(--color-glass)]",
      )}
    >
      <Check className={cn("w-3 h-3", active ? "visible" : "invisible")} />
      {children}
    </button>
  );
}

function Pill({ kind, children }: { kind: "match" | "new"; children: React.ReactNode }) {
  const cls =
    kind === "match"
      ? "bg-[var(--color-mint)]/15 text-[var(--color-mint)] border-[var(--color-mint)]/30"
      : "bg-[var(--color-violet)]/15 text-[var(--color-violet)] border-[var(--color-violet)]/30";
  return (
    <span className={cn("text-[10px] uppercase tracking-wider font-mono px-2 py-0.5 rounded-full border", cls)}>
      {children}
    </span>
  );
}

function DecisionButtons({
  available,
  value,
  onChange,
}: {
  available: ActressDecision[];
  value: ActressDecision;
  onChange: (d: ActressDecision) => void;
}) {
  const opts: Array<{ key: ActressDecision; label: string; Icon: React.ComponentType<{ className?: string }> }> = [
    { key: "link", label: "Link", Icon: Link2 },
    { key: "create", label: "Create", Icon: UserPlus },
    { key: "skip", label: "Skip", Icon: MinusCircle },
  ];
  return (
    <div className="flex items-center gap-1">
      {opts
        .filter((o) => available.includes(o.key))
        .map(({ key, label, Icon }) => (
          <button
            key={key}
            type="button"
            onClick={() => onChange(key)}
            className={cn(
              "flex items-center gap-1 text-[11px] font-mono px-2 py-1 rounded-md border transition-colors",
              value === key
                ? "bg-[var(--color-cyan)] text-black border-transparent"
                : "border-[var(--color-glass-border)] text-[var(--color-fg-dim)] hover:text-[var(--color-fg)] hover:bg-[var(--color-glass)]",
            )}
          >
            <Icon className="w-3 h-3" />
            {label}
          </button>
        ))}
    </div>
  );
}
