// Shared UI primitives + tweak defaults. Requires React + tweaks-panel.jsx.

const { useState, useEffect, useRef, useMemo } = React;

window.SHARED_TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "cyan",
  "motion": "subtle",
  "grain": true
}/*EDITMODE-END*/;

window.useTime = function useTime() {
  const [now, setNow] = useState(() => new Date());
  useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id);
  }, []);
  return now;
};

window.useReveal = function useReveal() {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  const [armed, setArmed] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const el = ref.current;
    if (typeof IntersectionObserver === "undefined") { setShown(true); return; }
    setArmed(true);
    let done = false;
    const reveal = () => { if (!done) { done = true; setShown(true); } };
    const io = new IntersectionObserver(
      ([entry]) => { if (entry.isIntersecting) { reveal(); io.disconnect(); } },
      { threshold: 0.05, rootMargin: "0px 0px -10px 0px" }
    );
    io.observe(el);
    requestAnimationFrame(() => {
      const r = el.getBoundingClientRect();
      if (r.top < window.innerHeight && r.bottom > 0) reveal();
    });
    const t = setTimeout(reveal, 1200);
    return () => { io.disconnect(); clearTimeout(t); };
  }, []);
  return [ref, shown, armed];
};

window.Reveal = function Reveal({ children, delay = 0, className = "", as: Tag = "div" }) {
  const [ref, shown, armed] = window.useReveal();
  const cls = ["reveal", armed ? "is-armed" : "", shown ? "is-shown" : "", className].filter(Boolean).join(" ");
  return <Tag ref={ref} className={cls} style={{ transitionDelay: `${delay}ms` }}>{children}</Tag>;
};

window.Caret = function Caret() {
  return <span className="caret" aria-hidden="true" />;
};

// Viewer modal — embeds PDF or demo in an iframe, with a download fallback.
// kind: "pdf" | "demo"
// src: path to file (e.g. "papers/foo.pdf" or "demos/foo/index.html")
// download: optional separate downloadable file (used when src can't be embedded — Word, Excel, Python, zip)
// title: heading text
window.Viewer = function Viewer({ open, onClose, kind, src, download, title }) {
  useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    document.body.style.overflow = "hidden";
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = "";
    };
  }, [open, onClose]);

  if (!open) return null;

  const hasEmbed = !!src;
  const isPdf = kind === "pdf" || (src && /\.pdf($|\?)/i.test(src));

  return (
    <div className="viewer" role="dialog" aria-modal="true" onClick={onClose}>
      <div className="viewer__panel" onClick={(e) => e.stopPropagation()}>
        <header className="viewer__bar">
          <div className="viewer__title">
            <span className="viewer__kind">{kind === "pdf" ? "pdf" : "demo"}</span>
            <span className="viewer__sep">/</span>
            <span>{title}</span>
          </div>
          <div className="viewer__barRight">
            {(src || download) && (
              <a className="viewer__dl" href={download || src} download target="_blank" rel="noreferrer">
                download ↓
              </a>
            )}
            <button className="viewer__close" onClick={onClose} aria-label="close">✕</button>
          </div>
        </header>
        <div className="viewer__body">
          {hasEmbed ? (
            <iframe
              className="viewer__frame"
              src={src}
              title={title}
              {...(isPdf ? { type: "application/pdf" } : { sandbox: "allow-scripts allow-same-origin allow-forms allow-popups" })}
            />
          ) : (
            <div className="viewer__empty">
              <div className="viewer__emptyMark">∅</div>
              <div className="viewer__emptyText">
                {kind === "pdf" ? "PDF coming soon." : "Demo coming soon."}
              </div>
              {download && (
                <a className="viewer__dl viewer__dl--big" href={download} download>
                  download file ↓
                </a>
              )}
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

window.SiteHeader = function SiteHeader({ here }) {
  const now = window.useTime();
  const stamp = useMemo(() => now.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false }), [now]);
  // Test pages can override link targets by setting window.NAV_TARGETS
  const nav = window.NAV_TARGETS || { home: "index.html", papers: "papers.html", projects: "projects.html" };
  return (
    <header className="topbar">
      <a className="topbar__left" href={nav.home}>
        <span className="mark">SM</span>
        <span className="topbar__sep">/</span>
        <span className="topbar__name">sam mcleod</span>
      </a>
      <nav className="topbar__nav">
        <a href={nav.home} className={here === "home" ? "is-here" : ""}>home</a>
        <a href={nav.papers} className={here === "papers" ? "is-here" : ""}>papers</a>
        <a href={nav.projects} className={here === "projects" ? "is-here" : ""}>projects</a>
      </nav>
      <div className="topbar__right">
        <span className="dot" />
        <span>online · {stamp} UTC</span>
      </div>
    </header>
  );
};

window.SiteFooter = function SiteFooter() {
  const now = window.useTime();
  return (
    <footer className="foot">
      <div>© {now.getFullYear()} sam mcleod · built by hand</div>
      <div className="foot__right">
        <span>last updated 2026.04.27</span>
        <span className="foot__pulse" />
      </div>
    </footer>
  );
};

window.SharedTweaks = function SharedTweaks() {
  const [tweaks, setTweak] = useTweaks(window.SHARED_TWEAK_DEFAULTS);
  useEffect(() => {
    const accent = window.ACCENTS[tweaks.accent] || window.ACCENTS.cyan;
    const root = document.documentElement;
    root.style.setProperty("--accent-h", accent.hue);
    root.style.setProperty("--accent-c", accent.chroma);
    root.dataset.motion = tweaks.motion;
    root.dataset.grain = tweaks.grain ? "on" : "off";
  }, [tweaks]);

  return (
    <>
      {tweaks.grain && <div className="grain" aria-hidden="true" />}
      <TweaksPanel title="Tweaks">
        <TweakSection title="Accent">
          <TweakRadio
            value={tweaks.accent}
            onChange={(v) => setTweak("accent", v)}
            options={[
              { value: "cyan", label: "cyan" },
              { value: "lime", label: "lime" },
              { value: "amber", label: "amber" },
              { value: "rose", label: "rose" },
              { value: "violet", label: "violet" },
              { value: "bone", label: "bone" },
            ]}
          />
        </TweakSection>
        <TweakSection title="Motion">
          <TweakRadio
            value={tweaks.motion}
            onChange={(v) => setTweak("motion", v)}
            options={[
              { value: "off", label: "off" },
              { value: "subtle", label: "subtle" },
              { value: "playful", label: "playful" },
            ]}
          />
        </TweakSection>
        <TweakSection title="Background">
          <TweakToggle label="film grain" value={tweaks.grain} onChange={(v) => setTweak("grain", v)} />
        </TweakSection>
      </TweaksPanel>
    </>
  );
};
