// origin-film-lib.jsx — shared primitives for the GrytLabs Origin Story film.
// Brand: indigo-black + studio gold (colors_and_type.css / brand-guidelines v0.4.0).
const { Sprite, useSprite, useTime, useTimeline, Easing, clamp, interpolate } = window;

const FILM = {
  W: 1920, H: 1080,
  bg: '#050a17', surface: '#0a1424', card: '#131e36',
  line: '#1a2844', lineStrong: '#28406a',
  ink: '#e8f0fa', ink2: '#c0d9f0', sec: '#8ab4e0', mut: '#5a8bc2', dim: '#3b6398',
  gold: '#c9a55a', goldHi: '#e0c184', goldSoft: 'rgba(201,165,90,0.16)',
  serif: '"Iowan Old Style","Palatino Linotype",Palatino,Georgia,"Times New Roman",serif',
  sans: "'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif",
  mono: "'Geist Mono', ui-monospace, SFMono-Regular, monospace",
};

// Global film config (tweaks) — provided by the app.
const FilmConfigContext = React.createContext({ showQuotes: true, motif: 'thread', opening: 'box' });
const useFilmConfig = () => React.useContext(FilmConfigContext);

// ── Appear — the workhorse: fade/slide a block in at local time `at`,
//    optionally out at `out`. align 'center' centers on x. ────────────────
function Appear({ at = 0, dur = 0.9, from = 26, out = null, outDur = 0.8,
                  x = 0, y = 0, w, align = 'left', style, children }) {
  const { localTime } = useSprite();
  let o = 0, ty = from;
  if (localTime >= at) {
    const t = Easing.easeOutCubic(clamp((localTime - at) / dur, 0, 1));
    o = t; ty = (1 - t) * from;
  }
  if (out != null && localTime >= out) {
    const t = Easing.easeInCubic(clamp((localTime - out) / outDur, 0, 1));
    o *= (1 - t); ty = -t * 12;
  }
  if (o <= 0.001) return null;
  const tx = align === 'center' ? '-50%' : '0px';
  return (
    <div style={{ position: 'absolute', left: x, top: y, width: w,
      opacity: o, transform: `translate(${tx}, ${ty}px)`,
      willChange: 'transform, opacity', ...style }}>
      {children}
    </div>
  );
}

// ── ChapterShell — entry/exit fade + slow camera drift for a whole chapter ──
function ChapterShell({ grow = 0.032, origin = '44% 40%', children }) {
  const { localTime, duration, progress } = useSprite();
  const inO = Easing.easeOutCubic(clamp(localTime / 0.8, 0, 1));
  const outO = 1 - Easing.easeInCubic(clamp((localTime - (duration - 1.1)) / 1.1, 0, 1));
  return (
    <div style={{ position: 'absolute', inset: 0, opacity: Math.min(inO, outO),
      transform: `scale(${1 + grow * progress})`, transformOrigin: origin }}>
      {children}
    </div>
  );
}

// ── Text helpers ────────────────────────────────────────────────────────────
const Hi = ({ children }) => <span style={{ color: FILM.ink, fontWeight: 600 }}>{children}</span>;
const Em = ({ children }) => <span style={{ color: FILM.goldHi, fontStyle: 'italic' }}>{children}</span>;

function Body({ size = 33, color = FILM.ink2, children, style }) {
  return (
    <div style={{ fontFamily: FILM.serif, fontSize: size, lineHeight: 1.56,
      color, textWrap: 'pretty', ...style }}>
      {children}
    </div>
  );
}

function ChapterHead({ date, kicker, title, at = 0.3, size = 96, w = 1440, titleStyle }) {
  return (
    <React.Fragment>
      <Appear at={at} x={200} y={168}>
        <div style={{ fontFamily: FILM.mono, fontSize: 22, letterSpacing: '0.3em',
          textTransform: 'uppercase', color: FILM.gold, whiteSpace: 'nowrap' }}>
          {date}{kicker ? <span style={{ color: FILM.dim }}>&nbsp;&nbsp;·&nbsp;&nbsp;{kicker}</span> : null}
        </div>
      </Appear>
      <Appear at={at + 0.55} x={196} y={216} w={w}>
        <div style={{ fontFamily: FILM.serif, fontWeight: 600, fontSize: size,
          lineHeight: 1.04, color: FILM.ink, letterSpacing: '-0.01em', ...titleStyle }}>
          {title}
        </div>
      </Appear>
    </React.Fragment>
  );
}

