"use client";
import { useCallback, useEffect, useMemo, useRef, useState, useTransition } from "react";
import { createPortal } from "react-dom";
import { Tag as TagIcon, FolderHeart, Trash2, Search, Gem, Star, Eye, Package, Loader2, ChevronRight, ListVideo } from "lucide-react";
import { useRouter } from "next/navigation";
import { fetchImageContextData } from "@/app/actions/imageMeta";
import { addTagToImage, removeTagFromImage } from "@/app/actions/tags";
import { addImageToCollection, removeImageFromCollection, createCollection } from "@/app/actions/collections";
import { setCoverVip, setCoverFavorite, setWatched, setCoverOwned } from "@/app/actions/coverMeta";
import { bulkSetMark, bulkSetWatched, bulkSetOwned, deleteImages } from "@/app/actions/bulk";
import { useClickOutside } from "@/lib/hooks/useClickOutside";
import { useUndoDeleteToast } from "@/components/select/UndoDeleteToast";
import { useSettings } from "@/components/settings/SettingsProvider";
import { useWatchQueue } from "@/components/queue/WatchQueueProvider";
import { dispatchQueueRemove } from "@/components/queue/watchQueueEvents";
import { cn } from "@/lib/utils";
import type { ContextData, ContextTagOption, ContextCollectionOption } from "@/lib/db/queries";

type SubmenuKey = "tags" | "collections" | null;

