/* core.jsx – data, motion hooks, chrome (Logo, Nav, Footer, Cursor, GridBackdrop) */
const { useState, useEffect, useRef, useCallback } = React;

/* ----------------------------- DATA ----------------------------- */
// All localizable content (PROFILE / CASES / CERTS / LEGAL / SOCIALS / NAV /
// DISCIPLINES / UI / IMG) now lives in i18n.jsx, which is loaded BEFORE this
// file and selects the active language from window.__LANG.

// ⚠️ Paste your real Formspree form ID here (from your live alexmedved.com form).
// It looks like "xkabcdef" – replace REPLACE_ME and the contact form goes live.
const FORMSPREE = "https://formspree.io/f/xovwzoed";

/* ---------------------- CONTACT FORM (shared) ----------------------
   Validation params ported 1:1 from the live alexmedved.com form:
   - message hard-capped at 3500 chars with a live counter
   - light email-format guard (blocks random/garbage addresses) that
     surfaces the localized error and refuses to POST until it's valid
   - submit button stays disabled until every field has content
   The exact same hook backs the homepage form and the case-study modal,
   so the behaviour is identical on every page and in every language. */
const CONTACT_MAX = 3500;
const CONTACT_EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

function useContactForm() {
  const [status, setStatus] = useState("idle"); // idle | sending | ok | error
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [message, setMessage] = useState("");
  const [tried, setTried] = useState(false);
  const emailRef = useRef(null);

  const emailValid = CONTACT_EMAIL_RE.test(email.trim());
  // After the first submit attempt we keep the error in sync while the user fixes it.
  const showEmailErr = tried && !emailValid;
  const atMax = message.length >= CONTACT_MAX;
  // Button unlocks once ALL fields have something (email format not required yet).
  const canSend =
    name.trim().length > 0 &&
    email.trim().length > 0 &&
    message.length > 0 &&
    message.length <= CONTACT_MAX &&
    status !== "sending";

  const onName = (e) => setName(e.target.value);
  const onEmail = (e) => setEmail(e.target.value);
  const onMessage = (e) => setMessage(e.target.value.slice(0, CONTACT_MAX));

  const reset = () => {
    setStatus("idle"); setName(""); setEmail(""); setMessage(""); setTried(false);
  };

  const submit = async (e) => {
    e.preventDefault();
    setTried(true);
    if (!CONTACT_EMAIL_RE.test(email.trim())) {
      // Block the Formspree POST and point the user at the email field.
      emailRef.current && emailRef.current.focus();
      return;
    }
    const form = e.target;
    setStatus("sending");
    try {
      const res = await fetch(form.action, {
        method: "POST",
        body: new FormData(form),
        headers: { Accept: "application/json" },
      });
      if (res.ok) {
        setStatus("ok");
        setName(""); setEmail(""); setMessage(""); setTried(false);
      } else setStatus("error");
    } catch (_) { setStatus("error"); }
  };

  return {
    status, name, email, message, emailRef,
    showEmailErr, atMax, canSend, MAX: CONTACT_MAX,
    onName, onEmail, onMessage, submit, reset,
  };
}

/* ----------------------------- HOOKS ----------------------------- */
// reveal-on-scroll: re-scan whenever `dep` changes (direction switch remounts)
function useReveal(dep) {
  useEffect(() => {
    const els = Array.from(document.querySelectorAll(".reveal:not(.rin)"));
    const vh = window.innerHeight || 800;
    // above-the-fold elements reveal immediately (no waiting on observer)
    const pending = [];
    els.forEach((e) => {
      const top = e.getBoundingClientRect().top;
      if (top < vh * 0.92) e.classList.add("rin");
      else pending.push(e);
    });
    if (!("IntersectionObserver" in window)) { return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((en) => {
        if (en.isIntersecting) { en.target.classList.add("rin"); io.unobserve(en.target); }
      });
    }, { threshold: 0.14, rootMargin: "0px 0px -8% 0px" });
    pending.forEach((e) => io.observe(e));
    return () => io.disconnect();
  }, [dep]);
}

// anim-ready: only enable the hidden→reveal transition once a REAL animation
// frame fires. In a non-painting context (snapshot capture, throttled tab) rAF
// never runs, the class is never added, and content stays visible by default.
function useAnimReady(enabled) {
  useEffect(() => {
    const root = document.querySelector(".app");
    if (!root) return;
    if (!enabled) { root.classList.remove("anim-ready"); return; }
    let raf1 = 0, raf2 = 0;
    // two nested frames guarantees a paint cycle actually happened
    raf1 = requestAnimationFrame(() => {
      raf2 = requestAnimationFrame(() => { root.classList.add("anim-ready"); });
    });
    return () => { cancelAnimationFrame(raf1); cancelAnimationFrame(raf2); };
  }, [enabled]);
}

