"use client";

import { useLayoutEffect, useRef, type ReactNode } from "react";
import { cn } from "@/lib/utils";
import { gsap, DUR, MM } from "@/lib/gsap";
import { STROKE_IN, WIPE_IN, playSheen, primeMark } from "@/components/motion/Draw";

export interface DrawMarkProps {
  /** `load` = on mount (hero), `view` = at 80 % in view (once), `scrub` = scroll-tied (Prinzip). */
  play?: "load" | "view" | "scrub";
  /** One-time champagne sweep over the wolf stroke after drawing. */
  sheen?: boolean;
  /** Continue the neck stroke downward into the first hairline (hero only). */
  handoff?: boolean;
  /** Length of the handoff hairline in px when the SVG has no `#handoff` path (24–48). */
  handoffLength?: number;
  delay?: number;
  className?: string;
  onComplete?: () => void;
  /** Receives the handoff x in px from the wrapper's left edge (for `<Rule origin={x}/>`). */
  onHandoff?: (x: number) => void;
  /** `<BrandMark/>` (Server Component, passed as children). */
  children: ReactNode;
}

/** Timing of the draw sequence in seconds (spec §5.5). */
const T = { vLeft: [0, 0.35], vRight: [0.25, 0.35], wolf: [0.5, 0.7], detail: [1.0, 0.3], handoff: [1.2, 0.2], sheen: 1.5 } as const;

/**
 * Client shell around <BrandMark/>: V-left → V-right → wolf → (handoff) → sheen.
 * Paths are ordered by the classes `.wolfmark-v` / `.wolfmark-wolf`; an inline
 * client SVG without those classes is drawn in DOM order.
 */