export function ImageContextMenu({
  imageIds,
  x,
  y,
  onClose,
}: {
  imageIds: number[];
  /** Single-cover convenience link href; only relevant in 1-cover mode. */
  singleHref?: string | null;
  x: number;
  y: number;
  onClose: () => void;
}) {
  const isBulk = imageIds.length > 1;
  const ref = useRef<HTMLDivElement>(null);
  const [data, setData] = useState<ContextData | null>(null);
  const [submenu, setSubmenu] = useState<SubmenuKey>(null);
  const [, start] = useTransition();
  const router = useRouter();
  const { show: showUndo } = useUndoDeleteToast();
  const { settings } = useSettings();
  const queue = useWatchQueue();
  const queuedCount = imageIds.reduce((acc, id) => acc + (queue.has(id) ? 1 : 0), 0);
  const allQueued = queuedCount === imageIds.length && imageIds.length > 0;

  useClickOutside(ref, onClose);

  useEffect(() => {
    let live = true;
    fetchImageContextData(imageIds).then((d) => { if (live) setData(d); });
    return () => { live = false; };
  }, [imageIds]);

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

  // Header — derive from cover info when available.
  const header = useMemo(() => {
    if (!data) return { title: isBulk ? `${imageIds.length} covers` : `Image #${imageIds[0]}`, sub: null as string | null };
    if (isBulk) {
      return { title: `${imageIds.length} covers selected`, sub: null };
    }
    const c = data.covers[0];
    if (!c) return { title: `Image #${imageIds[0]}`, sub: null };
    const title = c.code ?? `Image #${c.id}`;
    const sub = c.actresses.length > 0
      ? c.actresses.join(", ")
      : (c.title ?? null);
    return { title, sub };
  }, [data, imageIds, isBulk]);

  // Mark state — for single, read straight from data; for bulk, all-true means
  // every cover has it.
  const marks = useMemo(() => {
    if (!data || data.covers.length === 0) {
      return { vip: false, favorite: false, watched: false, owned: false };
    }
    const all = (pred: (c: ContextData["covers"][number]) => boolean) => data.covers.every(pred);
    return {
      vip: all((c) => c.isVip),
      favorite: all((c) => c.isFavorite),
      watched: all((c) => c.isWatched),
      owned: all((c) => c.isOwned),
    };
  }, [data]);

  const setMark = useCallback((kind: "vip" | "favorite" | "watched" | "owned") => {
    const wasOn = marks[kind];
    setData((d) => d && {
      ...d,
      covers: d.covers.map((c) => {
        if (kind === "vip") return { ...c, isVip: !wasOn, isFavorite: !wasOn ? false : c.isFavorite };
        if (kind === "favorite") return { ...c, isFavorite: !wasOn, isVip: !wasOn ? false : c.isVip };
        if (kind === "watched") return { ...c, isWatched: !wasOn };
        return { ...c, isOwned: !wasOn };
      }),
    });
    start(async () => {
      if (isBulk) {
        if (kind === "vip") await bulkSetMark(imageIds, !wasOn ? "vip" : "unmarked");
        else if (kind === "favorite") await bulkSetMark(imageIds, !wasOn ? "favorite" : "unmarked");
        else if (kind === "watched") await bulkSetWatched(imageIds, !wasOn);
        else await bulkSetOwned(imageIds, !wasOn);
      } else {
        const id = imageIds[0];
        if (kind === "vip") await setCoverVip(id, !wasOn);
        else if (kind === "favorite") await setCoverFavorite(id, !wasOn);
        else if (kind === "watched") await setWatched(id, !wasOn);
        else await setCoverOwned(id, !wasOn);
      }
      if (kind === "watched" && !wasOn) dispatchQueueRemove(imageIds);
      router.refresh();
    });
  }, [imageIds, isBulk, marks, router]);

  const toggleTag = useCallback((tag: { id: number; name: string }, on: boolean) => {
    setData((d) => d && {
      ...d,
      tags: d.tags.map((t) =>
        t.id === tag.id ? { ...t, count: on ? imageIds.length : 0 } : t,
      ),
    });
    start(async () => {
      await Promise.all(imageIds.map((id) => on ? addTagToImage(id, tag.name) : removeTagFromImage(id, tag.id)));
      router.refresh();
    });
  }, [imageIds, router]);

  const toggleCollection = useCallback((coll: { id: number; name: string }, on: boolean) => {
    setData((d) => d && {
      ...d,
      collections: d.collections.map((c) =>
        c.id === coll.id ? { ...c, count: on ? imageIds.length : 0 } : c,
      ),
    });
    start(async () => {
      await Promise.all(imageIds.map((id) => on ? addImageToCollection(coll.id, id) : removeImageFromCollection(coll.id, id)));
      router.refresh();
    });
  }, [imageIds, router]);

  const onDelete = (e: React.MouseEvent) => {
    // Honor the same Shift-for-permanent / useRecycleBin semantics as
    // SelectionBar so right-click delete doesn't bypass the user's
    // recycle-bin preference.
    const permanent = e.shiftKey || !settings.useRecycleBin;
    const target = [...imageIds];
    onClose();
    start(async () => {
      await deleteImages(target, permanent ? { permanent: true } : undefined);
      router.refresh();
      if (!permanent) showUndo(target);
    });
  };

  // Compute viewport-clamped position. We render via a portal to
  // document.body so a transformed ancestor (the page's fade-in
  // wrapper) can't hijack our fixed-positioning containing block.
  // Horizontal: center on the cursor so the menu sits symmetrically
  // around the click — keeps right-edge clicks from spilling offscreen.
  // Vertical: top of the menu sits at the cursor, expanding down.
  const margin = 8;
  const menuW = 380;
  const menuH = 320;
  const [coords, setCoords] = useState({ left: x, top: y });
  useEffect(() => {
    if (typeof window === "undefined") return;
    setCoords({
      left: Math.max(margin, Math.min(x - menuW / 2, window.innerWidth - menuW - margin)),
      top: Math.max(margin, Math.min(y + 20, window.innerHeight - menuH - margin)),
    });
  }, [x, y]);

  if (typeof document === "undefined") return null;
  return createPortal(
    <div
      ref={ref}
      onClick={(e) => e.stopPropagation()}
      onContextMenu={(e) => e.preventDefault()}
      className="fixed z-[80] flex items-start gap-2"
      style={{ left: coords.left, top: coords.top }}
    >
      <div
        className="w-[380px] rounded-xl shadow-2xl border border-[var(--color-glass-border-strong)] backdrop-blur-2xl overflow-hidden text-sm"
        style={{ background: "color-mix(in oklch, var(--color-bg-0) 96%, transparent)" }}
      >
        {/* Header */}
        <div className="px-3 py-2.5 border-b border-[var(--color-glass-border)]">
          <div className="text-[13px] font-mono font-semibold text-[var(--color-cyan)] truncate">
            {header.title}
          </div>
          {header.sub && (
            <div className="text-[11px] text-[var(--color-fg-dim)] truncate mt-0.5">{header.sub}</div>
          )}
          {isBulk && (
            <div className="text-[10px] uppercase tracking-wider font-mono text-[var(--color-cyan)] mt-1">
              ⚡ Applying to {imageIds.length} covers
            </div>
          )}
        </div>

        {/* Quick marks: VIP, Fav, Watched, Owned */}
        <div className="flex gap-1.5 px-3 py-2.5 border-b border-[var(--color-glass-border)]">
          <QBtn icon={Gem}     label="VIP"     active={marks.vip}      onClick={() => setMark("vip")}      />
          <QBtn icon={Star}    label="Fav"     active={marks.favorite} onClick={() => setMark("favorite")} accent="amber" />
          <QBtn icon={Eye}     label="Watched" active={marks.watched}  onClick={() => setMark("watched")}  accent="mint" />
          <QBtn icon={Package} label="Owned"   active={marks.owned}    onClick={() => setMark("owned")}    accent="violet" />
        </div>

        {/* Submenu trigger rows — open on hover (saves a click). Click
            still toggles, in case you want to close one without moving
            the cursor. */}
        <SubmenuRow
          icon={TagIcon}
          label="Tags"
          countLabel={data ? formatCountLabel(data.tags, data.selectedCount, isBulk) : "…"}
          open={submenu === "tags"}
          onHover={() => setSubmenu("tags")}
          onClick={() => setSubmenu(submenu === "tags" ? null : "tags")}
        />
        <SubmenuRow
          icon={FolderHeart}
          label="Collections"
          countLabel={data ? formatCountLabel(data.collections, data.selectedCount, isBulk) : "…"}
          open={submenu === "collections"}
          onHover={() => setSubmenu("collections")}
          onClick={() => setSubmenu(submenu === "collections" ? null : "collections")}
        />

        {/* Watch queue */}
        <button
          onClick={() => {
            if (allQueued) queue.removeMany(imageIds);
            else queue.addMany(imageIds);
            onClose();
          }}
          className="w-full flex items-center gap-2 px-3 py-2 hover:bg-[var(--color-glass)] border-t border-[var(--color-glass-border)]"
        >
          <ListVideo className={cn("w-3.5 h-3.5", allQueued ? "text-[var(--color-cyan)]" : "text-[var(--color-fg-dim)]")} />
          <span className="text-sm">
            {allQueued
              ? (isBulk ? `Remove ${imageIds.length} from Watch Queue` : "Remove from Watch Queue")
              : isBulk
                ? (queuedCount > 0 ? `Add ${imageIds.length - queuedCount} to Watch Queue` : `Add ${imageIds.length} to Watch Queue`)
                : "Add to Watch Queue"}
          </span>
        </button>

        {/* Delete */}
        <button
          onClick={onDelete}
          className="w-full flex items-center gap-2 px-3 py-2 text-[var(--color-coral)] hover:bg-[var(--color-coral)]/10 border-t border-[var(--color-glass-border)]"
        >
          <Trash2 className="w-3.5 h-3.5" />
          {isBulk ? `Delete ${imageIds.length} images` : "Delete image"}
        </button>
      </div>

      {/* Flyout */}
      {submenu === "tags" && data && (
        <Flyout
          title="Tags"
          options={data.tags}
          recent={data.recentTags.map((t) => ({ id: t.id, name: t.name, color: t.color, count: 0 }))}
          selectedCount={data.selectedCount}
          isBulk={isBulk}
          onToggle={(opt, on) => toggleTag(opt as ContextTagOption, on)}
          onCreate={async (name) => {
            for (const id of imageIds) await addTagToImage(id, name);
            const fresh = await fetchImageContextData(imageIds);
            setData(fresh);
            router.refresh();
          }}
        />
      )}
      {submenu === "collections" && data && (
        <Flyout
          title="Collections"
          options={data.collections.map((c) => ({ id: c.id, name: c.name, color: null, count: c.count }))}
          recent={data.recentCollections.map((c) => ({ id: c.id, name: c.name, color: null, count: 0 }))}
          selectedCount={data.selectedCount}
          isBulk={isBulk}
          onToggle={(opt, on) => toggleCollection(opt as ContextCollectionOption, on)}
          onCreate={async (name) => {
            const created = await createCollection(name);
            if (!created) return;
            for (const id of imageIds) await addImageToCollection(created.id, id);
            const fresh = await fetchImageContextData(imageIds);
            setData(fresh);
            router.refresh();
          }}
        />
      )}
    </div>,
    document.body,
  );
}