// ── Quote — pull quote; hidden when the "Pull quotes" tweak is off ─────────
function Quote({ at, out, x = 200, y, w = 1000, size = 42, quote, who, tag }) {
  const { showQuotes } = useFilmConfig();
  if (!showQuotes) return null;
  return (
    <Appear at={at} out={out} x={x} y={y} w={w}>
      <div style={{ borderLeft: `3px solid ${FILM.gold}`, paddingLeft: 30 }}>
        <div style={{ fontFamily: FILM.serif, fontStyle: 'italic', fontSize: size,
          lineHeight: 1.34, color: FILM.ink, textWrap: 'pretty' }}>{quote}</div>
        {who ? <div style={{ marginTop: 16, fontFamily: FILM.mono, fontSize: 16,
          letterSpacing: '0.2em', textTransform: 'uppercase', color: FILM.dim }}>{who}</div> : null}
        {tag ? <div style={{ marginTop: 10, fontFamily: FILM.mono, fontSize: 15,
          letterSpacing: '0.2em', textTransform: 'uppercase', color: FILM.gold }}>{tag}</div> : null}
      </div>
    </Appear>
  );
}

// ── Beats — the ledger of concrete outputs, staggered in ───────────────────
function Beats({ at = 0, x = 1240, y = 380, w = 520, step = 0.55, rowH = 88, label, items }) {
  return (
    <React.Fragment>
      {label ? (
        <Appear at={at} x={x} y={y - 48}>
          <div style={{ fontFamily: FILM.mono, fontSize: 15, letterSpacing: '0.26em',
            textTransform: 'uppercase', color: FILM.gold }}>{label}</div>
        </Appear>
      ) : null}
      {items.map((it, i) => (
        <Appear key={i} at={at + i * step} x={x} y={y + i * rowH} w={w} from={18}>
          <div style={{ display: 'flex', gap: 16, alignItems: 'baseline',
            borderTop: `1px solid ${FILM.line}`, paddingTop: 16 }}>
            <span style={{ width: 11, height: 11, flex: '0 0 auto', background: FILM.gold,
              transform: 'rotate(45deg) translateY(1px)', borderRadius: 2, opacity: 0.9 }}></span>
            <span>
              <span style={{ fontFamily: FILM.sans, fontSize: 23, fontWeight: 600,
                color: FILM.ink, display: 'block' }}>{it[0]}</span>
              {it[1] ? <span style={{ fontFamily: FILM.sans, fontSize: 19, color: FILM.mut,
                display: 'block', marginTop: 3 }}>{it[1]}</span> : null}
            </span>
          </div>
        </Appear>
      ))}
    </React.Fragment>
  );
}

// ── GlassMoment — heightened glass card for pivotal beats ──────────────────
function GlassMoment({ at, out, x = 360, y = 460, w = 1200, label, who, children }) {
  return (
    <Appear at={at} out={out} x={x} y={y} w={w} from={34} dur={1.1}>
      <div style={{ background: 'rgba(10,20,36,0.78)', border: '1px solid rgba(201,165,90,0.32)',
        borderRadius: 16, padding: '38px 46px',
        boxShadow: 'inset 0 0 80px rgba(201,165,90,0.06), 0 8px 32px rgba(0,0,0,0.3)' }}>
        {label ? <div style={{ fontFamily: FILM.mono, fontSize: 15, letterSpacing: '0.24em',
          textTransform: 'uppercase', color: FILM.gold, marginBottom: 18 }}>{label}</div> : null}
        <div style={{ fontFamily: FILM.serif, fontStyle: 'italic', fontSize: 40,
          lineHeight: 1.32, color: '#fff', textWrap: 'pretty' }}>{children}</div>
        {who ? <div style={{ fontFamily: FILM.mono, fontSize: 16, letterSpacing: '0.2em',
          textTransform: 'uppercase', color: FILM.dim, marginTop: 22 }}>{who}</div> : null}
      </div>
    </Appear>
  );
}