export function DrawMark({
  play = "load",
  sheen = false,
  handoff = false,
  handoffLength = 40,
  delay = 0,
  className,
  onComplete,
  onHandoff,
  children,
}: DrawMarkProps) {
  const wrap = useRef<HTMLSpanElement>(null);
  const line = useRef<HTMLSpanElement>(null);
  const onCompleteRef = useRef(onComplete);
  const onHandoffRef = useRef(onHandoff);
  /* Read imperatively, never a dependency: the hero measures the gap and pushes a new
     length on every resize, and re-running the effect would `mm.revert()` the whole
     matchMedia scope and replay the monogram draw mid-interaction. */
  const handoffLengthRef = useRef(handoffLength);
  useLayoutEffect(() => {
    onCompleteRef.current = onComplete;
    onHandoffRef.current = onHandoff;
  });
  useLayoutEffect(() => {
    handoffLengthRef.current = handoffLength;
    const el = line.current;
    if (el) el.style.height = `${handoffLength}px`;
  }, [handoffLength]);

  useLayoutEffect(() => {
    const el = wrap.current;
    const svg = el?.querySelector("svg");
    if (!el || !svg) return;

    const all = Array.from(svg.querySelectorAll<SVGPathElement>("path"));
    const handoffPath = all.find((p) => p.classList.contains("wolfmark-handoff")) ?? null;
    let vs = all.filter((p) => p.classList.contains("wolfmark-v"));
    let wolves = all.filter((p) => p.classList.contains("wolfmark-wolf") && p !== handoffPath);
    if (!vs.length && !wolves.length) {
      // Unknown SVG: everything in DOM order – first two strokes as "V", the rest as "wolf".
      const drawable = all.filter((p) => p.hasAttribute("data-draw"));
      const seq = drawable.length ? drawable : all;
      vs = seq.slice(0, 2);
      wolves = seq.slice(2);
    }
    const mainWolf = wolves[0] ?? null;
    const details = wolves.slice(1);
    const sequence = [...vs, ...wolves, ...(handoffPath ? [handoffPath] : [])];
    const lineEl = line.current;
    const useLine = handoff && !handoffPath && !!lineEl;

    /* The path geometry is fixed in SVG user space – only the CTM changes on resize.
       Sample once (2 × 121 `getPointAtLength` calls), then re-project the cached point. */
    let anchor: { x: number; y: number } | null | undefined;
    const sampleAnchor = (): { x: number; y: number } | null => {
      if (!mainWolf) return null;
      const end = mainWolf.getPointAtLength(mainWolf.getTotalLength());
      const pts: { x: number; y: number }[] = [];
      let maxY = -Infinity;
      vs.forEach((v) => {
        const len = v.getTotalLength();
        for (let i = 0; i <= 120; i++) {
          const p = v.getPointAtLength((len * i) / 120);
          pts.push({ x: p.x, y: p.y });
          if (p.y > maxY) maxY = p.y;
        }
      });
      const pool = pts.filter((p) => p.y >= maxY - 2);
      return (pool.length ? pool : [{ x: end.x, y: end.y }]).reduce((a, b) =>
        Math.abs(b.x - end.x) < Math.abs(a.x - end.x) ? b : a,
      );
    };

    /** Position of the handoff hairline: the lowest V point nearest the wolf's end. */
    const placeLine = (): number | null => {
      if (!mainWolf || !lineEl) return null;
      try {
        const ctm = svg.getScreenCTM();
        if (!ctm) return null;
        if (anchor === undefined) anchor = sampleAnchor();
        if (!anchor) return null;
        const rect = el.getBoundingClientRect();
        const sp = new DOMPoint(anchor.x, anchor.y).matrixTransform(ctm);
        const x = sp.x - rect.left;
        const y = sp.y - rect.top;
        lineEl.style.left = `${x}px`;
        lineEl.style.top = `${y}px`;
        lineEl.style.height = `${handoffLengthRef.current}px`;
        el.style.setProperty("--handoff-x", `${x}px`);
        return x;
      } catch {
        /* A measurement failure must never strand the mark – the reveal below runs anyway. */
        return null;
      }
    };

    const mm = gsap.matchMedia();
    mm.add({ reduce: MM.reduce, ok: MM.ok }, (ctx) => {
      if (ctx.conditions?.reduce) {
        gsap.set(el, { opacity: 1 });
        if (useLine) {
          const x = placeLine();
          gsap.set(lineEl, { scaleY: 1, opacity: 1 });
          if (x !== null) onHandoffRef.current?.(x);
        }
        onCompleteRef.current?.();
        return;
      }

      const mode = primeMark(svg, sequence);
      let hx: number | null = null;
      if (useLine) {
        hx = placeLine();
        gsap.set(lineEl, { scaleY: 0, transformOrigin: "50% 0%", opacity: 1 });
      }
      gsap.set(el, { opacity: 1 });

      const tl = gsap.timeline({
        paused: play !== "load",
        delay: play === "load" ? delay : 0,
        onComplete: () => {
          if (hx !== null) onHandoffRef.current?.(hx);
          if (sheen && play !== "scrub" && mainWolf) playSheen(mainWolf, DUR.sheen);
          onCompleteRef.current?.();
        },
      });
      const ease = "power2.inOut";
      if (mode === "wipe") {
        /* Interlocked filled artwork: one slice down the whole mark (see Draw.tsx). */
        tl.to(svg, { ...WIPE_IN, duration: T.wolf[0] + T.wolf[1], ease }, T.vLeft[0]);
      } else {
        if (vs[0]) tl.to(vs[0], { ...STROKE_IN, duration: T.vLeft[1], ease }, T.vLeft[0]);
        if (vs[1]) tl.to(vs[1], { ...STROKE_IN, duration: T.vRight[1], ease }, T.vRight[0]);
        vs.slice(2).forEach((v, i) => tl.to(v, { ...STROKE_IN, duration: 0.3, ease }, T.vRight[0] + 0.1 * (i + 1)));
        if (mainWolf) tl.to(mainWolf, { ...STROKE_IN, duration: T.wolf[1], ease }, T.wolf[0]);
        if (details.length) tl.to(details, { ...STROKE_IN, duration: T.detail[1], ease, stagger: 0.08 }, T.detail[0]);
        if (handoffPath) tl.to(handoffPath, { ...STROKE_IN, duration: T.handoff[1], ease: "power2.out" }, T.handoff[0]);
      }
      /* The hairline lives outside the svg, so it follows either reveal. */
      if (useLine) tl.to(lineEl, { scaleY: 1, duration: T.handoff[1] + 0.1, ease: "power2.out" }, T.handoff[0]);

      if (play === "view") {
        gsap.timeline({ scrollTrigger: { trigger: el, start: "top 80%", once: true, onEnter: () => tl.play() } });
      } else if (play === "scrub") {
        tl.eventCallback("onComplete", null);
        gsap.to(tl, {
          progress: 1,
          ease: "none",
          scrollTrigger: { trigger: el, start: "top 80%", end: "bottom 40%", scrub: 0.6 },
        });
      }

      /* Keep the hairline attached to the mark when the viewport changes – one rAF per
         frame, not one full re-projection per resize event. */
      let raf = 0;
      const onResize = () => {
        if (!useLine || raf) return;
        raf = requestAnimationFrame(() => {
          raf = 0;
          placeLine();
        });
      };
      window.addEventListener("resize", onResize);
      return () => {
        window.removeEventListener("resize", onResize);
        if (raf) cancelAnimationFrame(raf);
      };
    });
    return () => mm.revert();
  }, [play, sheen, handoff, delay]);

  return (
    <span ref={wrap} className={cn("relative inline-block [.js_&]:opacity-0", className)}>
      {children}
      {handoff && (
        <span
          ref={line}
          aria-hidden="true"
          className="pointer-events-none absolute w-px bg-gold opacity-0"
          style={{ height: handoffLength, left: 0, top: "100%" }}
        />
      )}
    </span>
  );
}

export default DrawMark;
