// ============================================================
// THEME — Shard tokens, prefs state, atoms
// Two themes (Midnight + Bone), four prefs (theme/textSize/
// lineSpacing/dyslexia). Pattern lifted from BRAINS.
// ============================================================

const THEMES = {
  midnight: {
    name: "Midnight",
    page: "#060912",
    surface: "#0a1020",
    surfaceAlt: "#0e1628",
    card: "rgba(255,255,255,0.025)",
    cardBorder: "rgba(255,255,255,0.08)",
    cardHoverBorder: "rgba(245,193,78,0.35)",
    ink: "#f4f1e8",
    sub: "rgba(244, 241, 232, 0.72)",
    mute: "rgba(244, 241, 232, 0.5)",
    accent: "#f5c14e",
    accentDeep: "#e2a82a",
    accentSoft: "rgba(245,193,78,0.12)",
    chip: "rgba(255,255,255,0.06)",
    chipBorder: "rgba(255,255,255,0.1)",
    chipText: "rgba(244,241,232,0.88)",
    success: "#5fd28a",
    warn: "#f59e0b",
    fail: "#ef4444",
    progress: "#7aa3ff",
    dark: true,
    heroBg: "radial-gradient(ellipse at 30% 20%, #1a2244 0%, #0a1020 55%, #060912 100%)",
    onAccent: "#0a1020",
    facetFillTop: "#1a2244",
    facetFillBottom: "#0a1020",
  },
  light: {
    name: "Bone",
    page: "#f6f1e2",
    surface: "#faf6ea",
    surfaceAlt: "#f1ead4",
    card: "rgba(22,26,43,0.025)",
    cardBorder: "rgba(22,26,43,0.10)",
    cardHoverBorder: "rgba(226,168,42,0.45)",
    ink: "#161a2b",
    sub: "rgba(22, 26, 43, 0.72)",
    mute: "rgba(22, 26, 43, 0.5)",
    accent: "#e2a82a",
    accentDeep: "#b88313",
    accentSoft: "rgba(226,168,42,0.10)",
    chip: "rgba(22,26,43,0.06)",
    chipBorder: "rgba(22,26,43,0.12)",
    chipText: "rgba(22,26,43,0.82)",
    success: "#1f8a5b",
    warn: "#b07a0c",
    fail: "#c12a1e",
    progress: "#2a5fc7",
    dark: false,
    heroBg: "radial-gradient(ellipse at 30% 20%, #fbf5e6 0%, #f3ead0 55%, #ead9a8 100%)",
    onAccent: "#161a2b",
    facetFillTop: "#fbf5e6",
    facetFillBottom: "#e6dab4",
  },
};

const TEXT_SCALES = {
  S: { base: 15, line: 1.5, letter: 0, zoom: 0.9 },
  M: { base: 17, line: 1.6, letter: 0, zoom: 1.0 },
  L: { base: 19, line: 1.7, letter: 0.1, zoom: 1.12 },
};

window.THEMES = THEMES;
window.TEXT_SCALES = TEXT_SCALES;

// ============================================================
// PREFS — localStorage-backed, broadcast via a custom event so
// every component's usePrefs() hook updates in sync.
// ============================================================

const PREFS_KEY = "shard.prefs";
const DEFAULT_PREFS = {
  theme: "midnight",
  textSize: "M",
  lineSpacing: "Standard",
  dyslexia: false,
};

function readPrefs() {
  try {
    const saved = JSON.parse(localStorage.getItem(PREFS_KEY) || "null");
    if (saved) return { ...DEFAULT_PREFS, ...saved };
  } catch (e) {}
  return DEFAULT_PREFS;
}

function writePrefs(next) {
  try {
    localStorage.setItem(PREFS_KEY, JSON.stringify(next));
  } catch (e) {}
  window.dispatchEvent(new CustomEvent("shard:prefs-change", { detail: next }));
}

function usePrefs() {
  const [prefs, setLocal] = React.useState(readPrefs);
  React.useEffect(() => {
    const onChange = (e) => setLocal(e.detail);
    window.addEventListener("shard:prefs-change", onChange);
    return () => window.removeEventListener("shard:prefs-change", onChange);
  }, []);
  return [prefs, writePrefs];
}