// ── HexMark — the GrytLabs mark, self-drawing ───────────────────────────────
function HexMark({ x, y, size = 140, progress = 1, opacity = 1, align = 'left' }) {
  const p = clamp(progress, 0, 1);
  const innerO = clamp((p - 0.55) / 0.45, 0, 1) * 0.65;
  const tx = align === 'center' ? '-50%' : '0px';
  return (
    <div style={{ position: 'absolute', left: x, top: y, opacity,
      transform: `translate(${tx}, 0)` }}>
      <svg width={size} height={size} viewBox="0 0 100 100" fill="none">
        <polygon points="50,6 90,28 90,72 50,94 10,72 10,28" stroke={FILM.gold}
          strokeWidth="2" pathLength="1" strokeDasharray="1" strokeDashoffset={1 - p}></polygon>
        <path d="M50 6 L50 94 M10 28 L90 72 M90 28 L10 72 M10 28 L50 50 L90 28 M10 72 L50 50 L90 72"
          stroke={FILM.gold} strokeWidth="1" opacity={innerO}></path>
      </svg>
    </div>
  );
}

// ── LogoMark — the official GrytLabs mark: chrome strata pierced by the gold
//    spine + apex node (from favicon.svg). Pure inline SVG, sized by `size`. ──
function LogoMark({ size = 104 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 64 64" role="img" aria-label="GrytLabs"
      style={{ display: 'block', overflow: 'visible' }}>
      <defs>
        <linearGradient id="lmSpine" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="#fff5d4"></stop>
          <stop offset="35%" stopColor="#e8c478"></stop>
          <stop offset="65%" stopColor="#c9a55a"></stop>
          <stop offset="100%" stopColor="#a08545"></stop>
        </linearGradient>
        <radialGradient id="lmNode" cx="35%" cy="35%" r="65%">
          <stop offset="0%" stopColor="#fff5d4"></stop>
          <stop offset="40%" stopColor="#e8c478"></stop>
          <stop offset="100%" stopColor="#a08545"></stop>
        </radialGradient>
        <radialGradient id="lmApex" cx="50%" cy="50%" r="50%">
          <stop offset="0%" stopColor="#fff5d4"></stop>
          <stop offset="45%" stopColor="#e8c478"></stop>
          <stop offset="100%" stopColor="#c9a55a" stopOpacity="0"></stop>
        </radialGradient>
        <filter id="lmGlow" x="-80%" y="-80%" width="260%" height="260%">
          <feGaussianBlur stdDeviation="1.9"></feGaussianBlur>
        </filter>
        <linearGradient id="lmC0" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#1a2844"></stop><stop offset="0.15" stopColor="#a8c8e8"></stop><stop offset="0.35" stopColor="#ffffff"></stop><stop offset="0.55" stopColor="#3b6398"></stop><stop offset="0.78" stopColor="#8ab4e0"></stop><stop offset="1" stopColor="#1a2844"></stop></linearGradient>
        <linearGradient id="lmC1" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#0e1830"></stop><stop offset="0.15" stopColor="#8ab4e0"></stop><stop offset="0.35" stopColor="#e8f0fa"></stop><stop offset="0.55" stopColor="#28406a"></stop><stop offset="0.78" stopColor="#5a8bc2"></stop><stop offset="1" stopColor="#0e1830"></stop></linearGradient>
        <linearGradient id="lmC2" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#08101c"></stop><stop offset="0.15" stopColor="#5a8bc2"></stop><stop offset="0.35" stopColor="#c0d9f0"></stop><stop offset="0.55" stopColor="#1a2844"></stop><stop offset="0.78" stopColor="#3b6398"></stop><stop offset="1" stopColor="#08101c"></stop></linearGradient>
        <linearGradient id="lmC3" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#040810"></stop><stop offset="0.15" stopColor="#3b6398"></stop><stop offset="0.35" stopColor="#8ab4e0"></stop><stop offset="0.55" stopColor="#0e1830"></stop><stop offset="0.78" stopColor="#28406a"></stop><stop offset="1" stopColor="#040810"></stop></linearGradient>
        <linearGradient id="lmC4" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="#020410"></stop><stop offset="0.15" stopColor="#28406a"></stop><stop offset="0.35" stopColor="#5a8bc2"></stop><stop offset="0.55" stopColor="#0a1424"></stop><stop offset="0.78" stopColor="#1a2844"></stop><stop offset="1" stopColor="#020410"></stop></linearGradient>
      </defs>
      <rect x="17" y="14.7" width="30" height="3.8" rx="1.9" fill="url(#lmC0)"></rect>
      <rect x="17" y="22.9" width="30" height="3.8" rx="1.9" fill="url(#lmC1)"></rect>
      <rect x="17" y="31.1" width="30" height="3.8" rx="1.9" fill="url(#lmC2)"></rect>
      <rect x="17" y="39.3" width="30" height="3.8" rx="1.9" fill="url(#lmC3)"></rect>
      <rect x="17" y="47.5" width="30" height="3.8" rx="1.9" fill="url(#lmC4)"></rect>
      <line x1="32" y1="12" x2="32" y2="53" stroke="url(#lmSpine)" strokeWidth="2.6" strokeLinecap="round"></line>
      <circle cx="32" cy="12" r="8" fill="#e8c478" opacity="0.4" filter="url(#lmGlow)"></circle>
      <circle cx="32" cy="12" r="6" fill="url(#lmApex)" opacity="0.7"></circle>
      <circle cx="32" cy="12" r="4" fill="url(#lmNode)"></circle>
      <circle cx="32" cy="24.8" r="2" fill="url(#lmNode)"></circle>
      <circle cx="32" cy="33.0" r="2" fill="url(#lmNode)"></circle>
      <circle cx="32" cy="41.2" r="2" fill="url(#lmNode)"></circle>
      <circle cx="32" cy="49.4" r="2.4" fill="url(#lmNode)"></circle>
    </svg>
  );
}

