// overlay-app.jsx — adapted from quantum-overlay/final-app.jsx
// Cinematic serif type + breathing blur fade.
// Adapted for storyboard integration:
//   - No autoplay timer — storyboard drives beats via window.setOverlayBeat
//   - No .backdrop div — Three.js canvas is the background
//   - No PREV/NEXT — navigation is sim-driven
// Reuses window.BEATS and the Tweaks shell.

const { useState, useEffect, memo } = React;

const FONT_MAP = {
  auto: null,
  "Garamond":  "'EB Garamond', Georgia, serif",
  "Spectral":  "'Spectral', Georgia, serif",
  "Plex Mono": "'IBM Plex Mono', ui-monospace, monospace",
  "Hanken":    "'Hanken Grotesk', system-ui, sans-serif",
};

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "side": "cinematic",
  "textY": 66,
  "narrFont": "Plex Mono",
  "pace": 1,
  "accent": "#eaa6ff",
  "glow": 0.55,
  "wash": true
}/*EDITMODE-END*/;

/* ---- Word-by-word staggered fade helper ---- */
const WORD_STAGGER = 0.09; // seconds between each word
const WORD_BASE_DELAY = 0.2; // initial delay before first word

function WordReveal({ text, baseDelay, className, expand }) {
  const words = text.split(/\s+/);
  const cls = expand ? "word-reveal--expand" : "word-reveal";
  return (
    <span className={className}>
      {words.map((word, i) => (
        <span key={i} className={cls}
              style={{ animationDelay: (baseDelay + i * WORD_STAGGER) + "s" }}>
          {word}{i < words.length - 1 ? "\u00A0" : ""}
        </span>
      ))}
    </span>
  );
}

/* ---- closing epigraph (E4 "The last thing") ----
   Mounted once by the storyboard via window.startOverlayEpilogue.
   Quote reveal is CSS animation delays (see index.html); the
   quote → credits transition is state-driven (.is-credits). */
const EPI_LEAD = "You look at a butterfly and see the color of its wings. In relation to me, a relation is established between you and the butterfly: the butterfly and you are now in an entangled state.";
const EPI_CODA = "Everything in the world does not exist other than in this web of entanglement.";
const EPI_ATTRIB = "\u2014 Carlo Rovelli, Helgoland";

// Word-reveal timing (seconds, relative to epilogue mount).
// The field fades to black over the first 20s; quotes reveal on black:
// lead reveals fully, a breath, then the coda, then the attribution.
function revealDuration(text) {
  return (text.split(/\s+/).length - 1) * WORD_STAGGER + 0.8; // last word fade-in ends
}
const EPI_BREATH = 3;
const EPI_LEAD_DELAY = 21;
const EPI_CODA_DELAY = EPI_LEAD_DELAY + revealDuration(EPI_LEAD) + EPI_BREATH;   // ~28s
const EPI_ATTRIB_DELAY = EPI_CODA_DELAY + revealDuration(EPI_CODA) + 1;          // ~31s

// Quote → credits transition: a click (once the quote is fully revealed)
// advances immediately; otherwise it auto-advances after the hold.
const EPI_CLICKABLE = EPI_ATTRIB_DELAY + revealDuration(EPI_ATTRIB);             // ~32s
const EPI_HOLD = 10;                       // fully revealed epigraph hold
const EPI_AUTO_CREDITS = EPI_CLICKABLE + EPI_HOLD;                               // ~52s

function Epilogue() {
  const [credits, setCredits] = useState(false);

  useEffect(() => {
    const autoTimer = setTimeout(() => setCredits(true), EPI_AUTO_CREDITS * 1000);
    let armed = false;
    const armTimer = setTimeout(() => { armed = true; }, EPI_CLICKABLE * 1000);
    const onClick = () => { if (armed) setCredits(true); };
    window.addEventListener('click', onClick);
    return () => {
      clearTimeout(autoTimer);
      clearTimeout(armTimer);
      window.removeEventListener('click', onClick);
    };
  }, []);

  return (
    <div className={"epi" + (credits ? " is-credits" : "")}>
      <div className="epi__black" />
      <div className="epi__quote">
        <div className="epi__inner">
          <p className="q epi__lead">
            <WordReveal text={EPI_LEAD} baseDelay={EPI_LEAD_DELAY} />
          </p>
          <p className="q epi__coda">
            <WordReveal text={EPI_CODA} baseDelay={EPI_CODA_DELAY} />
          </p>
          <div className="epi__attrib">
            <WordReveal text={EPI_ATTRIB} baseDelay={EPI_ATTRIB_DELAY} />
          </div>
        </div>
      </div>
      <div className="epi__credit">
        <div className="lastcredit">
          <div className="lastcredit__line1">
            <a href="https://www.artofxinyi.com" target="_blank" rel="noopener noreferrer">Xinyi Zhang</a> &middot; <i>Quantum Butterfly Field</i> &middot; 2026
          </div>
          <div className="lastcredit__method">
            This work enacts the quantum no-butterfly effect (Yan &amp; Sinitsyn, 2020) on IonQ Forte quantum hardware.
          </div>
          <div className="lastcredit__support">With support from IonQ and Qollab</div>
        </div>
      </div>
    </div>
  );
}