// parallax: translate [data-parallax] by speed on scroll + slight mouse drift
function useParallax(enabled, dep) {
  useEffect(() => {
    if (!enabled) return;
    let raf = 0, mx = 0, my = 0;
    const run = () => {
      raf = 0;
      const vh = window.innerHeight;
      document.querySelectorAll("[data-parallax]").forEach((el) => {
        const sp = parseFloat(el.getAttribute("data-parallax")) || 0;
        const r = el.getBoundingClientRect();
        const center = r.top + r.height / 2 - vh / 2;
        const y = center * sp * -0.12;
        const dx = mx * sp * 8, dy = my * sp * 8;
        el.style.transform = `translate3d(${dx}px, ${y + dy}px, 0)`;
      });
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(run); };
    const onMove = (e) => { mx = (e.clientX / window.innerWidth - 0.5); my = (e.clientY / window.innerHeight - 0.5); if (!raf) raf = requestAnimationFrame(run); };
    run();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("mousemove", onMove, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("mousemove", onMove); window.removeEventListener("resize", onScroll); if (raf) cancelAnimationFrame(raf); };
  }, [enabled, dep]);
}

// custom cursor – mode: "off" | "dot" | "ring". Uses accent color (no
// difference-blend), so it reads clearly on both light and dark palettes.
function useCursor(mode) {
  useEffect(() => {
    const dot = document.getElementById("cursorDot");
    const ring = document.getElementById("cursorRing");
    if (!dot || !ring) return;
    const coarse = window.matchMedia("(hover: none), (pointer: coarse)").matches;
    if (mode === "off" || coarse) {
      document.body.removeAttribute("data-cursor");
      document.body.classList.remove("has-cursor");
      dot.style.opacity = ring.style.opacity = "0";
      return;
    }
    document.body.setAttribute("data-cursor", mode);
    document.body.classList.add("has-cursor");
    dot.style.opacity = "1";
    ring.style.opacity = mode === "ring" ? "1" : "0";
    let rx = innerWidth / 2, ry = innerHeight / 2, dx = rx, dy = ry, raf = 0;
    const loop = () => {
      rx += (dx - rx) * 0.2; ry += (dy - ry) * 0.2;
      ring.style.transform = `translate3d(${rx}px,${ry}px,0) translate(-50%,-50%)`;
      raf = requestAnimationFrame(loop);
    };
    const move = (e) => {
      dx = e.clientX; dy = e.clientY;
      dot.style.transform = `translate3d(${dx}px,${dy}px,0) translate(-50%,-50%)`;
      const hot = e.target.closest("a,button,[data-hot],input,textarea,.case,.workrow,.cert");
      ring.classList.toggle("is-hot", !!hot);
      dot.classList.toggle("is-hot", !!hot);
    };
    const down = () => ring.classList.add("is-down");
    const up = () => ring.classList.remove("is-down");
    window.addEventListener("mousemove", move, { passive: true });
    window.addEventListener("mousedown", down); window.addEventListener("mouseup", up);
    if (mode === "ring") loop();
    return () => {
      window.removeEventListener("mousemove", move); window.removeEventListener("mousedown", down); window.removeEventListener("mouseup", up);
      cancelAnimationFrame(raf); document.body.classList.remove("has-cursor"); document.body.removeAttribute("data-cursor");
      dot.style.opacity = ring.style.opacity = "0"; dot.classList.remove("is-hot");
    };
  }, [mode]);
}

// sticky-nav shadow on scroll
function useStuck() {
  const [stuck, setStuck] = useState(false);
  useEffect(() => {
    const onScroll = () => setStuck(window.scrollY > 12);
    onScroll(); window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return stuck;
}

/* ----------------------------- SMALL UI ----------------------------- */
function MonoLabel({ children, className = "" }) {
  return <span className={"mono eyebrow " + className}>{children}</span>;
}

function Reveal({ as = "div", d, className = "", children, ...rest }) {
  const Tag = as;
  return <Tag className={"reveal " + className} data-d={d} {...rest}>{children}</Tag>;
}

function GridBackdrop() { return <div className="grid-backdrop" aria-hidden="true"></div>; }

function maskStyle(uri) {
  const v = "url(\"" + (uri || "") + "\")";
  return { WebkitMaskImage: v, maskImage: v };
}

function Logo({ onDark }) {
  const I = (typeof window !== "undefined" && window.ICONS) || {};
  return (
    <a href="#top" className="row" data-hot style={{ gap: ".62rem" }} aria-label="Aleksandr Medved – home">
      <span className="sm-mark" aria-hidden="true">
        <span className="sm-mark-layer sm-mark-ink" style={maskStyle(I.logoInk)}></span>
        <span className="sm-mark-layer sm-mark-accent" style={maskStyle(I.logoAccent)}></span>
      </span>
      <span style={{ display: "flex", flexDirection: "column", lineHeight: 1 }}>
        <span style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 15, letterSpacing: "-.02em" }}>Aleksandr Medved</span>
        <span className="mono-sm" style={{ color: "var(--ink-faint)", marginTop: 3 }}>Product Designer <span style={{ color: "var(--accent)" }}>//</span> Smellfigty</span>
      </span>
    </a>
  );
}

