"use client";

import { usePathname } from "next/navigation";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { flags, legal as legalDe, site } from "@/lib/site";
import type { Lang } from "@/lib/i18n";
import { LangSwitch } from "./LangSwitch";
import { chrome as chromeDe, type Chrome } from "@/content/chrome";
import { gsap, EASE, MM, getLenis, refreshSoon } from "@/lib/gsap";
import { TransitionLink } from "@/components/motion/TransitionLink";
import { Container } from "./Container";

export interface OverlayMenuProps {
  open: boolean;
  onClose: () => void;
  /** Texte der Hülle. Ohne Angabe die deutsche Fassung. */
  chrome?: Chrome;
  /** Impressum/Datenschutz der jeweiligen Sprache. */
  legal?: readonly { href: string; label: string }[];
  /** Aktuelle Sprache – für den Umschalter im Fuß des Menüs. */
  lang?: Lang;
}

/** The header's „Menü“ button – part of the dialog's operable set (see the trap below). */
export const MENU_TOGGLE_ID = "menue-toggle";

/**
 * Fullscreen ivory sheet from the top (0.5 s power3.inOut): numbered ledger
 * 01–05, contact row at the bottom. `data-lenis-prevent`, `lenis.stop()`,
 * focus trap, Escape closes, closes on route change, rest of the page `inert`.
 *
 * The trap spans the panel AND the header's toggle: the toggle stays visible on
 * top of the sheet (header z-50 > panel z-40), so it has to stay reachable by Tab
 * as well as by mouse. Every other header control is `inert` while the sheet is
 * open, so the mouse-operable and the keyboard-operable set are the same one.
 */
