// reader.jsx — Ashfall reader, multi-chapter, candles + audio

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

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "ashfall",
  "font": "cormorant",
  "fontSize": 19,
  "lineHeight": 1.55,
  "pageWidth": 580,
  "justify": true,
  "audioEnabled": true
}/*EDITMODE-END*/;

// ── build the linear list of "screens"
function buildPages() {
  const out = [];
  out.push({ kind: "cover" });
  out.push({ kind: "halftitle" });
  out.push({ kind: "warning" });
  out.push({ kind: "toc" });
  for (const ch of CHAPTERS) {
    out.push({ kind: "chapter-title", ch });
    const pages = window[`CH${ch.n}_PAGES`];
    if (pages && pages.length) {
      pages.forEach((p, i) => out.push({ ...p, ch, isLastInCh: i === pages.length - 1 }));
    } else {
      out.push({ kind: "synopsis", ch });
    }
  }
  out.push({ kind: "thefin" });
  return out;
}

// ── localStorage utils ──
const LS_KEY = "ashfall.v1";
function loadState() {
  try { return JSON.parse(localStorage.getItem(LS_KEY)) || {}; } catch (_) { return {}; }
}
function saveState(s) {
  try { localStorage.setItem(LS_KEY, JSON.stringify(s)); } catch (_) {}
}

// ── page-turn sound (Web Audio, synthesized soft paper rustle) ──
let __audioCtx = null;
function __getCtx() {
  if (!__audioCtx) {
    try {
      const C = window.AudioContext || window.webkitAudioContext;
      if (C) __audioCtx = new C();
    } catch (_) {}
  }
  return __audioCtx;
}
function pageTurnSound(forward) {
  const ctx = __getCtx();
  if (!ctx) return;
  if (ctx.state === "suspended") { try { ctx.resume(); } catch (_) {} }
  const now = ctx.currentTime;
  const dur = 0.22;
  // pink-ish noise burst, band-pass filtered
  const buf = ctx.createBuffer(1, Math.floor(ctx.sampleRate * dur), ctx.sampleRate);
  const data = buf.getChannelData(0);
  let b0 = 0, b1 = 0, b2 = 0;
  for (let i = 0; i < data.length; i++) {
    const white = Math.random() * 2 - 1;
    // simple pink-ish filter (Voss approximation)
    b0 = 0.99765 * b0 + white * 0.0990460;
    b1 = 0.96300 * b1 + white * 0.2965164;
    b2 = 0.57000 * b2 + white * 1.0526913;
    data[i] = (b0 + b1 + b2 + white * 0.1848) * 0.10;
  }
  const src = ctx.createBufferSource();
  src.buffer = buf;
  const filt = ctx.createBiquadFilter();
  filt.type = "bandpass";
  filt.frequency.value = forward ? 2400 : 1800;
  filt.Q.value = 1.4;
  const gain = ctx.createGain();
  gain.gain.setValueAtTime(0, now);
  gain.gain.linearRampToValueAtTime(0.10, now + 0.012);
  gain.gain.exponentialRampToValueAtTime(0.0001, now + dur);
  // gentle high-pass to remove rumble
  const hp = ctx.createBiquadFilter();
  hp.type = "highpass";
  hp.frequency.value = 700;
  src.connect(hp).connect(filt).connect(gain).connect(ctx.destination);
  src.start(now);
  src.stop(now + dur + 0.02);
}

// ─────────────── screen renderers ───────────────

function Cover() {
  return (
    <div className="cover">
      <div className="cover-bg"></div>
      <div className="cover-top">
        <div className="cover-eyebrow">{BOOK.publisher}</div>
        <div className="cover-pub">22 Veilles · 1 Cage</div>
      </div>
      <div className="cover-mid">
        <div className="cover-rule"></div>
        <h1 className="cover-title">Ashfall</h1>
        <div className="cover-tag">« Le vide n'est pas une absence. C'est une méthode. »</div>
        <div className="cover-rule pulse"></div>
      </div>
      <div className="cover-bot">
        <div className="cover-author">{BOOK.author}</div>
        <div className="cover-pub" style={{ opacity: 0.55, marginTop: 6 }}>
          Marseille&nbsp;·&nbsp;MMXXVI
        </div>
      </div>
    </div>
  );
}