// Returns the active theme object. Dyslexia mode forces the light (Bone)
// theme regardless of the saved choice, because dark backgrounds + cream
// ink fight cream-bg overrides and create unreadable patches.
function useActiveTheme() {
  const [prefs] = usePrefs();
  const key = prefs.dyslexia ? "light" : prefs.theme;
  return THEMES[key] || THEMES.midnight;
}

window.usePrefs = usePrefs;
window.useActiveTheme = useActiveTheme;
window.readPrefs = readPrefs;
window.writePrefs = writePrefs;

// Backwards-compatible singleton for any code path that hasn't been
// migrated yet. Always returns the Midnight tokens.
window.THEME = THEMES.midnight;

// ============================================================
// VIEWPORT HOOK — <768px is mobile.
// ============================================================

function useViewport() {
  const [w, setW] = React.useState(
    typeof window !== "undefined" ? window.innerWidth : 1280
  );
  React.useEffect(() => {
    let raf = 0;
    const onResize = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => setW(window.innerWidth));
    };
    window.addEventListener("resize", onResize);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", onResize);
    };
  }, []);
  return { width: w, isMobile: w < 768, isTablet: w < 1024 };
}

window.useViewport = useViewport;

// ============================================================
// SHARED ATOMS — Section, Eyebrow, KintsugiRule, StatusChip, AccentPhrase
// ============================================================

function Section({ id, children, padding = "120px 0", mobilePadding, style, tone }) {
  const theme = useActiveTheme();
  const { isMobile } = useViewport();
  const resolved = isMobile ? (mobilePadding || "64px 0") : padding;
  // Alt bands are slightly translucent so the fixed ShardField backdrop
  // reads through as depth instead of being hard-clipped per section.
  const bg =
    tone === "alt"
      ? theme.dark
        ? "rgba(10,16,32,0.78)"
        : "rgba(250,246,234,0.85)"
      : tone === "raised"
      ? theme.surfaceAlt
      : "transparent";
  return (
    <section
      id={id}
      style={{
        position: "relative",
        padding: resolved,
        background: bg,
        ...style,
      }}
    >
      <div
        style={{
          maxWidth: 1240,
          margin: "0 auto",
          padding: isMobile ? "0 20px" : "0 48px",
          position: "relative",
        }}
      >
        {children}
      </div>
    </section>
  );
}

function Eyebrow({ children, accent }) {
  const theme = useActiveTheme();
  return (
    <div
      style={{
        display: "inline-flex",
        alignItems: "center",
        gap: 10,
        fontSize: 12,
        letterSpacing: 1.6,
        textTransform: "uppercase",
        color: accent ? theme.accent : theme.sub,
        fontWeight: 600,
        marginBottom: 24,
        fontFamily: "'DM Sans', sans-serif",
      }}
    >
      <span
        style={{
          display: "inline-block",
          width: 22,
          height: 1.5,
          background: theme.accent,
        }}
      />
      {children}
    </div>
  );
}

function AccentPhrase({ children, italic = true }) {
  const theme = useActiveTheme();
  return (
    <span
      style={{
        color: theme.accent,
        fontStyle: italic ? "italic" : "normal",
        fontWeight: 600,
      }}
    >
      {children}
    </span>
  );
}