// ── AmbientBg — blueprint grid + drifting gold glow + vignette ─────────────
function AmbientBg() {
  const t = useTime();
  const gx = 72 + 9 * Math.sin(t * 0.045);
  const gy = -6 + 7 * Math.cos(t * 0.032);
  const bx = -8 + 6 * Math.sin(t * 0.028 + 2);
  return (
    <React.Fragment>
      <div style={{ position: 'absolute', inset: 0, background: FILM.bg }}></div>
      <div style={{ position: 'absolute', inset: 0, backgroundImage:
        'linear-gradient(rgba(201,165,90,0.055) 1px, transparent 1px),' +
        'linear-gradient(90deg, rgba(201,165,90,0.055) 1px, transparent 1px),' +
        'linear-gradient(rgba(201,165,90,0.028) 1px, transparent 1px),' +
        'linear-gradient(90deg, rgba(201,165,90,0.028) 1px, transparent 1px)',
        backgroundSize: '100px 100px, 100px 100px, 20px 20px, 20px 20px' }}></div>
      <div style={{ position: 'absolute', inset: 0, background:
        `radial-gradient(1000px 680px at ${gx}% ${gy}%, rgba(201,165,90,0.085), transparent 65%)` }}></div>
      <div style={{ position: 'absolute', inset: 0, background:
        `radial-gradient(900px 640px at ${bx}% 78%, rgba(59,99,152,0.10), transparent 62%)` }}></div>
      <div style={{ position: 'absolute', inset: 0, background:
        'radial-gradient(130% 95% at 50% 46%, transparent 55%, rgba(2,5,12,0.6) 100%)' }}></div>
    </React.Fragment>
  );
}