/* ---------- helpers ---------- */

function formatCountLabel(opts: Array<{ count: number }>, selected: number, isBulk: boolean): string {
  const fullyApplied = opts.filter((o) => o.count === selected && o.count > 0).length;
  if (!isBulk) return String(fullyApplied);
  const partial = opts.filter((o) => o.count > 0 && o.count < selected).length;
  if (partial > 0) return "mixed";
  return String(fullyApplied);
}

function QBtn({
  icon: Icon,
  label,
  active,
  accent = "cyan",
  onClick,
}: {
  icon: React.ComponentType<{ className?: string }>;
  label: string;
  active: boolean;
  accent?: "cyan" | "amber" | "mint" | "violet";
  onClick: () => void;
}) {
  const palette: Record<string, string> = {
    cyan: "bg-[var(--color-cyan)]/18 text-[var(--color-cyan)]",
    amber: "bg-amber-400/18 text-amber-300",
    mint: "bg-[var(--color-mint)]/18 text-[var(--color-mint)]",
    violet: "bg-[var(--color-violet)]/18 text-[var(--color-violet)]",
  };
  return (
    <button
      type="button"
      onClick={onClick}
      className={cn(
        "flex-1 min-w-0 flex items-center justify-center gap-1.5 px-3 py-2 rounded-md text-xs font-medium whitespace-nowrap transition-colors",
        active
          ? palette[accent]
          : "bg-[var(--color-glass)] text-[var(--color-fg-dim)] hover:bg-[var(--color-glass-strong)] hover:text-[var(--color-fg)]",
      )}
    >
      <Icon className="w-3.5 h-3.5 shrink-0" />
      {label}
    </button>
  );
}