function KintsugiRule({ width = 96, opacity = 0.9 }) {
  const theme = useActiveTheme();
  return (
    <svg
      width={width}
      height="6"
      viewBox={`0 0 ${width} 6`}
      style={{ display: "block", opacity }}
      aria-hidden
    >
      <path
        d={`M0 3 L${width * 0.4} 3 L${width * 0.46} 1 L${width * 0.54} 5 L${width * 0.6} 3 L${width} 3`}
        stroke={theme.accent}
        strokeWidth="1.5"
        fill="none"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}

function StatusChip({ children, color }) {
  const theme = useActiveTheme();
  return (
    <span
      style={{
        display: "inline-flex",
        alignItems: "center",
        gap: 8,
        padding: "5px 11px",
        borderRadius: 999,
        background: theme.chip,
        border: `1px solid ${theme.chipBorder}`,
        color: theme.chipText,
        fontSize: 11.5,
        letterSpacing: 0.4,
        fontFamily: "'JetBrains Mono', 'SF Mono', ui-monospace, monospace",
      }}
    >
      <span
        style={{
          width: 6,
          height: 6,
          borderRadius: "50%",
          background: color || theme.accent,
          boxShadow: `0 0 8px ${color || theme.accent}`,
        }}
      />
      {children}
    </span>
  );
}

window.Section = Section;
window.Eyebrow = Eyebrow;
window.AccentPhrase = AccentPhrase;
window.KintsugiRule = KintsugiRule;
window.StatusChip = StatusChip;

// ============================================================
// MOTION PRIMITIVES — Reveal, SpotlightCard, Marquee, TypeLoop
// Choreography rules: transform/opacity only, IO-driven,
// everything collapses to static under reduced-motion/dyslexia.
// ============================================================

// One-time global CSS for the motion system.
(function injectMotionCSS() {
  if (document.getElementById("shard-motion-css")) return;
  const style = document.createElement("style");
  style.id = "shard-motion-css";
  style.textContent = `
    .sv-reveal {
      opacity: 0;
      transform: translateY(26px);
      transition:
        opacity .9s cubic-bezier(.16,1,.3,1),
        transform .9s cubic-bezier(.16,1,.3,1);
      transition-delay: var(--sv-delay, 0ms);
      will-change: transform, opacity;
    }
    .sv-reveal.sv-in { opacity: 1; transform: none; }

    @keyframes sv-marquee {
      from { transform: translateX(0); }
      to { transform: translateX(-50%); }
    }
    @keyframes sv-caret {
      0%, 49% { opacity: 1; }
      50%, 100% { opacity: 0; }
    }
    @keyframes sv-breathe {
      0%, 100% { transform: scale(1); opacity: .9; }
      50% { transform: scale(1.35); opacity: .5; }
    }
    .sv-press { transition: transform .15s cubic-bezier(.16,1,.3,1); }
    .sv-press:active { transform: scale(.98); }

    @media (prefers-reduced-motion: reduce) {
      .sv-reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
    }
    body.shard-dyslexia .sv-reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
    body.shard-dyslexia .sv-marquee-track { transform: none !important; }
  `;
  document.head.appendChild(style);
})();

// Heavy motion (scroll-reveal, the full-page 3D field) respects the OS
// reduced-motion preference as well as the site's dyslexia toggle.
function motionAllowed() {
  if (typeof window === "undefined") return false;
  if (document.body.classList.contains("shard-dyslexia")) return false;
  if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return false;
  return true;
}
window.motionAllowed = motionAllowed;

// Light, non-vestibular decorative motion (marquee, typewriter) — consistent
// with the hero shard, which also keeps turning under OS reduced-motion. Only
// the explicit on-site dyslexia toggle halts these.
function decorMotionOk() {
  if (typeof window === "undefined") return false;
  if (document.body.classList.contains("shard-dyslexia")) return false;
  return true;
}
window.decorMotionOk = decorMotionOk;

// Scroll-reveal wrapper. Children rise + fade in once when scrolled into view.
function Reveal({ children, delay = 0, style, as = "div" }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (!motionAllowed() || !("IntersectionObserver" in window)) {
      el.classList.add("sv-in");
      return;
    }
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            e.target.classList.add("sv-in");
            io.unobserve(e.target);
          }
        });
      },
      { threshold: 0.12, rootMargin: "0px 0px -8% 0px" }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  const Tag = as;
  return (
    <Tag ref={ref} className="sv-reveal" style={{ "--sv-delay": `${delay}ms`, ...style }}>
      {children}
    </Tag>
  );
}

// Card whose border lights up under the cursor (radial spotlight).
// Pointer position is written to CSS vars via ref — no re-renders.
function SpotlightCard({ children, style, radius = 18, padding }) {
  const theme = useActiveTheme();
  const ref = React.useRef(null);
  const onMove = (e) => {
    const el = ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    el.style.setProperty("--sx", `${e.clientX - r.left}px`);
    el.style.setProperty("--sy", `${e.clientY - r.top}px`);
    el.style.setProperty("--so", "1");
  };
  const onLeave = () => {
    const el = ref.current;
    if (el) el.style.setProperty("--so", "0");
  };
  return (
    <div
      ref={ref}
      onPointerMove={onMove}
      onPointerLeave={onLeave}
      style={{
        position: "relative",
        borderRadius: radius,
        background: theme.card,
        border: `1px solid ${theme.cardBorder}`,
        boxShadow: "inset 0 1px 0 rgba(255,255,255,0.06)",
        overflow: "hidden",
        padding,
        ...style,
      }}
    >
      <div
        aria-hidden
        style={{
          position: "absolute",
          inset: 0,
          opacity: "var(--so, 0)",
          transition: "opacity .4s ease",
          background: `radial-gradient(280px circle at var(--sx, 50%) var(--sy, 50%), ${theme.accent}14, transparent 65%)`,
          pointerEvents: "none",
        }}
      />
      <div style={{ position: "relative", height: "100%", display: "flex", flexDirection: "column" }}>{children}</div>
    </div>
  );
}