function HalfTitle() {
  return (
    <div className="halftitle">
      <div className="ht-name">Ashfall</div>
      <div className="ht-tag">« On organise un tel décor pour quelqu'un que l'on a déjà. »</div>
      <div className="ht-meta">{BOOK.author}</div>
      <div className="ht-meta" style={{ opacity: 0.6 }}>{BOOK.publisher}</div>
      <div className="ht-warning">{BOOK.warning}</div>
    </div>
  );
}

function WarningPage() {
  return (
    <div className="warning-page">
      <div className="warning-head">
        <div className="stamp">Content Warnings</div>
        <h3>Ce livre contient.</h3>
        <div className="by">« Si vous avez besoin d'une porte de sortie, elle est encore là. »</div>
      </div>
      <div className="warning-grid">
        {CONTENT_WARNINGS.map((w, i) => (
          <div key={i} className="item">{w}</div>
        ))}
      </div>
      <div className="warning-foot">
        Aucune sexualité anatomique explicite. Tout le « spice » passe par le système nerveux
        de Sasha&nbsp;: tachycardie, hypoxie, sueurs froides, dilatation pupillaire.
        <br />Ceci est, par construction, un livre psychologique.
      </div>
    </div>
  );
}

function TOC({ litUpTo, current, onJump }) {
  return (
    <div className="toc">
      <div className="toc-head">
        <div className="label">Table des Vingt-Deux Veilles</div>
        <div className="title">Les bougies allumées</div>
        <div className="sub">« On allume une bougie par chapitre lu. On en éteint aucune. »</div>
      </div>
      <div className="toc-grid">
        {Array.from({ length: 22 }, (_, i) => i + 1).map((n) => (
          <Candle key={n} n={n} lit={n <= litUpTo} current={n === current}
                  onClick={() => onJump(n)}
                  title={CHAPTERS[n-1] && CHAPTERS[n-1].name} />
        ))}
      </div>
      <div className="toc-list">
        {CHAPTERS.map((ch) => (
          <div key={ch.n}
               className={`toc-item ${ch.n === current ? "current" : ""}`}
               onClick={() => onJump(ch.n)}>
            <span className="num">{ROMAN[ch.n]}</span>
            <span className="name">{ch.name}</span>
            <span className="act">{["I","II","III"][actOf(ch.n)-1]}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function ChapterTitle({ ch }) {
  const sc = scentForChapter(ch.n);
  return (
    <div className="chapter-title">
      <div className="ch-marker">{actLabel(ch.n)}</div>
      <div className="ch-rule"></div>
      <div className="ch-number">Chapitre {ROMAN[ch.n]}</div>
      <h2 className="ch-name">{ch.name}</h2>
      <div className="ch-sub">« {ch.epigraph} »</div>
      {sc && (
        <div className="scent" style={{ "--scent-glow": sc.glow }}>
          <div className="scent-row">
            <div className="scent-disk"></div>
            <div className="scent-name">Bougie&nbsp;·&nbsp;{sc.name}</div>
          </div>
          <div className="scent-notes">{sc.notes}</div>
          <div className="scent-moment">{sc.moment}</div>
        </div>
      )}
      <div className="ch-act-line">
        <div className="h"></div>
        <div className="lbl">Veille n°{String(ch.n).padStart(2,"0")}</div>
        <div className="h"></div>
      </div>
    </div>
  );
}

function Body({ html }) {
  return <div className="prose" dangerouslySetInnerHTML={{ __html: html }} />;
}

function Synopsis({ ch }) {
  return (
    <div className="synopsis">
      <div className="synopsis-head">
        <div className="ttl">{ch.name}</div>
        <div className="meta">{actLabel(ch.n)}</div>
      </div>
      <div className="synopsis-body">
        {ch.synopsis.map((p, i) => (
          <p key={i} dangerouslySetInnerHTML={{ __html: p }} />
        ))}
      </div>
      <div className="synopsis-tags">
        <div className="synopsis-tag">
          <div className="k">Sound Design</div>
          <div className="v">{ch.soundDesign}</div>
        </div>
        <div className="synopsis-tag">
          <div className="k">Note de Spice</div>
          <div className="v">{ch.spice}</div>
        </div>
        <div className="synopsis-tag">
          <div className="k">Veille audio</div>
          <div className="v">{ch.audioTitle}</div>
        </div>
        <div className="synopsis-tag">
          <div className="k">Épigraphe</div>
          <div className="v">« {ch.epigraph} »</div>
        </div>
      </div>
      <div className="synopsis-status">Aperçu · À paraître</div>
    </div>
  );
}

function EndCard({ ch }) {
  const next = CHAPTERS[ch.n] && CHAPTERS[ch.n]; // ch.n is 1-indexed, so ch.n equals next index
  return (
    <div className="endcard">
      <div className="endcard-mark">⁂</div>
      <div className="endcard-line"></div>
      <div className="endcard-next">Fin du Chapitre {ROMAN[ch.n]}</div>
      <div className="endcard-ch" style={{ fontStyle: "italic" }}>— {ch.name} —</div>
      <div className="endcard-line"></div>
      {next && (
        <div className="endcard-next" style={{ opacity: 0.55 }}>
          Chapitre {ROMAN[next.n]}&nbsp;·&nbsp;
          <span style={{ fontStyle: "italic", fontFamily: "var(--serif)", textTransform: "none", letterSpacing: 0 }}>
            {next.name}
          </span>
        </div>
      )}
    </div>
  );
}

function TheFin() {
  return (
    <div className="endcard">
      <div className="endcard-mark">∎</div>
      <div className="endcard-line"></div>
      <div className="endcard-next">Fin du livre</div>
      <div className="endcard-ch">— Ashfall —</div>
      <div className="endcard-line"></div>
      <div className="endcard-next" style={{ opacity: 0.55 }}>{BOOK.author}&nbsp;·&nbsp;{BOOK.publisher}</div>
    </div>
  );
}

// ─────────────── reader ───────────────

function Reader() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const SCREENS = useMemo(buildPages, []);
  const TOTAL = SCREENS.length;

  const initial = loadState();
  const [idx, setIdx] = useState(typeof initial.idx === "number" ? Math.min(initial.idx, TOTAL - 1) : 0);
  const [litUpTo, setLitUpTo] = useState(typeof initial.litUpTo === "number" ? initial.litUpTo : 0);
  const [phase, setPhase] = useState("show");
  const [audioOn, setAudioOn] = useState(t.audioEnabled);

  const screen = SCREENS[idx];
  const currentCh = screen?.ch?.n || (screen?.kind === "chapter-title" ? screen.ch.n : null);

  // light candles up to current chapter
  useEffect(() => {
    if (currentCh && currentCh > litUpTo) setLitUpTo(currentCh);
  }, [currentCh]);

  // persist state
  useEffect(() => {
    saveState({ idx, litUpTo });
  }, [idx, litUpTo]);

  // theme
  useEffect(() => {
    document.documentElement.className = `theme-${t.theme}`;
  }, [t.theme]);

  // body class for cover full-bleed background
  useEffect(() => {
    document.body.classList.toggle("is-cover", screen?.kind === "cover");
  }, [screen?.kind]);

  // type vars
  useEffect(() => {
    const r = document.documentElement.style;
    const fontMap = {
      cormorant: '"Cormorant Garamond","EB Garamond","Spectral",Georgia,serif',
      garamond:  '"EB Garamond","Cormorant Garamond","Spectral",Georgia,serif',
      spectral:  '"Spectral","EB Garamond",Georgia,serif',
    };
    r.setProperty("--serif", fontMap[t.font] || fontMap.cormorant);
    r.setProperty("--display", fontMap[t.font] || fontMap.cormorant);
    r.setProperty("--type-size", `${t.fontSize}px`);
    r.setProperty("--line-h", String(t.lineHeight));
    r.setProperty("--page-w", `${t.pageWidth}px`);
  }, [t.font, t.fontSize, t.lineHeight, t.pageWidth]);

  // navigation helpers
  const go = useCallback((delta) => {
    const next = idx + delta;
    if (next < 0 || next >= TOTAL) return;
    pageTurnSound(delta > 0);
    setPhase(delta > 0 ? "exit-r" : "exit-l");
    setTimeout(() => {
      setIdx(next);
      setPhase(delta > 0 ? "enter-r" : "enter-l");
      requestAnimationFrame(() => requestAnimationFrame(() => setPhase("show")));
    }, 260);
  }, [idx, TOTAL]);

  const jumpToChapter = useCallback((n) => {
    const target = SCREENS.findIndex(s => s.kind === "chapter-title" && s.ch.n === n);
    if (target === -1) return;
    setIdx(target);
    setPhase("show");
  }, [SCREENS]);

  // keyboard
  useEffect(() => {
    const onKey = (e) => {
      const tgt = e.target;
      if (tgt && (tgt.tagName === "INPUT" || tgt.tagName === "TEXTAREA" || tgt.isContentEditable)) return;
      if (e.key === "ArrowRight" || e.key === " " || e.key === "PageDown") { e.preventDefault(); go(+1); }
      else if (e.key === "ArrowLeft" || e.key === "PageUp") { e.preventDefault(); go(-1); }
      else if (e.key === "Home") { e.preventDefault(); setIdx(0); setPhase("show"); }
      else if (e.key === "End")  { e.preventDefault(); setIdx(TOTAL - 1); setPhase("show"); }
      else if (e.key === "t" || e.key === "T") {
        const i = SCREENS.findIndex(s => s.kind === "toc");
        if (i !== -1) { setIdx(i); setPhase("show"); }
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [go, TOTAL, SCREENS]);

  // click halves
  const stageRef = useRef(null);
  const onStageClick = (e) => {
    if (e.target.closest(".audio-bar")) return;
    if (e.target.closest(".twk-panel")) return;
    const rect = stageRef.current.getBoundingClientRect();
    const x = e.clientX - rect.left;
    if (x < rect.width / 2) go(-1); else go(+1);
  };

  // chrome strings
  const chrumbLeft  = currentCh ? `Chapitre ${ROMAN[currentCh]}` : (screen.kind === "toc" ? "Sommaire" : screen.kind === "cover" ? "Couverture" : "");
  const chrumbRight = currentCh ? CHAPTERS[currentCh-1].name : "";
  const progress = idx === 0 ? 0 : (idx / (TOTAL - 1)) * 100;

  // audio
  const chForAudio = currentCh || (screen.kind === "chapter-title" ? screen.ch.n : null) || 1;
  const showAudio = !!currentCh; // audio shows once we're inside a chapter

  // theme options for TweakRadio (3 fit segments)
  const themeOpts = [
    { value: "ashfall", label: "Ashfall" },
    { value: "marbre",  label: "Marbre" },
    { value: "beton",   label: "Béton" },
  ];

  // page render
  const renderScreen = () => {
    switch (screen.kind) {
      case "cover":          return <Cover />;
      case "halftitle":      return <HalfTitle />;
      case "warning":        return <WarningPage />;
      case "toc":            return <TOC litUpTo={litUpTo} current={currentCh} onJump={jumpToChapter} />;
      case "chapter-title":  return <ChapterTitle ch={screen.ch} />;
      case "body":           return <Body html={screen.html} />;
      case "synopsis":       return <Synopsis ch={screen.ch} />;
      case "endcard":        return <EndCard ch={screen.ch} />;
      case "thefin":         return <TheFin />;
      default: return null;
    }
  };

  return (
    <div className="reader">
      {/* TOP CHROME */}
      <div className="chrome-top">
        <div className="crumb-l">{chrumbLeft || <span>&nbsp;</span>}</div>
        <div className="crumb-c"><span className="brand">ASHFALL</span></div>
        <div className="crumb-r">
          {chrumbRight ? (
            <span style={{ fontStyle: "italic", letterSpacing: "0.06em", textTransform: "none" }}>{chrumbRight}</span>
          ) : <span>&nbsp;</span>}
        </div>
      </div>

      {/* STAGE */}
      <div className="stage" ref={stageRef} onClick={onStageClick}>
        <div className={`page ${phase}`} style={{ textAlign: t.justify ? undefined : "left" }}>
          <div className="page-inner">
            {renderScreen()}
          </div>
        </div>
        <div className="nav-arrow l">‹</div>
        <div className="nav-arrow r">›</div>
      </div>

      {/* PROGRESS */}
      <div className="progress">
        <div className="progress-fill" style={{ width: `${progress}%` }}></div>
      </div>

      {/* AUDIO BAR (only inside chapters) */}
      {showAudio && (
        <AudioBar
          chapter={chForAudio}
          src={audioSrc(chForAudio)}
          title={CHAPTERS[chForAudio - 1].audioTitle}
          enabled={audioOn}
          onToggle={(v) => setAudioOn(v)}
        />
      )}

      {/* BOTTOM CHROME — candles row + page count */}
      <div className="chrome-bot">
        <div className="crumb-l">
          <CandlesRow litUpTo={litUpTo} current={currentCh} onSelect={jumpToChapter} />
        </div>
        <div className="crumb-c">
          <span>{String(idx + 1).padStart(2, "0")}</span>
          <span className="dot"></span>
          <span>{String(TOTAL).padStart(2, "0")}</span>
        </div>
        <div className="crumb-r">
          <span>← →&nbsp;&nbsp;page&nbsp;&nbsp;·&nbsp;&nbsp;T&nbsp;sommaire</span>
        </div>
      </div>

      {/* TWEAKS */}
      <TweaksPanel title="Tweaks">
        <TweakSection label="Atmosphère" />
        <TweakRadio
          label="Thème"
          value={t.theme}
          options={themeOpts}
          onChange={(v) => setTweak("theme", v)}
        />
        <TweakToggle
          label="Bande-son"
          value={audioOn}
          onChange={(v) => { setAudioOn(v); setTweak("audioEnabled", v); }}
        />

        <TweakSection label="Typographie" />
        <TweakRadio
          label="Caractère"
          value={t.font}
          options={[
            { value: "cormorant", label: "Cormorant" },
            { value: "garamond",  label: "Garamond" },
            { value: "spectral",  label: "Spectral" },
          ]}
          onChange={(v) => setTweak("font", v)}
        />
        <TweakSlider
          label="Corps du texte"
          value={t.fontSize} min={14} max={24} step={1} unit="px"
          onChange={(v) => setTweak("fontSize", v)}
        />
        <TweakSlider
          label="Interlignage"
          value={t.lineHeight} min={1.3} max={2.0} step={0.05}
          onChange={(v) => setTweak("lineHeight", v)}
        />
        <TweakSlider
          label="Largeur de page"
          value={t.pageWidth} min={420} max={760} step={10} unit="px"
          onChange={(v) => setTweak("pageWidth", v)}
        />
        <TweakToggle
          label="Justification"
          value={t.justify}
          onChange={(v) => setTweak("justify", v)}
        />

        <TweakSection label="Navigation" />
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }}>
          <TweakButton label="Couverture" onClick={() => { setIdx(0); setPhase("show"); }} />
          <TweakButton label="Sommaire" onClick={() => {
            const i = SCREENS.findIndex(s => s.kind === "toc");
            if (i !== -1) { setIdx(i); setPhase("show"); }
          }} />
        </div>
        <TweakSelect
          label="Aller au chapitre"
          value=""
          options={[{ value: "", label: "—" }, ...CHAPTERS.map(c => ({ value: String(c.n), label: `${ROMAN[c.n]} · ${c.name}` }))]}
          onChange={(v) => v && jumpToChapter(Number(v))}
        />
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<Reader />);