export function OverlayMenu({ open, onClose, chrome = chromeDe, legal = legalDe, lang = "de" }: OverlayMenuProps) {
  const panel = useRef<HTMLDivElement>(null);
  const pathname = usePathname();
  const onCloseRef = useRef(onClose);
  /* The panel has to outlive `open` for the length of its exit tween. State adjusted
     during render (the sanctioned pattern), not in an effect – an effect would cost a
     cascading render on every open. */
  const [exiting, setExiting] = useState(false);
  const [prevOpen, setPrevOpen] = useState(open);
  if (prevOpen !== open) {
    setPrevOpen(open);
    setExiting(!open);
  }
  const mounted = open || exiting;

  useEffect(() => {
    onCloseRef.current = onClose;
  });

  // Close on route change.
  const lastPath = useRef(pathname);
  useEffect(() => {
    if (lastPath.current !== pathname) {
      lastPath.current = pathname;
      onCloseRef.current();
    }
  }, [pathname]);

  /* Enter / exit on GSAP (the site's only animation runtime). The panel is a single
     element, so an <AnimatePresence> is not load-bearing – `exiting` keeps it in the
     DOM until the exit tween has run. */
  useLayoutEffect(() => {
    const el = panel.current;
    if (!mounted || !el) return;
    const rows = gsap.utils.toArray<HTMLElement>("[data-menu-row]", el);
    const foot = el.querySelector<HTMLElement>("[data-menu-foot]");
    const mm = gsap.matchMedia();

    mm.add({ reduce: MM.reduce, ok: MM.ok }, (ctx) => {
      const reduce = Boolean(ctx.conditions?.reduce);
      if (open) {
        gsap.set(el, { yPercent: reduce ? 0 : -100 });
        gsap.set(rows, { yPercent: reduce ? 0 : 100 });
        gsap.set(foot, { opacity: reduce ? 1 : 0 });
        if (reduce) return;
        const tl = gsap.timeline();
        tl.to(el, { yPercent: 0, duration: 0.5, ease: EASE.inOut }, 0)
          .to(rows, { yPercent: 0, duration: 0.6, ease: EASE.out, stagger: 0.06 }, 0.25)
          .to(foot, { opacity: 1, duration: 0.6 }, 0.6);
        return;
      }
      /* Reduced motion still goes through the tween, at zero duration: `onComplete`
         must fire off the current render, never synchronously inside this effect. */
      gsap.to(el, {
        yPercent: -100,
        duration: reduce ? 0 : 0.4,
        ease: EASE.inOut,
        onComplete: () => setExiting(false),
      });
    });

    return () => mm.revert();
  }, [open, mounted]);

  useEffect(() => {
    if (!open) return;
    const lenis = getLenis();
    lenis?.stop();
    const previous = document.activeElement as HTMLElement | null;
    const toggle = document.getElementById(MENU_TOGGLE_ID) as HTMLElement | null;
    /* Everything the reader can still SEE but must no longer reach: the rest of the
       page plus the header's own links, which float above the ivory sheet. */
    const others = Array.from(
      document.querySelectorAll<HTMLElement>("main, footer, header [data-menu-inert]"),
    );
    others.forEach((el) => {
      el.inert = true;
    });

    const inPanel = () =>
      Array.from(panel.current?.querySelectorAll<HTMLElement>("a[href], button:not([disabled])") ?? []);
    /* The toggle first: it sits above the sheet, so Shift+Tab from the first ledger row
       lands on it and Tab from the last row cycles back to it. */
    const focusables = () => (toggle ? [toggle, ...inPanel()] : inPanel());
    const t = window.setTimeout(() => inPanel()[0]?.focus(), 50);

    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") {
        e.preventDefault();
        onCloseRef.current();
        return;
      }
      if (e.key !== "Tab") return;
      const items = focusables();
      if (!items.length) return;
      const first = items[0];
      const last = items[items.length - 1];
      const active = document.activeElement as HTMLElement | null;
      const inside = active ? items.includes(active) : false;
      if (e.shiftKey && (active === first || !inside)) {
        e.preventDefault();
        last.focus();
      } else if (!e.shiftKey && (active === last || !inside)) {
        e.preventDefault();
        first.focus();
      }
    };
    document.addEventListener("keydown", onKey);

    return () => {
      window.clearTimeout(t);
      document.removeEventListener("keydown", onKey);
      others.forEach((el) => {
        el.inert = false;
      });
      lenis?.start();
      previous?.focus?.();
      refreshSoon();
    };
  }, [open]);

  if (!mounted) return null;

  return (
    <div
      ref={panel}
      id="menue"
      role="dialog"
      aria-modal="true"
      aria-label={chrome.header.menu.ariaLabel}
      data-theme="light"
      data-lenis-prevent=""
      className="fixed inset-0 z-40 flex flex-col overflow-y-auto overscroll-contain bg-ivory text-ink"
    >
      <Container className="flex min-h-full flex-1 flex-col pb-10 pt-[calc(var(--header-h,60px)+24px)]">
        <ol className="border-t border-line-light">
          {chrome.menu.items.map((item) => (
            <li key={item.href} className="overflow-hidden border-b border-line-light">
              <div data-menu-row="">
                <TransitionLink
                  href={item.href}
                  label={item.label}
                  onClick={() => onCloseRef.current()}
                  className="group flex items-baseline gap-5 py-5 lg:gap-8 lg:py-7"
                >
                  <span className="font-mono text-xs tracking-[0.06em] tabular-nums text-gold-deep">{item.numeral}</span>
                  <span className="font-display text-[clamp(2rem,1.4rem+3vw,4rem)] leading-none text-ink transition-[font-style] group-hover:italic">
                    {item.label}
                  </span>
                  <span
                    aria-hidden="true"
                    className="ml-auto text-gold-deep transition-transform duration-300 group-hover:translate-x-2"
                  >
                    →
                  </span>
                </TransitionLink>
              </div>
            </li>
          ))}
        </ol>

        <div
          data-menu-foot=""
          className="mt-auto flex flex-col gap-4 pt-12 lg:flex-row lg:items-end lg:justify-between"
        >
          <div className="flex flex-col gap-2">
            <a href={site.phoneHref} className="font-display text-[1.75rem] leading-none text-ink">
              {site.phoneDisplay}
            </a>
            <a href={`mailto:${site.email}`} className="font-sans text-sm text-grey-deep">
              {site.email}
            </a>
          </div>
          <div className="flex flex-wrap items-center gap-x-6 gap-y-2 font-mono text-xs tracking-[0.06em] text-grey-deep">
            {/* Unter 1024 px versteckt der Kopf den Umschalter (dort ist kein Platz neben
                der Wortmarke). Ohne diese Zeile gäbe es auf dem Telefon gar keinen Weg in
                die andere Sprache – er steht deshalb genau dort, wo er im Kopf fehlt. */}
            {flags.showLangSwitch && <LangSwitch lang={lang} className="lg:hidden" />}
            <a href={site.linkedin} target="_blank" rel="noopener noreferrer" className="hover:text-gold-deep">
              {chrome.menu.linkedin} <span aria-hidden="true">↗</span>
            </a>
            <span>
              {legal.map((l, i) => (
                <span key={l.href}>
                  {i > 0 && <span aria-hidden="true"> · </span>}
                  <TransitionLink href={l.href} label={l.label} onClick={() => onCloseRef.current()} className="hover:text-gold-deep">
                    {l.label}
                  </TransitionLink>
                </span>
              ))}
            </span>
          </div>
        </div>
      </Container>
    </div>
  );
}

export default OverlayMenu;
