"use client";

import { useEffect, useId, useRef, useState, useSyncExternalStore } from "react";
import { createPortal } from "react-dom";
import { motion } from "framer-motion";
import Image from "next/image";
import { X, ExternalLink, Download, ChevronLeft, ChevronRight } from "lucide-react";
import { SiGitea } from "react-icons/si";
import { LiveDot, InProgressBadge } from "@/components/sections/ProjectsCarousel";
import type { Dictionary } from "@/lib/dictionaries";

type Project = Dictionary["projects"]["items"][number];

interface ProjectModalProps {
  project: Project | null;
  dict: Dictionary;
  onClose: () => void;
}

// SSR-safe client gate for the portal: false on the server, true after
// hydration — no effect, no setState, no hydration mismatch.
const emptySubscribe = () => () => {};
function useIsClient() {
  return useSyncExternalStore(emptySubscribe, () => true, () => false);
}

export default function ProjectModal({ project, dict, onClose }: ProjectModalProps) {
  const mounted = useIsClient();
  const [imgIndex, setImgIndex] = useState(0);
  // Track which screenshot sources have finished decoding so we can fade them
  // in over a dark placeholder — never a blank/bright pop.
  const [loaded, setLoaded] = useState<Record<string, boolean>>({});
  // Retain the last project so content stays rendered while the panel fades out.
  const [data, setData] = useState<Project | null>(project);

  const open = project !== null;
  const titleId = useId();
  const panelRef = useRef<HTMLDivElement>(null);
  const restoreFocusRef = useRef<HTMLElement | null>(null);

  // Latch the project + reset the gallery when a new one opens. Adjusting state
  // during render (guarded) is the recommended alternative to a prop-sync effect.
  if (project && project !== data) {
    setData(project);
    setImgIndex(0);
  }

  // Body scroll lock with scrollbar-width compensation (prevents layout shift).
  useEffect(() => {
    if (!open) return;
    const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
    const prevOverflow = document.body.style.overflow;
    const prevPadding = document.body.style.paddingRight;
    document.body.style.overflow = "hidden";
    if (scrollbarWidth > 0) document.body.style.paddingRight = `${scrollbarWidth}px`;
    return () => {
      document.body.style.overflow = prevOverflow;
      document.body.style.paddingRight = prevPadding;
    };
  }, [open]);

  // Focus management: trap Tab, close on Escape, restore focus on close.
  useEffect(() => {
    if (!open) return;
    restoreFocusRef.current = document.activeElement as HTMLElement | null;
    panelRef.current?.focus();

    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") { onClose(); return; }
      if (e.key !== "Tab" || !panelRef.current) return;
      const focusables = panelRef.current.querySelectorAll<HTMLElement>(
        'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])',
      );
      if (focusables.length === 0) return;
      const first = focusables[0];
      const last = focusables[focusables.length - 1];
      if (e.shiftKey && document.activeElement === first) {
        e.preventDefault();
        last.focus();
      } else if (!e.shiftKey && document.activeElement === last) {
        e.preventDefault();
        first.focus();
      }
    };

    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("keydown", onKey);
      restoreFocusRef.current?.focus?.();
    };
  }, [open, onClose]);

  if (!mounted) return null;

  const screenshots = data?.screenshots ?? [];
  const hasGallery = screenshots.length > 1;
  const markLoaded = (src: string) =>
    setLoaded((prev) => (prev[src] ? prev : { ...prev, [src]: true }));

  const overlay = (
    // Static container — NEVER animates opacity. Animating opacity here is what
    // forced the backdrop-filter into a grouped buffer and produced the flash.
    <div
      className="fixed inset-0 z-[80] flex items-center justify-center p-4"
      aria-hidden={!open}
      style={{ pointerEvents: open ? "auto" : "none" }}
    >
      {/* Backdrop — own animated layer. will-change + translateZ promote it to a
          dedicated compositing layer so a panel/image repaint can't force the
          backdrop-filter to recomposite (the source of the bright flash). */}
      <motion.div
        className="absolute inset-0 bg-black/75 backdrop-blur-sm"
        initial={false}
        animate={{ opacity: open ? 1 : 0 }}
        transition={{ duration: 0.2, ease: "easeOut" }}
        style={{ willChange: "opacity", transform: "translateZ(0)" }}
        onClick={onClose}
      />

      {/* Panel — independent sibling (not nested under an opacity-animated node) */}
      <motion.div
        ref={panelRef}
        role="dialog"
        aria-modal="true"
        aria-labelledby={titleId}
        tabIndex={-1}
        initial={false}
        animate={{ opacity: open ? 1 : 0, scale: open ? 1 : 0.96, y: open ? 0 : 8 }}
        transition={{ duration: 0.22, ease: [0.16, 1, 0.3, 1] }}
        className="relative max-h-[90vh] w-full max-w-3xl overflow-y-auto rounded-2xl border border-white/10 bg-[#0f0d1a] shadow-2xl outline-none"
        onClick={(e) => e.stopPropagation()}
      >
        {data && (
          <>
            <button
              type="button"
              onClick={onClose}
              className="absolute right-4 top-4 z-10 flex h-8 w-8 items-center justify-center rounded-lg bg-white/5 text-text-secondary transition-colors hover:bg-white/10 hover:text-text-primary"
              aria-label="Close"
            >
              <X className="h-4 w-4" />
            </button>

            {/* Screenshot gallery — every frame is a permanently-mounted stacked
                layer. Keys are the stable loop index (never `src` or `imgIndex`),
                so nothing ever remounts → no decode resets. `loading="eager"`
                preloads the whole gallery on open; opacity is gated on decode so
                each frame fades in over a dark placeholder (no instant pop). */}
            {screenshots.length > 0 && (
              <div>
                <div className="relative aspect-video w-full overflow-hidden rounded-t-2xl bg-surface/50">
                  {screenshots.map((src, i) => (
                    <Image
                      key={i}
                      src={src}
                      alt={`${data.title} screenshot ${i + 1}`}
                      fill
                      loading="eager"
                      sizes="(max-width: 768px) 100vw, 768px"
                      draggable={false}
                      className="object-contain transition-opacity duration-300 ease-out"
                      style={{ opacity: i === imgIndex && loaded[src] ? 1 : 0 }}
                      onLoad={() => markLoaded(src)}
                    />
                  ))}

                  {hasGallery && (
                    <>
                      <button
                        type="button"
                        onClick={() => setImgIndex((idx) => (idx - 1 + screenshots.length) % screenshots.length)}
                        className="absolute left-3 top-1/2 z-10 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-lg bg-black/60 text-white transition-colors hover:bg-black/80"
                        aria-label="Previous screenshot"
                      >
                        <ChevronLeft className="h-4 w-4" />
                      </button>
                      <button
                        type="button"
                        onClick={() => setImgIndex((idx) => (idx + 1) % screenshots.length)}
                        className="absolute right-3 top-1/2 z-10 flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded-lg bg-black/60 text-white transition-colors hover:bg-black/80"
                        aria-label="Next screenshot"
                      >
                        <ChevronRight className="h-4 w-4" />
                      </button>

                      <div className="absolute bottom-3 left-1/2 z-10 flex -translate-x-1/2 gap-1.5">
                        {screenshots.map((_, i) => (
                          <button
                            key={i}
                            type="button"
                            onClick={() => setImgIndex(i)}
                            className={`h-1.5 rounded-full transition-all duration-200 ${i === imgIndex ? "w-5 bg-white" : "w-1.5 bg-white/40"}`}
                            aria-label={`Screenshot ${i + 1}`}
                          />
                        ))}
                      </div>
                    </>
                  )}
                </div>

                {hasGallery && (
                  <div className="flex gap-2 overflow-x-auto bg-white/[0.02] px-4 py-3">
                    {screenshots.map((src, i) => (
                      <button
                        key={i}
                        type="button"
                        onClick={() => setImgIndex(i)}
                        className={`relative h-14 w-24 shrink-0 overflow-hidden rounded-lg border-2 transition-all ${
                          i === imgIndex
                            ? "border-purple-400 opacity-100"
                            : "border-transparent opacity-50 hover:opacity-80"
                        }`}
                      >
                        <Image src={src} alt="" fill loading="eager" className="object-cover" sizes="96px" draggable={false} />
                      </button>
                    ))}
                  </div>
                )}
              </div>
            )}

            {/* Content */}
            <div className="p-6">
              <div className="mb-3 flex flex-wrap items-center gap-2">
                <span
                  className={`rounded-md px-2 py-0.5 font-mono text-[10px] uppercase tracking-wider ${
                    data.type === "client"
                      ? "border border-purple-400/30 bg-purple-500/15 text-purple-300"
                      : "border border-border/50 bg-white/5 text-text-secondary"
                  }`}
                >
                  {data.type === "client" ? dict.projects.clientWork : dict.projects.personal}
                </span>
                {data.live && <LiveDot />}
                {data.inProgress && <InProgressBadge label={dict.projects.inProgress} />}
              </div>

              <h2 id={titleId} className="mb-4 font-display text-2xl font-medium tracking-tight text-text-primary">
                {data.title}
              </h2>

              <p className="mb-5 leading-relaxed text-text-secondary">{data.about}</p>

              <div className="mb-6 flex flex-wrap gap-1.5">
                {data.tags.map((tag) => (
                  <span
                    key={tag}
                    className="rounded-md bg-surface/60 px-2.5 py-1 font-mono text-xs text-text-secondary"
                  >
                    {tag}
                  </span>
                ))}
              </div>

              <div className="flex flex-wrap gap-2">
                {data.live && data.url && (
                  <a
                    href={data.url}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="inline-flex items-center gap-1.5 rounded-lg border border-emerald-400/30 bg-emerald-500/10 px-4 py-2 text-sm font-medium text-emerald-300 transition-all hover:bg-emerald-500/20"
                  >
                    <ExternalLink className="h-3.5 w-3.5" />
                    {dict.projects.viewProject}
                  </a>
                )}
                {data.github && (
                  <a
                    href={data.github}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="inline-flex items-center gap-1.5 rounded-lg border border-border/50 bg-white/5 px-4 py-2 text-sm text-text-secondary transition-all hover:border-purple-400/30 hover:bg-white/10 hover:text-text-primary"
                  >
                    <SiGitea className="h-3.5 w-3.5" />
                    {dict.projects.source}
                  </a>
                )}
                {data.download && (
                  <a
                    href={data.download}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="inline-flex items-center gap-1.5 rounded-lg border border-border/50 bg-white/5 px-4 py-2 text-sm text-text-secondary transition-all hover:border-purple-400/30 hover:bg-white/10 hover:text-text-primary"
                  >
                    <Download className="h-3.5 w-3.5" />
                    {dict.projects.download}
                  </a>
                )}
              </div>
            </div>
          </>
        )}
      </motion.div>
    </div>
  );

  return createPortal(overlay, document.body);
}