function SubmenuRow({
  icon: Icon,
  label,
  countLabel,
  open,
  onHover,
  onClick,
}: {
  icon: React.ComponentType<{ className?: string }>;
  label: string;
  countLabel: string;
  open: boolean;
  onHover?: () => void;
  onClick: () => void;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      onMouseEnter={onHover}
      onFocus={onHover}
      className={cn(
        "w-full flex items-center gap-2 px-3 py-2 transition-colors text-left",
        open ? "bg-[var(--color-glass-strong)]" : "hover:bg-[var(--color-glass)]",
      )}
    >
      <Icon className={cn("w-3.5 h-3.5 shrink-0", open ? "text-[var(--color-cyan)]" : "text-[var(--color-fg-dim)]")} />
      <span className="flex-1 text-[var(--color-fg)]">{label}</span>
      <span
        className={cn(
          "text-[10px] font-mono px-1.5 py-0.5 rounded",
          open
            ? "bg-[var(--color-cyan)]/15 text-[var(--color-cyan)]"
            : "bg-[var(--color-glass)] text-[var(--color-fg-muted)]",
        )}
      >
        {countLabel}
      </span>
      <ChevronRight className={cn("w-3.5 h-3.5", open ? "text-[var(--color-cyan)]" : "text-[var(--color-fg-muted)]")} />
    </button>
  );
}

interface FlyoutOption {
  id: number;
  name: string;
  color: string | null;
  count: number;
}