/* ---- top-right collapsible physics panel ---- */
function PhysicsPanel({ beat }) {
  const [open, setOpen] = useState(false);
  const panelRef = React.useRef(null);
  const glowTimer = React.useRef(null);

  function triggerGlow(duration) {
    const el = panelRef.current;
    if (!el) return;
    clearTimeout(glowTimer.current);
    el.classList.add('is-glowing');
    glowTimer.current = setTimeout(() => el.classList.remove('is-glowing'), duration);
  }

  // On mount: glow for 3 pulses (12s), then auto-open
  useEffect(() => {
    triggerGlow(12000);
    const timer = setTimeout(() => setOpen(true), 12000);
    return () => clearTimeout(timer);
  }, []);

  // Expose glow trigger for setOverlayBeat to call on each beat change
  useEffect(() => {
    window._physPanelGlow = () => {
      if (!open) return; // only pulse after auto-opened
      setTimeout(() => triggerGlow(12000), 1000); // 1s delay, 3 pulses
    };
    return () => { delete window._physPanelGlow; };
  }, [open]);

  return (
    <div ref={panelRef} className={"fin__physpanel" + (open ? " is-open" : "")}>
      <button className="physpanel__bar" onClick={() => setOpen(o => !o)}>
        <span className="physpanel__tag">Physics</span>
        <span className="physpanel__meta">
          <span className="physpanel__phase">{beat.phaseLabel}</span>
          <svg className="physpanel__chev" viewBox="0 0 12 8" aria-hidden="true">
            <path d="M1 1.5 6 6.5 11 1.5" fill="none" stroke="currentColor"
                  strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </span>
      </button>
      <div className="physpanel__bodywrap">
        <div className="physpanel__body">
          <p className="physpanel__text">{beat.physics}</p>
        </div>
      </div>
    </div>
  );
}

/* ---- text block: Story (hero) + Physics (secondary) ---- */
/* Supports "|" split marker in beat.story for sequential line reveal.
   revealLines controls how many segments to show (null = all).
   Key is beat.num only — adding lines doesn't remount the component. */
