/* global React */
const { useState, useEffect, useRef } = React;

const SLIDES = [
  {
    t: 0, dur: 6,
    title: "The room before anyone arrived.",
    sub: "Chapter 1 · Setup",
    narr: "I used to get there twenty minutes early, just to sit with it.",
    bg: "linear-gradient(135deg, #1a2140, #2a1d3d 60%, #3f2540)",
  },
  {
    t: 6, dur: 7,
    title: "September, 2019.",
    sub: "Chapter 2 · The first cohort",
    narr: "Fourteen students. I thought I was teaching one course; I was teaching another.",
    bg: "linear-gradient(135deg, #2a2d3d, #50354a 60%, #7b3a5a)",
  },
  {
    t: 13, dur: 8,
    title: "What I thought it was about.",
    sub: "Chapter 3 · The turn",
    narr: "It wasn't about the technology at all. It never had been.",
    bg: "linear-gradient(135deg, #0f2a2e, #1d4a45 60%, #2e7a62)",
  },
  {
    t: 21, dur: 7,
    title: "The note, folded in half.",
    sub: "Chapter 4 · Crisis",
    narr: "A student had left it in my office. I didn't find it until May.",
    bg: "linear-gradient(135deg, #3b1d1d, #6a2d2a 60%, #a64b38)",
  },
  {
    t: 28, dur: 6,
    title: "What it actually said.",
    sub: "Chapter 5 · Resolution",
    narr: "'Thank you for the time you didn't know you were giving me.'",
    bg: "linear-gradient(135deg, #2a2338, #41355a 60%, #6b5a99)",
  },
  {
    t: 34, dur: 6,
    title: "The second time I taught it.",
    sub: "Chapter 6 · Coda",
    narr: "I got there twenty minutes early. But not for the same reason.",
    bg: "linear-gradient(135deg, #1a2430, #34465e 60%, #5f7aa0)",
  },
];

const TOTAL = SLIDES[SLIDES.length - 1].t + SLIDES[SLIDES.length - 1].dur; // 40

function fmt(s) {
  s = Math.max(0, Math.min(TOTAL, s));
  const m = Math.floor(s / 60);
  const ss = Math.floor(s % 60);
  return `${m}:${String(ss).padStart(2, "0")}`;
}

