"use client";
import Link from "next/link";
import { memo, useEffect, useState, useTransition } from "react";
import { useRouter } from "next/navigation";
import { Check, Star, Eye, EyeOff, Gem, Package, Play, Captions } from "lucide-react";
import { useSelection } from "@/components/select/SelectionProvider";
import { ImageContextMenu } from "@/components/grid/ImageContextMenu";
import { cn, coverHref } from "@/lib/utils";
import { thumbUrl } from "@/lib/assetUrls";
import { setWatched, setCoverVip, setCoverFavorite, setCoverOwned } from "@/app/actions/coverMeta";
import { dispatchQueueRemove } from "@/components/queue/watchQueueEvents";
import { useVideoIndex } from "@/components/video/VideoIndexProvider";
import { VideoPlayerModal } from "@/components/video/VideoPlayerModal";

export interface CardImage {
  id: number;
  thumbPath: string;
  width: number;
  height: number;
  code: string | null;
  title: string | null;
  rating: number | null;
  watched: boolean;
  isVip: boolean;
  isFavorite: boolean;
  isOwned: boolean;
  studioName: string | null;
  actresses: Array<{ id: number; name: string; slug: string }>;
  /** Mirror of images.has_video — server-rendered fallback so the
   *  play button shows correctly before the client-side video index
   *  provider hydrates. */
  hasVideo?: boolean;
  /** Mirror of images.has_subtitle. Same reason as hasVideo. */
  hasSubtitle?: boolean;
}