// ── MotifLayer — persistent story-spine, two variants ───────────────────────
function MotifLayer({ variant, marks = [] }) {
  const { time, duration } = useTimeline();
  if (variant === 'none') return null;
  if (variant === 'constellation') return <Constellation time={time} duration={duration} />;
  return <Thread time={time} duration={duration} marks={marks} />;
}

function Thread({ time, duration, marks }) {
  const x0 = 150, x1 = 1770, y = 998;
  const xFor = (t) => x0 + (t / duration) * (x1 - x0);
  const px = xFor(time);
  return (
    <svg width="1920" height="1080" style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
      <line x1={x0} y1={y} x2={x1} y2={y} stroke={FILM.line} strokeWidth="1.5"></line>
      <line x1={x0} y1={y} x2={px} y2={y} stroke={FILM.gold} strokeWidth="2" opacity="0.85"></line>
      {marks.map((m, i) => {
        const mx = xFor(m.t), passed = time >= m.t;
        return (
          <g key={i}>
            <circle cx={mx} cy={y} r="5" fill={passed ? FILM.gold : FILM.bg}
              stroke={passed ? FILM.gold : FILM.lineStrong} strokeWidth="1.5"></circle>
            <text x={mx} y={y + 32} textAnchor="middle" fill={passed ? FILM.gold : FILM.dim}
              opacity={passed ? 0.85 : 0.6} fontFamily="Geist Mono, monospace"
              fontSize="13" letterSpacing="3">{m.label}</text>
          </g>
        );
      })}
      <circle cx={px} cy={y} r="4" fill={FILM.goldHi}></circle>
      <circle cx={px} cy={y} r="9.5" fill="none" stroke={FILM.gold} opacity="0.45"></circle>
    </svg>
  );
}

// Deterministic pseudo-random constellation of milestone stars.
const frac = (v) => v - Math.floor(v);
const STARS = Array.from({ length: 26 }, (_, i) => {
  const r1 = frac(Math.sin(i * 127.1 + 311.7) * 43758.5453);
  const r2 = frac(Math.sin(i * 269.5 + 183.3) * 43758.5453);
  const r3 = frac(Math.sin(i * 419.2 + 371.9) * 43758.5453);
  const r4 = frac(Math.sin(i * 631.1 + 97.7) * 43758.5453);
  return {
    x: 90 + r1 * 1740,
    y: 50 + r2 * 980,
    r: 2 + r3 * 2.4,
    base: 0.16 + r3 * 0.3,
    appear: r4,
    link: i > 0 ? Math.max(0, i - 1 - Math.floor(r2 * 3)) : -1,
  };
});

function Constellation({ time, duration }) {
  const ord = STARS.map((s, i) => ({ ...s, i })).sort((a, b) => a.appear - b.appear);
  const tAt = {};
  ord.forEach((s, k) => { tAt[s.i] = 1 + (k / ord.length) * (duration * 0.92); });
  const oFor = (i) => clamp((time - tAt[i]) / 1.6, 0, 1) * STARS[i].base *
    (0.78 + 0.22 * Math.sin(time * 1.25 + i * 1.7));
  return (
    <svg width="1920" height="1080" style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
      {STARS.map((s, i) => (s.link >= 0 ? (
        <line key={'l' + i} x1={s.x} y1={s.y} x2={STARS[s.link].x} y2={STARS[s.link].y}
          stroke={FILM.gold} strokeWidth="1"
          opacity={Math.min(oFor(i), oFor(s.link)) * 0.45}></line>
      ) : null))}
      {STARS.map((s, i) => (
        <circle key={'s' + i} cx={s.x} cy={s.y} r={s.r} fill={FILM.gold} opacity={oFor(i)}></circle>
      ))}
    </svg>
  );
}

Object.assign(window, {
  FILM, FilmConfigContext, useFilmConfig,
  Appear, ChapterShell, Hi, Em, Body, ChapterHead, Quote, Beats,
  GlassMoment, HexMark, LogoMark, AmbientBg, MotifLayer,
});