function FinText({ beat, total, revealLines }) {
  const segments = beat.story.split("|");
  const hasSplit = segments.length > 1;
  const visibleCount = revealLines != null ? Math.min(revealLines, segments.length) : segments.length;

  // Count words in preceding visible segments for stagger offset
  let wordOffset = 0;

  return (
    <div className="fin__text" key={beat.num}>
      <div className="fin__lead">
        <p className="fin__story">
          {segments.map((line, i) => {
            if (i >= visibleCount) return null;
            const delay = WORD_BASE_DELAY + wordOffset * WORD_STAGGER + 0.3;
            wordOffset += line.split(/\s+/).length;
            return <span key={i}>
              {i > 0 ? " " : ""}<WordReveal text={line} baseDelay={delay} expand={hasSplit} />
            </span>;
          })}
        </p>
        {beat.attribution && <div className="fin__attrib">
          <WordReveal text={"\u2014 " + beat.attribution}
                      baseDelay={WORD_BASE_DELAY + wordOffset * WORD_STAGGER + 0.5} />
        </div>}
      </div>
    </div>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [active, setActive] = useState(-1); // -1 = no beat shown yet
  const [revealLines, setRevealLines] = useState(null); // null = show all lines
  const [autoReveal, setAutoReveal] = useState(false); // true = timer-driven reveal
  const [progress, setProgress] = useState(0);
  const [epilogue, setEpilogue] = useState(false);
  const total = BEATS.length;
  const beat = active >= 0 && active < total ? BEATS[active] : null;

  // Expose programmatic beat control for storyboard
  // setOverlayBeat(index, lines) — lines controls split-line reveal (null = all)
  useEffect(() => {
    window.setOverlayBeat = (index, lines) => {
      if (index >= 0 && index < total) {
        setActive(index);
        const beat = BEATS[index];
        const segments = beat.story.split("|");
        if (lines != null) {
          // Storyboard-controlled reveal — no auto timer
          setRevealLines(lines);
          setAutoReveal(false);
        } else if (segments.length > 1) {
          // Auto-reveal: start at 1, timer reveals rest
          setRevealLines(1);
          setAutoReveal(true);
        } else {
          setRevealLines(null);
          setAutoReveal(false);
        }
        setProgress(0);
        // Pulse physics panel glow on beat change
        if (window._physPanelGlow) window._physPanelGlow();
      }
    };
    return () => { delete window.setOverlayBeat; };
  }, [total]);

  // Expose epilogue trigger for storyboard (one-way; sequence is CSS-driven)
  useEffect(() => {
    window.startOverlayEpilogue = () => setEpilogue(true);
    return () => { delete window.startOverlayEpilogue; };
  }, []);

  // Auto-reveal timer for "|" split beats (only when autoReveal is true)
  const SPLIT_REVEAL_DELAY = 3000; // ms between each segment reveal
  useEffect(() => {
    if (!beat || !autoReveal) return;
    const segments = beat.story.split("|");
    if (segments.length <= 1 || revealLines == null) return;
    if (revealLines >= segments.length) return; // all revealed
    const timer = setTimeout(() => {
      setRevealLines(prev => prev != null ? prev + 1 : null);
    }, SPLIT_REVEAL_DELAY);
    return () => clearTimeout(timer);
  }, [beat, revealLines, autoReveal]);

  // Announce active beat to host app via CustomEvent
  useEffect(() => {
    if (!beat) return;
    const detail = { index: active, num: beat.num, name: beat.name, chapter: beat.chapter };
    window.dispatchEvent(new CustomEvent("qbf:beat", { detail }));
    if (typeof window.onQBFBeat === "function") window.onQBFBeat(detail);
  }, [active, beat]);

  const tone = beat ? beat.tone : { hue: 220, sat: 18, light: 60, strength: 0 };
  const washColor = `hsla(${tone.hue}, ${tone.sat}%, ${tone.light}%, ${t.wash ? tone.strength : 0})`;

  const stageStyle = {
    "--accent": t.accent,
    "--glow": t.glow,
    "--text-y": t.textY + "%",
    "--wash": washColor,
  };
  if (FONT_MAP[t.narrFont]) stageStyle["--narr-font"] = FONT_MAP[t.narrFont];

  return (
    <div className={"stage" + (epilogue ? " is-epilogue" : "")} data-side={t.side} style={stageStyle}>
      {/* No .backdrop — Three.js canvas is the background */}
      <div className="wash" />
      <div className="vignette" />
      {epilogue && <Epilogue />}
      {beat && active > 0 && <PhysicsPanel beat={beat} />}

      <div className="fin">
        {beat && (
          <div className="fin__stack">
            {beat.chapter !== "INTRO" && <div className="fin__meta">
              <span className="meta-reveal" style={{ animationDelay: WORD_BASE_DELAY + "s" }}>
                {beat.phaseLabel}
              </span>
            </div>}
            <FinText beat={beat} total={total} revealLines={revealLines} />
            {active > 0 && <div className="fin__rail rail--fade"
                 style={{ animationDelay: WORD_BASE_DELAY + "s" }}>
              {Array.from({ length: total - 1 }).map((_, i) => {
                const dotActive = active - 1;
                return (
                  <div key={i} className={"rail__dot" + (i === dotActive ? " is-on" : i < dotActive ? " is-done" : "")}
                       aria-label={"Beat " + (i + 1)}>
                    <span className="rail__core" />
                    {i === dotActive && (
                      <svg className="rail__ring" viewBox="0 0 36 36">
                        <circle className="rail__track" cx="18" cy="18" r="15" />
                        <circle className="rail__prog" cx="18" cy="18" r="15"
                                style={{ strokeDashoffset: 94.25 * (1 - progress) }} />
                      </svg>
                    )}
                  </div>
                );
              })}
            </div>}
          </div>
        )}
      </div>

      <TweaksPanel>
        <TweakSection label="Text" />
        <TweakSelect label="Layout" value={t.side} options={["right", "left", "cinematic"]}
          onChange={(v) => setTweak("side", v)} />
        <TweakSlider label="Text height" value={t.textY} min={30} max={78} unit="%"
          onChange={(v) => setTweak("textY", v)} />
        <TweakSelect label="Physics font" value={t.narrFont}
          options={["auto", "Garamond", "Spectral", "Plex Mono", "Hanken"]}
          onChange={(v) => setTweak("narrFont", v)} />
        <TweakSection label="Field" />
        <TweakColor label="Accent" value={t.accent}
          options={["#eaa6ff", "#9ad7ff", "#ffd59a", "#a8ffd0", "#ffffff"]}
          onChange={(v) => setTweak("accent", v)} />
        <TweakSlider label="Glow" value={t.glow} min={0} max={1} step={0.05}
          onChange={(v) => setTweak("glow", v)} />
        <TweakToggle label="Ambient wash" value={t.wash}
          onChange={(v) => setTweak("wash", v)} />
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("overlay-root")).render(<App />);