/* social link with brand glyph (mask-tinted, accent on hover) */
function SocialLink({ s, showLabel = true, className = "" }) {
  const I = (typeof window !== "undefined" && window.ICONS) || {};
  return (
    <a className={"social " + className} href={s.url} target="_blank" rel="noopener" data-hot>
      <span className="soc-ico" style={maskStyle(I[s.iconKey])} aria-hidden="true"></span>
      {showLabel && <span>{s.label}</span>}
    </a>
  );
}

/* ----------------------------- NAV ----------------------------- */
function ThemeToggle({ theme, onToggle }) {
  const dark = theme === "dark";
  return (
    <button className="theme-toggle" data-hot onClick={onToggle} aria-label={dark ? "Switch to light theme" : "Switch to dark theme"} title={dark ? "Light theme" : "Dark theme"}>
      {dark ? (
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <circle cx="12" cy="12" r="4"></circle>
          <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path>
        </svg>
      ) : (
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
        </svg>
      )}
    </button>
  );
}

/* language switcher – links come from UI.langLinks (built per-language in i18n) */
function LangSwitch({ style }) {
  return (
    <span className="lang" aria-label="Language" style={style}>
      {UI.langLinks.map((l) => (
        <a key={l.label} className={l.active ? "active" : ""} href={l.href} data-hot
          {...(/^https?:/.test(l.href) ? { target: "_blank", rel: "noopener" } : {})}>{l.label}</a>
      ))}
    </span>
  );
}

function Nav({ theme, onToggleTheme }) {
  const stuck = useStuck();
  const [open, setOpen] = useState(false);
  return (
    <header className={"nav" + (stuck ? " stuck" : "")}>
      <div className="wrap-wide nav-inner">
        <Logo />
        <nav className="nav-links desktop">
          {NAV.map((n) => (
            <a key={n.href} href={n.href} className="nav-link" data-hot>{n.label}</a>
          ))}
          <LangSwitch />
          <ThemeToggle theme={theme} onToggle={onToggleTheme} />
          <a href="#contact" className="btn" data-hot style={{ padding: ".7em 1.1em" }}>{UI.navGetInTouch}</a>
        </nav>
        <span className="nav-toggle">
          <ThemeToggle theme={theme} onToggle={onToggleTheme} />
          <button className="btn" data-hot onClick={() => setOpen((v) => !v)} style={{ padding: ".55em .9em" }} aria-label="Menu">
            {open ? UI.navClose : UI.navMenu}
          </button>
        </span>
      </div>
      {open && (
        <div className="wrap-wide" style={{ paddingBottom: "1.2rem", display: "flex", flexDirection: "column", gap: ".4rem" }}>
          {NAV.map((n) => (
            <a key={n.href} href={n.href} className="nav-link" data-hot onClick={() => setOpen(false)} style={{ fontSize: "1.1rem", padding: ".4rem 0" }}>{n.label}</a>
          ))}
          <LangSwitch style={{ marginTop: ".4rem" }} />
        </div>
      )}
    </header>
  );
}

/* ----------------------------- FOOTER ----------------------------- */
function Footer() {
  return (
    <footer className="footer" id="imprint">
      <div className="wrap-wide" style={{ display: "flex", justifyContent: "space-between", gap: "2rem", flexWrap: "wrap", alignItems: "flex-end" }}>
        <div className="stack" style={{ gap: ".8rem" }}>
          <Logo />
          <p className="mono-sm" style={{ color: "var(--ink-faint)", maxWidth: 360, lineHeight: 1.6 }}>
            {UI.footerTagline}
          </p>
          <a className="footer-mail" href="mailto:alex@alexmedved.com" data-hot>
            alex<span className="footer-mail-at">@</span>alexmedved.com
          </a>
        </div>
        <div className="stack" style={{ gap: ".5rem", alignItems: "flex-start" }}>
          <a className="mono-sm" href="#impressum" data-hot style={{ color: "var(--ink-soft)" }}>{UI.footerImprint}</a>
          <a className="mono-sm" href="#privacy" data-hot style={{ color: "var(--ink-soft)" }}>{UI.footerPrivacy}</a>
          <span className="mono-sm" style={{ color: "var(--ink-faint)", marginTop: ".6rem" }}>{UI.footerCopyright}</span>
        </div>
      </div>
    </footer>
  );
}

/* export */
Object.assign(window, {
  useReveal, useParallax, useCursor, useStuck, useAnimReady, useContactForm,
  MonoLabel, Reveal, GridBackdrop, Logo, Nav, Footer, SocialLink, ThemeToggle, LangSwitch,
});
