"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { cn } from "@/lib/utils";
import { useMouseVector } from "@/components/hooks/use-mouse-vector";
import styles from "./image-trail.module.css";

interface TrailNode {
  id: string;
  src: string;
  x: number;
  y: number;
}

const MAX_NODES = 5;
const NODE_LIFETIME_MS = 700;
const AUTO_SPAWN_INTERVAL_MS = 900;

type SpawnMode = "cursor" | "auto" | "static";

// `crypto.randomUUID()` needs a secure context (https, or localhost/127.0.0.1)
// — opening the dev server from a phone over LAN (http://192.168.x.x:...) is
// not one, so it'd throw on every spawn there. These node ids are just React
// keys, nothing security-sensitive, so a plain fallback is fine.
function createNodeId(): string {
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
    return crypto.randomUUID();
  }
  return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
}

/**
 * Cursor-following image trail, clipped strictly to its own container —
 * `useMouseVector` is scoped to `containerRef`, never `window`, so nodes
 * can only ever spawn inside this component's own box (see
 * `use-mouse-vector.ts`). Spawned nodes auto-unmount after
 * `NODE_LIFETIME_MS` and the live set is hard-capped at `MAX_NODES`
 * (oldest evicted first) so continuous spawning can't grow the DOM without
 * bound.
 *
 * Three modes, chosen purely from real media queries (never user-agent
 * sniffing):
 * - `cursor` (hover-capable + fine pointer): spawns on mouse movement.
 * - `auto` (touch/no-hover — there's no cursor to drive it): same visual
 *   trail, but spawns on its own at a jittered interval so the panel still
 *   feels alive without requiring interaction.
 * - `static` (`prefers-reduced-motion`): no spawning at all, a static
 *   caption + photo grid instead (see `image-trail.module.css`).
 */
export function ImageTrail({
  images,
  label,
  className,
}: {
  images: string[];
  label?: string;
  className?: string;
}) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [nodes, setNodes] = useState<TrailNode[]>([]);
  const [spawnMode, setSpawnMode] = useState<SpawnMode>("static");

  const spawnNode = useCallback(
    (point: { x: number; y: number }) => {
      const id = createNodeId();
      const src = images[Math.floor(Math.random() * images.length)];

      setNodes((prev) => {
        const next = [...prev, { id, src, x: point.x, y: point.y }];
        return next.length > MAX_NODES ? next.slice(next.length - MAX_NODES) : next;
      });

      window.setTimeout(() => {
        setNodes((prev) => prev.filter((node) => node.id !== id));
      }, NODE_LIFETIME_MS);
    },
    [images],
  );

  useEffect(() => {
    const hoverFineQuery = window.matchMedia("(hover: hover) and (pointer: fine)");
    const touchQuery = window.matchMedia("(hover: none), (max-width: 767px)");
    const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");

    function update() {
      if (motionQuery.matches) {
        setSpawnMode("static");
      } else if (hoverFineQuery.matches) {
        setSpawnMode("cursor");
      } else if (touchQuery.matches) {
        setSpawnMode("auto");
      } else {
        setSpawnMode("static");
      }
    }

    update();
    hoverFineQuery.addEventListener("change", update);
    touchQuery.addEventListener("change", update);
    motionQuery.addEventListener("change", update);
    return () => {
      hoverFineQuery.removeEventListener("change", update);
      touchQuery.removeEventListener("change", update);
      motionQuery.removeEventListener("change", update);
    };
  }, []);

  useMouseVector(containerRef, spawnNode, { minDistance: 60, enabled: spawnMode === "cursor" });

  useEffect(() => {
    if (spawnMode !== "auto") return;
    const container = containerRef.current;
    if (!container) return;

    let timeoutId: number;

    function tick() {
      const rect = container!.getBoundingClientRect();
      spawnNode({ x: Math.random() * rect.width, y: Math.random() * rect.height });
      timeoutId = window.setTimeout(tick, AUTO_SPAWN_INTERVAL_MS * (0.7 + Math.random() * 0.6));
    }

    timeoutId = window.setTimeout(tick, AUTO_SPAWN_INTERVAL_MS);
    return () => window.clearTimeout(timeoutId);
  }, [spawnMode, spawnNode]);

  return (
    <div ref={containerRef} className={cn(styles.trail, className)}>
      <div aria-hidden="true" className={styles.spawnLayer}>
        <AnimatePresence>
          {nodes.map((node) => (
            <motion.img
              key={node.id}
              src={node.src}
              alt=""
              className={styles.node}
              style={{ left: node.x, top: node.y }}
              initial={{ opacity: 0, scale: 0.1 }}
              animate={{ opacity: 1, scale: 1 }}
              exit={{ opacity: 0, scale: 0.9 }}
              transition={{ duration: 0.6, ease: "easeOut" }}
            />
          ))}
        </AnimatePresence>
      </div>

      {label && (
        <p className={cn(styles.label, "font-heading text-2xl font-semibold text-kolabora-neutral-dark sm:text-3xl")}>
          {label}
        </p>
      )}

      <div aria-hidden="true" className={styles.fallbackGrid}>
        {images.map((src, i) => (
          // eslint-disable-next-line @next/next/no-img-element -- dynamic/external URLs, not on next/image allowlist
          <img key={i} src={src} alt="" loading="lazy" />
        ))}
      </div>
    </div>
  );
}