function ImageCardImpl({ image, view = "landscape" }: { image: CardImage; view?: "portrait" | "landscape" }) {
  const sel = useSelection();
  const router = useRouter();
  const selected = sel.has(image.id);
  const anySelected = sel.ids.size > 0;
  // Snapshot imageIds at the moment the context menu opens. Otherwise the
  // prop array reference changes on every parent re-render, retriggering
  // the menu's data-fetch effect and risking the menu acting on a
  // selection that drifted between right-click and click.
  const [menuPos, setMenuPos] = useState<{ x: number; y: number; ids: number[] } | null>(null);
  const [watched, setLocalWatched] = useState(image.watched);
  const [vip, setLocalVip] = useState(image.isVip);
  const [favorite, setLocalFavorite] = useState(image.isFavorite);
  const [owned, setLocalOwned] = useState(image.isOwned);
  const [playing, setPlaying] = useState(false);
  const videoIdx = useVideoIndex();
  // Provider is the live source of truth once it has scanned. Until
  // then (cold boot of the server before /api/video-status responds)
  // fall back to the SSR'd flags from the DB so play buttons / CC
  // chips don't flicker in.
  const providerReady = videoIdx.lastScannedAt > 0;
  const hasVideo = providerReady ? videoIdx.hasVideo(image.code) : !!image.hasVideo;
  const hasSubtitle = providerReady ? videoIdx.hasSubtitle(image.code) : !!image.hasSubtitle;
  const [, startMutate] = useTransition();

  useEffect(() => { setLocalWatched(image.watched); }, [image.watched]);
  useEffect(() => { setLocalVip(image.isVip); }, [image.isVip]);
  useEffect(() => { setLocalFavorite(image.isFavorite); }, [image.isFavorite]);
  useEffect(() => { setLocalOwned(image.isOwned); }, [image.isOwned]);

  const toggleWatched = (e: React.MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
    const next = !watched;
    const prev = watched;
    setLocalWatched(next);
    startMutate(async () => {
      try {
        await setWatched(image.id, next);
        if (next) dispatchQueueRemove(image.id);
        router.refresh();
      } catch (err) {
        setLocalWatched(prev);
        console.error("[toggleWatched] failed:", err);
      }
    });
  };

  const toggleVip = (e: React.MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
    const next = !vip;
    const prevVip = vip;
    const prevFav = favorite;
    setLocalVip(next);
    if (next) setLocalFavorite(false); // mutually exclusive
    startMutate(async () => {
      try {
        await setCoverVip(image.id, next);
        router.refresh();
      } catch (err) {
        setLocalVip(prevVip);
        setLocalFavorite(prevFav);
        console.error("[toggleVip] failed:", err);
      }
    });
  };

  const toggleFavorite = (e: React.MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
    const next = !favorite;
    const prevFav = favorite;
    const prevVip = vip;
    setLocalFavorite(next);
    if (next) setLocalVip(false); // mutually exclusive
    startMutate(async () => {
      try {
        await setCoverFavorite(image.id, next);
        router.refresh();
      } catch (err) {
        setLocalFavorite(prevFav);
        setLocalVip(prevVip);
        console.error("[toggleFavorite] failed:", err);
      }
    });
  };

  const toggleOwned = (e: React.MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
    const next = !owned;
    const prev = owned;
    setLocalOwned(next);
    startMutate(async () => {
      try {
        await setCoverOwned(image.id, next);
        router.refresh();
      } catch (err) {
        setLocalOwned(prev);
        console.error("[toggleOwned] failed:", err);
      }
    });
  };

  const handleCheckbox = (e: React.MouseEvent) => {
    e.preventDefault();
    e.stopPropagation();
    sel.toggle(image.id);
  };

  const handleCardClick = (e: React.MouseEvent) => {
    if (anySelected) {
      e.preventDefault();
      sel.toggle(image.id);
    }
  };

  const handleContextMenu = (e: React.MouseEvent) => {
    e.preventDefault();
    const ids = sel.has(image.id) ? Array.from(sel.ids) : [image.id];
    setMenuPos({ x: e.clientX, y: e.clientY, ids });
  };

  return (
    <>
      <Link
        href={coverHref(image)}
        onClick={handleCardClick}
        onContextMenu={handleContextMenu}
        draggable={false}
        className={cn(
          // No `glass` here — backdrop-filter on every card kills scroll
          // FPS, and the card root is fully covered by the cover image
          // anyway. Inner overlays (badges, pills) keep their blurs.
          "cover-hero-frame group relative flex flex-col justify-end rounded-2xl overflow-hidden bg-[var(--color-glass)] border border-[var(--color-glass-border)] cursor-default transition-shadow hover:border-[var(--color-glass-border-strong)]",
          selected && "ring-2 ring-[var(--color-cyan)] shadow-[var(--shadow-glow-cyan)]",
          anySelected && !selected && "opacity-70 hover:opacity-100",
        )}
        style={{ breakInside: "avoid" } as React.CSSProperties}
      >
        <div className="cover-hero-hover relative">
          {view === "portrait" ? (
            // JAV covers are composite back+spine+front, ~800×538 with the
            // front taking the rightmost ~373×538. We crop to that aspect
            // by anchoring the thumb to the right and scaling to fit
            // height — pure CSS, no extra fetch.
            <div
              className="w-full block transition-transform duration-500 group-hover:scale-[1.02]"
              style={{
                aspectRatio: "373 / 538",
                backgroundImage: `url(${thumbUrl({ thumbPath: image.thumbPath, code: image.code, id: image.id })})`,
                backgroundSize: "auto 100%",
                backgroundPosition: "right center",
                backgroundRepeat: "no-repeat",
              }}
              role="img"
              aria-label={image.title ?? image.code ?? ""}
            />
          ) : (
            // eslint-disable-next-line @next/next/no-img-element
            <img
              src={thumbUrl({ thumbPath: image.thumbPath, code: image.code, id: image.id })}
              alt={image.title ?? image.code ?? ""}
              loading="lazy"
              draggable={false}
              width={image.width}
              height={image.height}
              className="w-full h-auto block transition-transform duration-500 group-hover:scale-[1.02]"
            />
          )}

          {hasVideo && (
            <button
              type="button"
              onClick={(e) => { e.preventDefault(); e.stopPropagation(); setPlaying(true); }}
              aria-label="Play video"
              title="Play video"
              className={cn(
                "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-20 inline-flex items-center justify-center backdrop-blur-md text-white/95 cursor-pointer transition-colors hover:text-[var(--color-cyan)] hover:[animation:play-pulse_1.2s_ease-out_infinite] active:scale-95",
                view === "portrait" ? "w-16 h-11 rounded-md" : "w-20 h-14 rounded-lg",
              )}
              style={{
                background: "rgba(20,20,28,0.75)",
                border: 0,
                boxShadow: "0 6px 16px rgba(0,0,0,0.55), 0 1px 2px rgba(0,0,0,0.5)",
              }}
            >
              <Play className={view === "portrait" ? "w-[18px] h-[18px]" : "w-6 h-6"} style={{ fill: "currentColor" }} />
            </button>
          )}

          <button
            onClick={handleCheckbox}
            aria-label={selected ? "Deselect" : "Select"}
            className={cn(
              "absolute top-3 right-3 z-20 w-8 h-8 rounded-md grid place-items-center transition-all backdrop-blur-md border-2",
              selected
                ? "bg-[var(--color-cyan)] border-[var(--color-cyan)] text-black shadow-[var(--shadow-glow-cyan)]"
                : "bg-black/40 border-white/50 text-transparent",
              !selected && !anySelected && "opacity-0 group-hover:opacity-100",
            )}
          >
            <Check className="w-4 h-4" strokeWidth={3} />
          </button>

          {hasVideo && !vip && !favorite && (
            <span
              className="absolute top-3 left-3 z-10 flex items-center gap-1 text-[11px] uppercase tracking-wider font-mono font-semibold px-2.5 py-0.5 rounded-full bg-black/80 shadow-md"
              style={{
                color: "var(--color-cyan)",
                border: "1px solid color-mix(in oklch, var(--color-cyan) 60%, transparent)",
                textShadow: "0 1px 2px rgba(0,0,0,0.9)",
              }}
              title="Has playable video"
            >
              <Play className="w-3 h-3" style={{ fill: "currentColor" }} /> Video
            </span>
          )}
          {(vip || favorite) && (
            <span
              className="absolute top-3 left-3 z-10 flex items-center gap-1 text-[11px] uppercase tracking-wider font-mono font-semibold px-2.5 py-0.5 rounded-full bg-black/80 backdrop-blur-md shadow-md"
              style={{
                color: vip ? "var(--color-cyan)" : "#fbbf24",
                border: `1px solid ${vip ? "var(--color-cyan)" : "#fbbf24"}aa`,
                textShadow: "0 1px 2px rgba(0,0,0,0.9)",
              }}
            >
              {vip ? <Gem className="w-3 h-3" /> : <Star className="w-3 h-3" style={{ fill: "#fbbf24" }} />}
              {vip ? "VIP" : "Favorite"}
            </span>
          )}

          <div
            className={cn(
              "absolute right-3 z-20 flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity",
              view === "portrait"
                ? "bottom-7 flex-col items-end"
                : "bottom-3 flex-row items-center",
            )}
          >
            <button
              type="button"
              onClick={toggleVip}
              title={vip ? "Unmark VIP" : "Mark VIP"}
              className={cn(
                "w-8 h-8 grid place-items-center rounded-md cursor-pointer transition-all duration-150",
                "hover:scale-110 hover:ring-2 hover:ring-cyan-300 hover:shadow-lg active:scale-95",
                vip
                  ? "bg-cyan-400/40 text-cyan-200 hover:bg-cyan-400/60"
                  : "bg-black/70 text-white hover:bg-cyan-400/30 hover:text-cyan-200",
              )}
            >
              <Gem className="w-4 h-4" />
            </button>
            <button
              type="button"
              onClick={toggleFavorite}
              title={favorite ? "Unmark Favorite" : "Mark Favorite"}
              className={cn(
                "w-8 h-8 grid place-items-center rounded-md cursor-pointer transition-all duration-150",
                "hover:scale-110 hover:ring-2 hover:ring-amber-300 hover:shadow-lg active:scale-95",
                favorite
                  ? "bg-amber-400/40 text-amber-200 hover:bg-amber-400/60"
                  : "bg-black/70 text-white hover:bg-amber-400/30 hover:text-amber-200",
              )}
            >
              <Star className={cn("w-4 h-4", favorite && "fill-amber-200")} />
            </button>
            <button
              type="button"
              onClick={toggleWatched}
              title={watched ? "Mark as not watched" : "Mark as watched"}
              className={cn(
                "w-8 h-8 grid place-items-center rounded-md cursor-pointer transition-all duration-150",
                "hover:scale-110 hover:ring-2 hover:ring-emerald-300 hover:shadow-lg active:scale-95",
                watched
                  ? "bg-emerald-400/40 text-emerald-200 hover:bg-emerald-400/60"
                  : "bg-black/70 text-white hover:bg-emerald-400/30 hover:text-emerald-200",
              )}
            >
              {watched ? <Eye className="w-4 h-4" /> : <EyeOff className="w-4 h-4" />}
            </button>
            <button
              type="button"
              onClick={toggleOwned}
              title={owned ? "Unmark Owned" : "Mark Owned"}
              className={cn(
                "w-8 h-8 grid place-items-center rounded-md cursor-pointer transition-all duration-150",
                "hover:scale-110 hover:ring-2 hover:ring-violet-300 hover:shadow-lg active:scale-95",
                owned
                  ? "bg-violet-400/40 text-violet-200 hover:bg-violet-400/60"
                  : "bg-black/70 text-white hover:bg-violet-400/30 hover:text-violet-200",
              )}
            >
              <Package className={cn("w-4 h-4", owned && "fill-violet-200/20")} />
            </button>
          </div>

          <div className="absolute inset-x-0 bottom-0 z-10 p-3 pt-12 bg-gradient-to-t from-black/90 via-black/60 to-transparent">
            {image.code && (
              <div className="flex items-center gap-2 min-w-0">
                <span
                  className="text-base uppercase tracking-wider font-mono font-bold text-[var(--color-cyan)] truncate"
                  style={{ textShadow: "0 1px 3px rgba(0,0,0,0.9)" }}
                >
                  {image.code}
                </span>
                {hasSubtitle && (
                  <span
                    title={hasVideo ? "Has playable video and subtitles" : "Has subtitle file"}
                    className="inline-flex items-center gap-1 text-[10px] uppercase tracking-wider font-mono font-semibold px-1.5 py-0.5 rounded border border-[var(--color-mint)]/50 bg-black/60 backdrop-blur-md text-[var(--color-mint)] shrink-0"
                    style={{ textShadow: "0 1px 2px rgba(0,0,0,0.9)" }}
                  >
                    <Captions className="w-3 h-3" /> CC
                  </span>
                )}
              </div>
            )}
            {image.actresses.length > 0 && (
              <div
                className="text-xs text-white/75 truncate mt-0.5"
                style={{ textShadow: "0 1px 3px rgba(0,0,0,0.9)" }}
              >
                {image.actresses.map((a, i) => (
                  <span key={a.id}>
                    {i > 0 && <span className="text-white/40">, </span>}
                    {/* Programmatic navigation rather than <Link>: HTML
                        forbids nested <a> elements, and the cover card
                        is wrapped in a Link of its own. */}
                    <span
                      role="link"
                      tabIndex={0}
                      onClick={(e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        router.push(`/actress/${a.slug}`);
                      }}
                      onKeyDown={(e) => {
                        if (e.key === "Enter" || e.key === " ") {
                          e.preventDefault();
                          e.stopPropagation();
                          router.push(`/actress/${a.slug}`);
                        }
                      }}
                      className="cursor-pointer hover:text-[var(--color-violet)] hover:underline underline-offset-2"
                    >
                      {a.name}
                    </span>
                  </span>
                ))}
              </div>
            )}
          </div>
        </div>
      </Link>
      {menuPos && (
        <ImageContextMenu
          imageIds={menuPos.ids}
          singleHref={menuPos.ids.length > 1 ? null : coverHref(image)}
          x={menuPos.x}
          y={menuPos.y}
          onClose={() => setMenuPos(null)}
        />
      )}
      {playing && image.code && (
        <VideoPlayerModal
          code={image.code}
          actresses={image.actresses}
          onClose={() => setPlaying(false)}
        />
      )}
    </>
  );
}

// Memoized so cards don't re-render on every Virtuoso scroll tick or
// parent state change. Equality is shallow on `image` + `view` — the
// card's mutable state (selection, watched, vip, etc.) is held inside
// the component itself, so a stable `image` reference + same `view`
// safely skip the re-render.
export const ImageCard = memo(ImageCardImpl, (a, b) =>
  a.view === b.view && a.image === b.image,
);