// Kinetic marquee — rAF-driven so it works regardless of stylesheet
// loading order. Speed = seconds for one full content loop.
function Marquee({ items, speed = 36, style }) {
  const theme = useActiveTheme();
  const trackRef = React.useRef(null);
  const copyRef = React.useRef(null);

  React.useEffect(() => {
    if (!decorMotionOk()) return;
    const track = trackRef.current;
    const copy = copyRef.current;
    if (!track || !copy) return;
    let raf = 0;
    let x = 0;
    let last = performance.now();
    const tick = (now) => {
      const dt = (now - last) / 1000;
      last = now;
      const w = copy.offsetWidth || 1;
      x -= (w / speed) * dt;
      if (x <= -w) x += w;
      track.style.transform = `translate3d(${x}px, 0, 0)`;
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [speed]);

  const renderRow = (copyKey, withRef) =>
    React.createElement(
      "div",
      {
        key: copyKey,
        ref: withRef ? copyRef : null,
        style: { display: "inline-flex", flexShrink: 0 },
      },
      items.map((it, i) => (
        <span
          key={`${copyKey}-${i}`}
          style={{
            display: "inline-flex",
            alignItems: "center",
            gap: 28,
            paddingRight: 28,
            whiteSpace: "nowrap",
          }}
        >
          <span
            style={{
              fontFamily: "'Lexend', sans-serif",
              fontSize: "clamp(15px, 1.6vw, 21px)",
              fontWeight: 600,
              letterSpacing: 2.5,
              textTransform: "uppercase",
              color: theme.mute,
            }}
          >
            {it}
          </span>
          <span aria-hidden style={{ color: theme.accent, fontSize: 12 }}>◆</span>
        </span>
      ))
    );

  return (
    <div
      aria-hidden
      style={{
        overflow: "hidden",
        maskImage: "linear-gradient(90deg, transparent, black 12%, black 88%, transparent)",
        WebkitMaskImage: "linear-gradient(90deg, transparent, black 12%, black 88%, transparent)",
        padding: "18px 0",
        ...style,
      }}
    >
      <div
        ref={trackRef}
        className="sv-marquee-track"
        style={{ display: "inline-flex", willChange: "transform" }}
      >
        {renderRow("a", true)}
        {renderRow("b", false)}
        {renderRow("c", false)}
      </div>
    </div>
  );
}

// Typewriter loop — cycles through lines char-by-char with a blinking caret.
// Under reduced motion it renders the first line statically.
function TypeLoop({ lines, typeMs = 34, holdMs = 1700, style }) {
  const theme = useActiveTheme();
  const [text, setText] = React.useState(decorMotionOk() ? "" : lines[0]);
  React.useEffect(() => {
    if (!decorMotionOk()) return;
    let line = 0;
    let char = 0;
    let deleting = false;
    let timer;
    const step = () => {
      const full = lines[line];
      if (!deleting) {
        char++;
        setText(full.slice(0, char));
        if (char >= full.length) {
          deleting = true;
          timer = setTimeout(step, holdMs);
          return;
        }
        timer = setTimeout(step, typeMs);
      } else {
        char -= 3;
        if (char <= 0) {
          char = 0;
          deleting = false;
          line = (line + 1) % lines.length;
        }
        setText(lines[line].slice(0, Math.max(char, 0)));
        timer = setTimeout(step, deleting ? 14 : 260);
      }
    };
    timer = setTimeout(step, 600);
    return () => clearTimeout(timer);
  }, []);
  return (
    <span style={style}>
      {text}
      <span
        aria-hidden
        style={{
          display: "inline-block",
          width: 8,
          height: "1.05em",
          marginLeft: 3,
          verticalAlign: "text-bottom",
          background: theme.accent,
          animation: decorMotionOk() ? "sv-caret 1.1s step-end infinite" : "none",
        }}
      />
    </span>
  );
}

window.Reveal = Reveal;
window.SpotlightCard = SpotlightCard;
window.Marquee = Marquee;
window.TypeLoop = TypeLoop;