function Player() {
  const [t, setT] = useState(0);
  const [playing, setPlaying] = useState(false);
  const [narrOn, setNarrOn] = useState(true);
  const [lastSpoken, setLastSpoken] = useState(-1);
  const rafRef = useRef(null);
  const lastRef = useRef(0);
  const scrubRef = useRef(null);
  const draggingRef = useRef(false);
  const audiosRef = useRef([]);   // one HTMLAudioElement per slide
  const currentAudioRef = useRef(null);

  // Per-slide narration is a set of pre-rendered ElevenLabs MP3s.
  // Browsers without `audio/mpeg` support fall back to no narration.
  const hasTTS = (() => {
    if (typeof window === "undefined" || typeof Audio === "undefined") return false;
    const probe = document.createElement("audio");
    return !!probe.canPlayType && probe.canPlayType("audio/mpeg") !== "";
  })();

  // Build the per-slide audio elements once. Subscribe to the audio bus so
  // tapping a hero tile cleanly stops the player's narration mid-line.
  useEffect(() => {
    if (!hasTTS) return;
    audiosRef.current = SLIDES.map((_, i) => {
      const a = new Audio(`audio/player-${i + 1}.mp3`);
      a.preload = "auto";
      return a;
    });
    const unsub = window.__audioBus && window.__audioBus.subscribe((id) => {
      if (id !== "player-narr") stopSpeaking();
    });
    return () => {
      audiosRef.current.forEach((a) => { try { a.pause(); } catch (_) {} });
      audiosRef.current = [];
      if (unsub) unsub();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  function speakLine(idx) {
    if (!hasTTS) return;
    stopSpeaking();
    if (window.__audioBus) window.__audioBus.claim("player-narr");
    const a = audiosRef.current[idx];
    if (!a) return;
    try { a.currentTime = 0; } catch (_) {}
    const p = a.play();
    if (p && p.catch) p.catch(() => {});
    currentAudioRef.current = a;
  }
  function stopSpeaking() {
    const a = currentAudioRef.current;
    if (a) { try { a.pause(); } catch (_) {} }
    currentAudioRef.current = null;
  }

  useEffect(() => {
    if (!playing) { stopSpeaking(); return; }
    lastRef.current = performance.now();
    function tick(now) {
      const dt = (now - lastRef.current) / 1000;
      lastRef.current = now;
      setT((prev) => {
        const next = prev + dt;
        if (next >= TOTAL) { setPlaying(false); return TOTAL; }
        return next;
      });
      rafRef.current = requestAnimationFrame(tick);
    }
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [playing]);

  // Speak the current line when it changes during playback.
  useEffect(() => {
    if (!playing || !narrOn || !hasTTS) return;
    const idx = SLIDES.findIndex((s, i) => {
      const end = i === SLIDES.length - 1 ? TOTAL : SLIDES[i + 1].t;
      return t >= s.t && t < end;
    });
    if (idx !== -1 && idx !== lastSpoken) {
      setLastSpoken(idx);
      speakLine(idx);
    }
  }, [t, playing, narrOn]);

  useEffect(() => () => stopSpeaking(), []);

  const curIdx = Math.max(0, SLIDES.findIndex((s, i) => {
    const end = i === SLIDES.length - 1 ? TOTAL : SLIDES[i + 1].t;
    return t >= s.t && t < end;
  }));
  const cur = SLIDES[curIdx === -1 ? SLIDES.length - 1 : curIdx];

  function seekFromEvent(e) {
    const r = scrubRef.current.getBoundingClientRect();
    const x = (e.touches ? e.touches[0].clientX : e.clientX) - r.left;
    const p = Math.max(0, Math.min(1, x / r.width));
    setT(p * TOTAL);
  }
  function onDown(e) { draggingRef.current = true; setPlaying(false); stopSpeaking(); setLastSpoken(-1); seekFromEvent(e); }
  function onMove(e) { if (draggingRef.current) seekFromEvent(e); }
  function onUp() { draggingRef.current = false; }

  useEffect(() => {
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
    window.addEventListener("touchmove", onMove);
    window.addEventListener("touchend", onUp);
    return () => {
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseup", onUp);
      window.removeEventListener("touchmove", onMove);
      window.removeEventListener("touchend", onUp);
    };
  }, []);

  const pct = (t / TOTAL) * 100;

  return (
    <section className="sec" id="gallery-player">
      <div className="wrap">
        <window.SectionHead
          num="—"
          eyebrow="Live example"
          title="A 40-second digital story, scrubbable."
          lede="Drag the playhead to jump between chapters. Each slide shows the visual beat, the narration line, and its track in the edit timeline below — so students can see the relationship between word, image, music, and cut."
        />

        <div className="player">
          <div className="player-stage">
            {SLIDES.map((s, i) => (
              <div
                key={i}
                className={"player-slide" + (i === (curIdx === -1 ? SLIDES.length - 1 : curIdx) ? " on" : "")}
                style={{background: s.bg}}
              >
                <div className="inner">
                  <div style={{fontFamily: "var(--mono)", fontSize: 11, textTransform: "uppercase", letterSpacing: "0.1em", opacity: 0.7, marginBottom: 14}}>{s.sub}</div>
                  <h3>{s.title}</h3>
                </div>
              </div>
            ))}
            <div className="player-cap">
              <div className="player-narr">"{cur.narr}"</div>
              <div>{fmt(t)} / {fmt(TOTAL)}</div>
            </div>
            {playing && narrOn && hasTTS && (
              <div style={{
                position: "absolute", top: 20, right: 24,
                display: "flex", alignItems: "center", gap: 6,
                background: "rgba(0,0,0,0.5)", padding: "6px 10px",
                borderRadius: 20, color: "#fff",
                fontFamily: "var(--mono)", fontSize: 10,
                textTransform: "uppercase", letterSpacing: "0.1em",
                backdropFilter: "blur(8px)",
              }}>
                <span style={{display: "inline-block", width: 6, height: 6, borderRadius: "50%", background: "var(--lime)", boxShadow: "0 0 8px var(--lime)", animation: "pulse 1.4s ease-in-out infinite"}}/>
                <span>Narrating</span>
              </div>
            )}
          </div>

          <div className="player-chrome">
            <div className="player-controls">
              <button className="play-btn" onClick={() => { if (t >= TOTAL) { setT(0); setLastSpoken(-1); } setPlaying((p) => !p); }}>
                {playing ? <window.Ic.pause /> : <window.Ic.play />}
              </button>
              <div className="player-time">{fmt(t)}</div>
              <div
                className="scrub"
                ref={scrubRef}
                onMouseDown={onDown}
                onTouchStart={onDown}
              >
                <div className="scrub-bar" />
                <div className="scrub-fill" style={{width: `${pct}%`}} />
                <div className="scrub-markers">
                  {SLIDES.map((s, i) => (
                    <div key={i} className="scrub-marker" style={{left: `${(s.t / TOTAL) * 100}%`}} />
                  ))}
                </div>
                <div className="scrub-thumb" style={{left: `${pct}%`}} />
              </div>
              <div className="player-time" style={{textAlign: "right"}}>{fmt(TOTAL)}</div>
              <button
                onClick={() => { setNarrOn((v) => { if (v) stopSpeaking(); return !v; }); }}
                title={hasTTS ? (narrOn ? "Narration on" : "Narration off") : "Narration unavailable in this browser"}
                disabled={!hasTTS}
                style={{
                  appearance: "none", border: "1px solid var(--line-2)",
                  background: narrOn && hasTTS ? "var(--accent)" : "var(--panel)",
                  color: narrOn && hasTTS ? "#0b0c0f" : "var(--ink-dim)",
                  padding: "6px 10px", borderRadius: 8, fontSize: 11,
                  fontFamily: "var(--mono)", textTransform: "uppercase", letterSpacing: "0.08em",
                  cursor: hasTTS ? "default" : "not-allowed",
                  opacity: hasTTS ? 1 : 0.5,
                }}
              >
                {hasTTS ? (narrOn ? "VO · on" : "VO · off") : "VO · n/a"}
              </button>
            </div>

            {/* Tracks */}
            {[
              { name: "Visual", kind: "img", clips: SLIDES.map((s, i) => ({ start: s.t, dur: i === SLIDES.length - 1 ? TOTAL - s.t : SLIDES[i+1].t - s.t, lbl: `SHOT ${String(i+1).padStart(2,"0")}` })) },
              { name: "Narr.", kind: "narr", clips: [{start: 1, dur: 38, lbl: "V/O"}] },
              { name: "Music", kind: "music", clips: [{start: 0, dur: 30, lbl: "SCORE"}, {start: 30, dur: 10, lbl: "OUTRO"}] },
              { name: "Titles", kind: "text", clips: [{start: 0, dur: 3, lbl: "TITLE"}, {start: 37, dur: 3, lbl: "CREDITS"}] },
            ].map((tr, i) => (
              <div className="track-row" key={i}>
                <div className="track-name">{tr.name}</div>
                <div className="track">
                  {tr.clips.map((c, j) => (
                    <div
                      key={j}
                      className={"track-clip " + tr.kind}
                      style={{left: `${(c.start / TOTAL) * 100}%`, width: `${(c.dur / TOTAL) * 100}%`}}
                    >
                      <span className="tc-lbl">{c.lbl}</span>
                    </div>
                  ))}
                  <div className="scrub-thumb" style={{left: `${pct}%`, top: "50%"}} />
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

window.Player = Player;