function Flyout({
  title,
  options,
  recent,
  selectedCount,
  isBulk,
  onToggle,
  onCreate,
}: {
  title: string;
  options: FlyoutOption[];
  recent: FlyoutOption[];
  selectedCount: number;
  isBulk: boolean;
  onToggle: (opt: FlyoutOption, on: boolean) => void;
  onCreate: (name: string) => Promise<void>;
}) {
  const [query, setQuery] = useState("");
  const [creating, start] = useTransition();
  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    if (!q) return options;
    return options.filter((o) => o.name.toLowerCase().includes(q));
  }, [options, query]);
  const trimmed = query.trim();
  const exact = trimmed
    ? options.find((o) => o.name.toLowerCase() === trimmed.toLowerCase())
    : undefined;
  const showCreate = trimmed.length > 0 && !exact;

  const submitCreate = () => {
    if (!showCreate || creating) return;
    const name = trimmed;
    setQuery("");
    start(async () => { await onCreate(name); });
  };

  return (
    <div
      className="w-[300px] rounded-xl shadow-2xl border border-[var(--color-glass-border-strong)] backdrop-blur-2xl overflow-hidden text-sm relative"
      style={{ background: "color-mix(in oklch, var(--color-bg-0) 96%, transparent)" }}
    >
      <div className="px-3 py-2 border-b border-[var(--color-glass-border)] flex items-center justify-between">
        <span className="text-[10px] uppercase tracking-wider font-mono text-[var(--color-fg-muted)]">{title}</span>
        {isBulk && <span className="text-[9px] font-mono text-[var(--color-fg-muted)]">N / {selectedCount}</span>}
      </div>

      <div className="p-2 space-y-2">
        {/* Filter or Create */}
        <div className="relative">
          <Search className="w-3 h-3 absolute left-2 top-1/2 -translate-y-1/2 text-[var(--color-fg-muted)] pointer-events-none" />
          <input
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter") submitCreate(); }}
            placeholder={`Filter or add new ${title.toLowerCase()}…`}
            className="w-full bg-[var(--color-bg-1)]/60 text-xs pl-7 pr-[60px] py-1.5 rounded-md border border-[var(--color-glass-border)] outline-none focus:border-[var(--color-cyan)]"
          />
          {showCreate && (
            <button
              type="button"
              onClick={submitCreate}
              disabled={creating}
              className="absolute right-1 top-1/2 -translate-y-1/2 text-[10px] font-mono px-1.5 py-1 rounded bg-[var(--color-cyan)]/15 text-[var(--color-cyan)] border border-[var(--color-cyan)]/30 hover:bg-[var(--color-cyan)]/25 disabled:opacity-50"
            >
              {creating ? <Loader2 className="w-3 h-3 animate-spin" /> : "+ Create"}
            </button>
          )}
        </div>

        {/* Recent strip — only when no filter active */}
        {recent.length > 0 && !query && (
          <div>
            <div className="text-[9px] uppercase tracking-wider font-mono text-[var(--color-fg-muted)] px-1 mb-1">Recent</div>
            <div className="flex flex-wrap gap-1">
              {recent.map((r) => (
                <button
                  key={r.id}
                  type="button"
                  onClick={() => onToggle(r, true)}
                  className="text-[11px] px-2 py-0.5 rounded-full bg-[var(--color-violet)]/12 text-[var(--color-violet)] hover:bg-[var(--color-violet)]/20"
                >
                  + {r.name}
                </button>
              ))}
            </div>
          </div>
        )}

        {/* Scrollable list — caps at ~5 rows */}
        <div>
          <div className="flex items-center justify-between px-1 mb-1">
            <span className="text-[9px] uppercase tracking-wider font-mono text-[var(--color-fg-muted)]">All</span>
            <span className="text-[9px] uppercase tracking-wider font-mono text-[var(--color-fg-muted)]">↕ scroll</span>
          </div>
          {filtered.length === 0 ? (
            <div className="text-xs text-[var(--color-fg-muted)] italic px-2 py-2">
              {query ? "No matches" : `No ${title.toLowerCase()} yet`}
            </div>
          ) : (
            <div className="max-h-[150px] overflow-y-auto -mx-1 px-1">
              {filtered.map((o) => {
                const state = stateFor(o.count, selectedCount);
                return (
                  <button
                    key={o.id}
                    type="button"
                    onClick={() => onToggle(o, state !== "all")}
                    className={cn(
                      "w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-left text-sm transition-colors",
                      state === "all" && "text-[var(--color-violet)]",
                      state === "partial" && "text-amber-300",
                      state === "none" && "text-[var(--color-fg-dim)] hover:text-[var(--color-fg)]",
                      "hover:bg-[var(--color-glass)]",
                    )}
                  >
                    <span className="w-3 inline-flex justify-center text-xs">
                      {state === "all" ? "✓" : state === "partial" ? "−" : ""}
                    </span>
                    <span className="flex-1 truncate">{o.name}</span>
                    <span className="text-[10px] font-mono text-[var(--color-fg-muted)]">
                      {isBulk ? `${o.count} / ${selectedCount}` : (o.count > 0 ? "✓" : "")}
                    </span>
                  </button>
                );
              })}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function stateFor(count: number, total: number): "all" | "partial" | "none" {
  if (count === 0) return "none";
  if (count >= total) return "all";
  return "partial";
}
