"use client";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react";
import { ArrowLeft, Captions, Loader2, Play, RefreshCw, Square, Sparkles } from "lucide-react";
import { thumbUrl } from "@/lib/assetUrls";
import { cn } from "@/lib/utils";

interface Candidate {
  id: number;
  code: string;
  title: string | null;
  thumbPath: string;
}

interface QueueState {
  queued: number;
  running: {
    id: string;
    code: string;
    started_at: number | null;
    stage: string | null;
    stage_index: number | null;
    stage_total: number | null;
  } | null;
}

const PAGE_SIZE = 100;

export function BatchGeneratorClient() {
  const [candidates, setCandidates] = useState<Candidate[]>([]);
  const [total, setTotal] = useState(0);
  const [loading, setLoading] = useState(true);
  const [page, setPage] = useState(0);
  const [selected, setSelected] = useState<Set<string>>(new Set());
  const [queueState, setQueueState] = useState<QueueState>({ queued: 0, running: null });
  const [enqueuing, setEnqueuing] = useState(false);
  const [stopping, setStopping] = useState(false);
  const [batchSize, setBatchSize] = useState<number>(5);
  const [lastResult, setLastResult] = useState<string | null>(null);

  const loadPage = useCallback(async (p: number) => {
    setLoading(true);
    try {
      const r = await fetch(
        `/api/whisperjav-candidates?limit=${PAGE_SIZE}&offset=${p * PAGE_SIZE}`,
        { cache: "no-store" },
      );
      const j = (await r.json()) as { candidates: Candidate[]; total: number };
      setCandidates(j.candidates ?? []);
      setTotal(j.total ?? 0);
    } catch {
      setCandidates([]);
      setTotal(0);
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => { loadPage(page); }, [page, loadPage]);

  // Poll queue state every 3s while the tab is visible. Hidden tabs
  // pause the interval (no point hammering the API when the UI isn't
  // on screen) and tick once on visibility-restore so the user sees
  // fresh state immediately.
  useEffect(() => {
    let live = true;
    let interval: ReturnType<typeof setInterval> | null = null;
    const tick = async () => {
      try {
        const r = await fetch("/api/whisperjav-jobs/batch", { cache: "no-store" });
        if (!r.ok) return;
        const j = (await r.json()) as QueueState;
        if (live) setQueueState(j);
      } catch { /* ignore */ }
    };
    const start = () => {
      if (interval != null) return;
      interval = setInterval(tick, 3000);
    };
    const stop = () => {
      if (interval == null) return;
      clearInterval(interval);
      interval = null;
    };
    const onVisibility = () => {
      if (document.hidden) {
        stop();
      } else {
        void tick();
        start();
      }
    };
    void tick();
    if (!document.hidden) start();
    document.addEventListener("visibilitychange", onVisibility);
    return () => {
      live = false;
      stop();
      document.removeEventListener("visibilitychange", onVisibility);
    };
  }, []);

  const toggleAll = (checked: boolean) => {
    if (!checked) {
      // Only clear codes from the current page so other-page selections persist.
      const here = new Set(candidates.map((c) => c.code));
      setSelected((prev) => {
        const next = new Set(prev);
        for (const c of here) next.delete(c);
        return next;
      });
    } else {
      setSelected((prev) => {
        const next = new Set(prev);
        for (const c of candidates) next.add(c.code);
        return next;
      });
    }
  };

  const toggleOne = (code: string) => {
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(code)) next.delete(code);
      else next.add(code);
      return next;
    });
  };

  const queueSelected = async (codesArg?: string[]) => {
    const codes = codesArg ?? Array.from(selected);
    if (codes.length === 0) return;
    setEnqueuing(true);
    setLastResult(null);
    try {
      const r = await fetch("/api/whisperjav-jobs/batch", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ codes }),
      });
      const j = (await r.json()) as { enqueued: number; skipped: number; errors: Array<{ code: string; error: string }> };
      const summary = `Queued ${j.enqueued}${j.skipped ? `, skipped ${j.skipped} (already have subs)` : ""}${j.errors.length ? `, ${j.errors.length} failed` : ""}`;
      setLastResult(summary);
      // Drop just-queued codes from the selection so the user can keep
      // moving down the list without manually unchecking.
      setSelected((prev) => {
        const next = new Set(prev);
        for (const c of codes) next.delete(c);
        return next;
      });
      // Refresh candidates so any newly-queued items can be removed
      // from the list once they actually produce a subtitle.
      void loadPage(page);
    } catch (e) {
      setLastResult(`Failed: ${(e as Error).message}`);
    } finally {
      setEnqueuing(false);
    }
  };

  const queueNextN = () => {
    // Walk the current page in order, skipping codes already selected
    // (they'll go through queueSelected anyway) and codes already in
    // the queue (we don't track them here; server-side check is the
    // source of truth via alreadyExists).
    const picks: string[] = [];
    for (const c of candidates) {
      if (picks.length >= batchSize) break;
      picks.push(c.code);
    }
    void queueSelected(picks);
  };

  const stopBatch = async () => {
    if (!window.confirm("Cancel all queued WhisperJAV jobs?\n\nThe currently running job is not affected — cancel it from the player.")) return;
    setStopping(true);
    try {
      const r = await fetch("/api/whisperjav-jobs/batch", { method: "DELETE" });
      const j = (await r.json()) as { cancelled: number };
      setLastResult(`Cancelled ${j.cancelled} queued job(s).`);
    } catch (e) {
      setLastResult(`Failed: ${(e as Error).message}`);
    } finally {
      setStopping(false);
    }
  };

  const allOnPageSelected = candidates.length > 0
    && candidates.every((c) => selected.has(c.code));
  const noneOnPageSelected = candidates.length > 0
    && candidates.every((c) => !selected.has(c.code));
  const totalPages = Math.ceil(total / PAGE_SIZE);
  const [now, setNow] = useState(() => Date.now());
  useEffect(() => {
    if (!queueState.running?.started_at) return;
    if (typeof document !== "undefined" && document.hidden) return;
    const i = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(i);
  }, [queueState.running]);
  const runningElapsed = useMemo(() => {
    const r = queueState.running;
    if (!r || !r.started_at) return null;
    return Math.floor((now - r.started_at) / 1000);
  }, [queueState.running, now]);

  return (
    <div className="space-y-section">
      <div className="flex items-center justify-between gap-4">
        <Link
          href="/"
          className="inline-flex items-center gap-1.5 text-sm text-[var(--color-fg-dim)] hover:text-[var(--color-fg)]"
        >
          <ArrowLeft className="w-4 h-4" /> Back to library
        </Link>
        <button
          type="button"
          onClick={() => loadPage(page)}
          disabled={loading}
          title="Refresh candidate list"
          className="inline-flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-lg glass glass-hover"
        >
          {loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <RefreshCw className="w-4 h-4" />}
          Refresh
        </button>
      </div>

      <div className="flex items-center gap-2.5">
        <Captions className="w-5 h-5 text-[var(--color-cyan)]" />
        <h1 className="text-xl font-semibold tracking-tight">Batch Subtitle Generation</h1>
      </div>
      <div className="text-sm text-[var(--color-fg-muted)] max-w-3xl -mt-3">
        Codes with a playable video but no subtitle file. Pick a batch size
        (each video is roughly 1–3 hours of generation time on a single GPU)
        and queue it. Jobs run sequentially via the existing single-worker
        WhisperJAV queue.
      </div>

      {/* Live queue state */}
      <div className="glass rounded-2xl p-4 flex items-center gap-4">
        <div className="text-[10px] uppercase tracking-wider font-mono text-[var(--color-fg-muted)] shrink-0">
          Queue
        </div>
        {queueState.running ? (
          <div className="flex items-center gap-2 min-w-0">
            <Loader2 className="w-4 h-4 animate-spin text-[var(--color-coral)] shrink-0" />
            <span className="font-mono text-sm text-[var(--color-coral)]">
              {queueState.running.code}
            </span>
            <span className="text-xs text-[var(--color-fg-dim)] truncate">
              {queueState.running.stage
                ? (queueState.running.stage_index && queueState.running.stage_total
                  ? `· Step ${queueState.running.stage_index}/${queueState.running.stage_total}: ${queueState.running.stage}`
                  : `· ${queueState.running.stage}`)
                : "· Starting..."}
            </span>
            {runningElapsed != null && (
              <span className="text-xs font-mono text-[var(--color-fg-dim)] shrink-0">
                · {Math.floor(runningElapsed / 60)}m{(runningElapsed % 60).toString().padStart(2, "0")}s
              </span>
            )}
          </div>
        ) : (
          <div className="text-sm text-[var(--color-fg-dim)]">Idle</div>
        )}
        <div className="ml-auto flex items-center gap-2 shrink-0">
          <span className="text-sm font-mono">
            {queueState.queued} queued
          </span>
          <button
            type="button"
            onClick={stopBatch}
            disabled={queueState.queued === 0 || stopping}
            className="inline-flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-lg border border-red-500/30 text-red-300 hover:bg-red-500/10 disabled:opacity-40"
          >
            {stopping ? <Loader2 className="w-4 h-4 animate-spin" /> : <Square className="w-4 h-4" />}
            Stop Batch
          </button>
        </div>
      </div>

      {/* Action bar */}
      <div className="glass rounded-2xl p-4 flex items-center gap-3 flex-wrap">
        <div className="flex items-center gap-2">
          <span className="text-xs text-[var(--color-fg-dim)] uppercase tracking-wider font-mono">Batch size</span>
          <input
            type="number"
            min={1}
            max={50}
            value={batchSize}
            onChange={(e) => setBatchSize(Math.max(1, Math.min(50, Number(e.target.value) || 1)))}
            className="w-20 glass rounded-lg px-3 py-1.5 text-sm font-mono outline-none focus:border-[var(--color-cyan)]"
          />
          <button
            type="button"
            onClick={queueNextN}
            disabled={enqueuing || candidates.length === 0}
            className="inline-flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-lg bg-[var(--color-cyan)] text-black font-medium disabled:opacity-40"
          >
            <Sparkles className="w-4 h-4" /> Queue Next {Math.min(batchSize, candidates.length)}
          </button>
        </div>
        <div className="ml-auto flex items-center gap-2">
          <span className="text-xs text-[var(--color-fg-dim)]">{selected.size} selected</span>
          <button
            type="button"
            onClick={() => queueSelected()}
            disabled={enqueuing || selected.size === 0}
            className="inline-flex items-center gap-1.5 text-sm px-3 py-1.5 rounded-lg glass glass-hover disabled:opacity-40"
          >
            {enqueuing ? <Loader2 className="w-4 h-4 animate-spin" /> : <Play className="w-4 h-4" />}
            Queue Selected
          </button>
          <button
            type="button"
            onClick={() => setSelected(new Set())}
            disabled={selected.size === 0}
            className="text-xs px-2 py-1 rounded-md text-[var(--color-fg-dim)] hover:text-[var(--color-fg)] hover:bg-[var(--color-glass)] disabled:opacity-40"
          >
            Clear
          </button>
        </div>
      </div>

      {lastResult && (
        <div className="text-xs text-[var(--color-mint)] bg-[var(--color-mint)]/5 border border-[var(--color-mint)]/25 rounded-lg px-3 py-2">
          {lastResult}
        </div>
      )}

      <div className="flex items-baseline justify-between gap-3 text-xs text-[var(--color-fg-dim)] font-mono">
        <span>
          {total > 0
            ? `Showing ${page * PAGE_SIZE + 1}–${Math.min((page + 1) * PAGE_SIZE, total)} of ${total} candidates`
            : (loading ? "Loading..." : "No candidates — every video has a subtitle")}
        </span>
        {totalPages > 1 && (
          <div className="flex items-center gap-1">
            <button
              type="button"
              onClick={() => setPage((p) => Math.max(0, p - 1))}
              disabled={page === 0 || loading}
              className="px-2 py-0.5 rounded glass glass-hover disabled:opacity-40"
            >
              Prev
            </button>
            <span className="px-2">Page {page + 1} / {totalPages}</span>
            <button
              type="button"
              onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
              disabled={page >= totalPages - 1 || loading}
              className="px-2 py-0.5 rounded glass glass-hover disabled:opacity-40"
            >
              Next
            </button>
          </div>
        )}
      </div>

      {/* Candidate table */}
      <div className="glass rounded-2xl overflow-hidden">
        <div className="grid grid-cols-[40px_60px_1fr_120px] gap-3 items-center px-4 py-2 border-b border-[var(--color-glass-border)] text-[10px] uppercase tracking-wider font-mono text-[var(--color-fg-muted)]">
          <input
            type="checkbox"
            checked={allOnPageSelected}
            ref={(el) => { if (el) el.indeterminate = !allOnPageSelected && !noneOnPageSelected; }}
            onChange={(e) => toggleAll(e.target.checked)}
            aria-label="Select all on this page"
          />
          <span>Cover</span>
          <span>Code · Title</span>
          <span className="text-right">Action</span>
        </div>
        {loading && candidates.length === 0 && (
          <div className="px-4 py-8 text-center text-[var(--color-fg-muted)]">
            <Loader2 className="w-5 h-5 animate-spin mx-auto" />
          </div>
        )}
        {!loading && candidates.length === 0 && (
          <div className="px-4 py-8 text-center text-[var(--color-fg-muted)] text-sm">
            No videos missing subtitles 🎉
          </div>
        )}
        {candidates.map((c) => {
          const isSelected = selected.has(c.code);
          return (
            <label
              key={c.id}
              className={cn(
                "grid grid-cols-[40px_60px_1fr_120px] gap-3 items-center px-4 py-2 border-b border-[var(--color-glass-border)] cursor-pointer transition-colors",
                isSelected ? "bg-[var(--color-cyan)]/10" : "hover:bg-[var(--color-glass)]",
              )}
            >
              <input
                type="checkbox"
                checked={isSelected}
                onChange={() => toggleOne(c.code)}
                aria-label={`Select ${c.code}`}
              />
              {/* eslint-disable-next-line @next/next/no-img-element */}
              <img
                src={thumbUrl({ thumbPath: c.thumbPath, code: c.code, id: c.id })}
                alt=""
                className="w-12 h-8 object-cover rounded"
              />
              <div className="min-w-0">
                <div className="font-mono text-sm font-semibold text-[var(--color-cyan)] truncate">
                  {c.code}
                </div>
                {c.title && (
                  <div className="text-xs text-[var(--color-fg-dim)] truncate">{c.title}</div>
                )}
              </div>
              <div className="text-right">
                <Link
                  href={`/id/${encodeURIComponent(c.code)}`}
                  onClick={(e) => e.stopPropagation()}
                  className="inline-flex items-center gap-1 text-xs text-[var(--color-fg-dim)] hover:text-[var(--color-fg)]"
                >
                  Open →
                </Link>
              </div>
            </label>
          );
        })}
      </div>
    </div>
  );
}
