/*  === monstrance-app.jsx ===
    Top-level modals + App routing + ReactDOM mount.
    Loaded as <script type="text/babel" src="js/monstrance-app.jsx"></script>.
    Cross-file references work because babel-standalone evaluates each script in
    the document's global scope after compile; top-level const/let/function decls
    are visible to subsequent scripts loaded in this order.
*/

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

function PartsModal({ onClose, speak, stopSpeech }) {
  const [i, setI] = useState(0);
  const part = PARTS[i];
  const partAudio = audioForPart(part);
  const partSpeech = narrationForPart(part, { includeVerse: true });

  useEffect(() => {
    speak(partSpeech, { force: true, audioSrc: partAudio });
  }, [i]);

  const next = () => setI((v) => Math.min(v + 1, PARTS.length - 1));
  const prev = () => setI((v) => Math.max(v - 1, 0));

  useEffect(() => {
    const h = (e) => {
      if (e.key === "ArrowRight") next();
      if (e.key === "ArrowLeft") prev();
      if (e.key === "Escape") onClose();
    };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, []);

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <button
          className="modal-close"
          onClick={onClose}
          aria-label="Close"
        >
          ×
        </button>
        <div className="modal-header">
          <div className="modal-eyebrow">
            part {part.id} of 5 · {part.hint.toLowerCase()}
          </div>
          <h2 className="modal-title">{part.name}</h2>
        </div>
        <div className="modal-body">
          <div className="part-card">
            <div className="part-thumb">
              <PartThumb id={part.id} />
              <div className="part-num">{part.id}</div>
            </div>
            <div>
              {PART_SCENE_SRC[part.id] && (
                <img
                  className="part-scene"
                  src={PART_SCENE_SRC[part.id]}
                  alt=""
                  onError={(e) => {
                    e.currentTarget.style.display = "none";
                  }}
                />
              )}
              <div className="part-words">{part.short}</div>
              <div className="part-words" style={{ marginBottom: 14 }}>
                {part.long}
              </div>
              <div className="part-verse">{part.verse}</div>
              <div className="tts-row">
                <button
                  className="btn btn-secondary"
                  onClick={(event) =>
                    speak(partSpeech, {
                      force: true,
                      audioSrc: partAudio,
                      failureTarget: event.currentTarget.parentElement,
                    })
                  }
                >
                  <SpeakerIcon on={true} /> Listen
                </button>
                <button className="btn btn-secondary" onClick={stopSpeech}>
                  Stop
                </button>
                <span className="tts-note">
                  Plays the Grok MP3 for this part.
                </span>
              </div>
            </div>
          </div>

          <div className="stepper-controls">
            <button
              className="btn btn-secondary"
              onClick={prev}
              disabled={i === 0}
              style={{ opacity: i === 0 ? 0.4 : 1 }}
            >
              ← Back
            </button>
            <div className="dots">
              {PARTS.map((_, idx) => (
                <button
                  key={idx}
                  className={`dot ${idx === i ? "is-on" : ""}`}
                  onClick={() => setI(idx)}
                  aria-label={`Go to part ${idx + 1}`}
                />
              ))}
            </div>
            <button
              className="btn btn-primary"
              onClick={i === PARTS.length - 1 ? onClose : next}
            >
              {i === PARTS.length - 1 ? "Finish ✦" : "Next →"}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

function PrayerModal({ onClose, speak, stopSpeech }) {
  const [selectedId, setSelectedId] = useState(null);
  const [step, setStep] = useState(0);
  const prayer = selectedId ? PRAYERS.find((p) => p.id === selectedId) : null;
  const current = prayer ? prayer.steps[step] : null;
  const currentAudio = prayer ? audioForPrayerStep(prayer, step) : "";
  const currentSpeech = current ? narrationForPrayerStep(current) : "";

  useEffect(() => {
    if (current) speak(currentSpeech, { force: true, audioSrc: currentAudio });
  }, [step, selectedId]);

  const next = () =>
    setStep((v) => (prayer && v + 1 < prayer.steps.length ? v + 1 : v));
  const prev = () => setStep((v) => Math.max(v - 1, 0));

  useEffect(() => {
    const h = (e) => {
      if (e.key === "Escape") onClose();
      if (!prayer) return;
      if (e.key === "ArrowRight") next();
      if (e.key === "ArrowLeft") prev();
    };
    window.addEventListener("keydown", h);
    return () => window.removeEventListener("keydown", h);
  }, [prayer]);

  const choosePrayer = (id) => {
    setSelectedId(id);
    setStep(0);
  };
  const backToPicker = () => {
    setSelectedId(null);
    setStep(0);
    stopSpeech();
  };

  if (!prayer) {
    return (
      <div className="modal-scrim" onClick={onClose}>
        <div className="modal" onClick={(e) => e.stopPropagation()}>
          <button
            className="modal-close"
            onClick={onClose}
            aria-label="Close"
          >
            ×
          </button>
          <div className="modal-header">
            <div className="modal-eyebrow">pick a prayer to pray together</div>
            <h2 className="modal-title">Simple Prayers</h2>
          </div>
          <div className="modal-body">
            <div className="prayer-picker">
              {PRAYERS.map((p) => (
                <button
                  key={p.id}
                  className="prayer-card"
                  onClick={() => choosePrayer(p.id)}
                >
                  <div className="prayer-card-icon">
                    <span aria-hidden="true">{p.icon}</span>
                    {PRAYER_IMG_SRC[p.id] && (
                      <img
                        src={PRAYER_IMG_SRC[p.id]}
                        alt=""
                        onError={(e) => {
                          e.currentTarget.style.display = "none";
                        }}
                      />
                    )}
                  </div>
                  <div className="prayer-card-name">{p.name}</div>
                  <div className="prayer-card-meta">
                    {p.steps.length} small steps
                  </div>
                </button>
              ))}
            </div>
          </div>
        </div>
      </div>
    );
  }

  const done = step === prayer.steps.length - 1;

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <button
          className="modal-close"
          onClick={onClose}
          aria-label="Close"
        >
          ×
        </button>
        <div className="modal-header" style={{ paddingBottom: 0 }}>
          <div className="modal-eyebrow">{prayer.eyebrow}</div>
          <h2 className="modal-title">{prayer.name}</h2>
        </div>
        <div className="modal-body">
          <div className="prayer" key={selectedId + ":" + step}>
            <div className="prayer-icon" style={{ fontSize: 56 }}>
              {current.icon}
            </div>
            <h3 className="prayer-step">"{current.say}"</h3>
            <div className="prayer-instruction">{current.do}</div>
            <div
              className="prayer-progress"
              aria-label={`Step ${step + 1} of ${prayer.steps.length}`}
            >
              {prayer.steps.map((_, i) => (
                <span key={i} className={i <= step ? "is-on" : ""} />
              ))}
            </div>
            <div className="tts-row" style={{ justifyContent: "center" }}>
                <button
                  className="btn btn-secondary"
                  onClick={(event) =>
                    speak(currentSpeech, {
                      force: true,
                      audioSrc: currentAudio,
                      failureTarget: event.currentTarget.parentElement,
                    })
                  }
                >
                <SpeakerIcon on={true} /> Listen
              </button>
              <button className="btn btn-secondary" onClick={stopSpeech}>
                Stop
              </button>
              <span className="tts-note">
                Plays the Grok MP3 for this prayer step.
              </span>
            </div>
          </div>

          <div className="stepper-controls" style={{ marginTop: 26 }}>
            <button
              className="btn btn-secondary"
              onClick={prev}
              disabled={step === 0}
              style={{ opacity: step === 0 ? 0.4 : 1 }}
            >
              ← Back
            </button>
            <div className="dots">
              {prayer.steps.map((_, idx) => (
                <button
                  key={idx}
                  className={`dot ${idx === step ? "is-on" : ""}`}
                  onClick={() => setStep(idx)}
                  aria-label={`Go to step ${idx + 1}`}
                />
              ))}
            </div>
            <button
              className="btn btn-primary"
              onClick={done ? backToPicker : next}
            >
              {done ? "Amen ✦" : "Next →"}
            </button>
          </div>

          <button className="prayer-switch" onClick={backToPicker}>
            ← Choose another prayer
          </button>
        </div>
      </div>
    </div>
  );
}

function App() {
  const [route, setRoute] = useState(() =>
    routeFromHash(window.location.hash),
  );
  useEffect(() => {
    const syncRoute = () => setRoute(routeFromHash(window.location.hash));
    window.addEventListener("hashchange", syncRoute);
    return () => window.removeEventListener("hashchange", syncRoute);
  }, []);
  useEffect(() => {
    const isSubpage = route.name === "stop" || route.name === "faith" || route.name === "games";
    document.body.dataset.routeSurface = isSubpage ? "subpage" : "home";
    if (isSubpage) {
      window.scrollTo({ top: 0, left: 0, behavior: "auto" });
    } else if (window.location.hash === "#journey") {
      requestAnimationFrame(() =>
        document.getElementById("journey")?.scrollIntoView({ block: "start" }),
      );
    } else if (window.location.hash === "#top") {
      window.scrollTo({ top: 0, left: 0, behavior: "auto" });
    }
  }, [route.name, route.slug]);

  const [palette, setPalette] = useState(() => {
    try {
      return localStorage.getItem("monstrance-palette") || "golden";
    } catch (e) {
      return "golden";
    }
  });
  useEffect(() => {
    document.documentElement.dataset.palette = palette;
    try {
      localStorage.setItem("monstrance-palette", palette);
    } catch (e) {}
  }, [palette]);

  const cyclePalette = () => {
    const idx = PALETTES.findIndex((p) => p.value === palette);
    setPalette(PALETTES[(idx + 1) % PALETTES.length].value);
  };

  const [activePart, setActivePart] = useState(null);
  const [visited, setVisited] = useState(new Set());
  const [modal, setModal] = useState(null);
  const [readAloud, setReadAloud] = useState(false);
  const [caption, setCaption] = useState("");
  const [audioSetupNeeded, setAudioSetupNeeded] = useState(false);
  const [audioPlaying, setAudioPlaying] = useState(false);
  const audioRef = useRef(null);

  const speak = useCallback(
    (text, options = {}) => {
      const force = Boolean(options.force);
      if (
        (!readAloud && !force) ||
        typeof window === "undefined" ||
        !options.audioSrc
      )
        return;
      const markAudioFailed = (error) => {
        const target =
          options.failureTarget instanceof HTMLElement
            ? options.failureTarget
            : document.body;
        target?.setAttribute("data-audio-failed", options.audioSrc);
        console.warn("Grok audio failed", options.audioSrc, error);
      };
      try {
        if (audioRef.current) {
          audioRef.current.pause();
          audioRef.current.currentTime = 0;
        }
        const audio = new Audio(options.audioSrc);
        audioRef.current = audio;
        setCaption(text);
        setAudioPlaying(true);
        audio.onended = () => {
          setAudioPlaying(false);
          setCaption("");
        };
        audio.onerror = (event) => {
          markAudioFailed(event);
          setAudioPlaying(false);
          setCaption("");
        };
        audio.play().catch((error) => {
          markAudioFailed(error);
          setAudioPlaying(false);
          setCaption("Tap Listen again to play Grok audio.");
        });
      } catch (e) {
        markAudioFailed(e);
        setAudioPlaying(false);
        setCaption("Grok audio could not be played.");
      }
    },
    [readAloud],
  );

  const stopSpeech = useCallback(() => {
    if (audioRef.current) {
      audioRef.current.pause();
      audioRef.current.currentTime = 0;
    }
    setAudioPlaying(false);
    setCaption("");
  }, []);

  const handleHotspot = (id) => {
    setActivePart(id);
    setVisited((prev) => new Set([...prev, id]));
    const part = PARTS.find((p) => p.id === id);
    if (part) {
      speak(narrationForPart(part), {
        force: true,
        audioSrc: audioForPart(part),
      });
    }
  };

  const toggleReadAloud = () => {
    const next = !readAloud;
    setReadAloud(next);
    if (!next) {
      stopSpeech();
    } else {
      speak("Read along is on. Tap a gold circle to learn.", {
        force: true,
        audioSrc: GROK_AUDIO.readAlong,
      });
    }
  };

  useEffect(
    () => () => {
      stopSpeech();
    },
    [stopSpeech],
  );

  const stopPart =
    route.name === "stop"
      ? PARTS.find((part) => part.slug === route.slug)
      : null;
  const faithStop =
    route.name === "faith"
      ? FAITH_STOPS.find((stop) => stop.slug === route.slug)
      : null;

  if (stopPart) {
    return (
      <div className="app">
        <StopPage
          part={stopPart}
          speak={speak}
          stopSpeech={stopSpeech}
          onShowAudioSetup={() => setAudioSetupNeeded((v) => !v)}
          palette={palette}
          onCyclePalette={cyclePalette}
        />
        {caption && (
          <div className="caption" aria-live="polite">
            {audioPlaying ? "Playing Grok audio: " : ""}
            {caption}
          </div>
        )}
        {audioSetupNeeded && (
          <div className="audio-setup" role="status">
            Grok MP3 files are missing. Add the xAI key to Keychain, then
            run <code>{GROK_AUDIO.setupCommand}</code> from this project.
            Expected manifest: <code>{GROK_AUDIO.manifest}</code>.
          </div>
        )}
      </div>
    );
  }

  if (faithStop) {
    return (
      <div className="app">
        <FaithStopPage
          stop={faithStop}
          palette={palette}
          onCyclePalette={cyclePalette}
          speak={speak}
          stopSpeech={stopSpeech}
          onShowAudioSetup={() => setAudioSetupNeeded((v) => !v)}
        />
        {caption && (
          <div className="caption" aria-live="polite">
            {audioPlaying ? "Playing: " : ""}
            {caption}
          </div>
        )}
        {audioSetupNeeded && (
          <div className="audio-setup" role="status">
            Audio is missing for this stop. Generate with{" "}
            <code>scripts/generate-grok-tts.py</code> or the
            faith-narration shell script.
          </div>
        )}
      </div>
    );
  }

  if (route.name === "games") {
    return (
      <div className="app">
        <GamesPage
          palette={palette}
          onCyclePalette={cyclePalette}
          initialGame={route.game}
        />
      </div>
    );
  }

  return (
    <div className="app">
      <div className="sky-scene">
        <CloudComposition showSparkles={true} />

        <div className="page-frame">
          <TopBar
            readAloud={readAloud}
            onToggleReadAloud={toggleReadAloud}
            onShowAudioSetup={() => setAudioSetupNeeded((v) => !v)}
            palette={palette}
            onCyclePalette={cyclePalette}
          />

          <main className="hero-center">
            <div className="title-block">
              <span className="kicker">
                <span className="spark">✦</span>
                Catholic kids explainer
              </span>
              <h1 className="title">
                What Is a{" "}
                <span className="squiggle-wrap">
                  Monstrance?
                  <svg
                    className="squiggle"
                    viewBox="0 0 200 18"
                    preserveAspectRatio="none"
                    aria-hidden="true"
                  >
                    <path
                      d="M2 12 C 30 2, 60 18, 90 8 S 150 18, 198 6"
                      fill="none"
                      stroke="currentColor"
                      strokeWidth="4"
                      strokeLinecap="round"
                    />
                  </svg>
                </span>
              </h1>
              <div className="tip">
                <span className="tip-emoji">☝️</span>
                <span>
                  Tap a <strong>gold circle</strong> to learn what that
                  part means.
                </span>
              </div>
            </div>

            <Stage
              activePart={activePart}
              visited={visited}
              onHotspot={handleHotspot}
              onClose={() => setActivePart(null)}
            />

            <div className="cta-row">
              <button
                className="btn btn-primary"
                onClick={() => setModal("parts")}
              >
                <BookIcon /> Learn the Parts
              </button>
              <button
                className="btn btn-secondary"
                onClick={() => setModal("prayer")}
              >
                <HandsIcon /> Simple Prayer
              </button>
              <a className="btn btn-secondary" href="#/games">
                ✺ Play the Games
              </a>
            </div>
            <nav className="stop-links" aria-label="Monstrance stop pages">
              {PARTS.map((part) => (
                <a
                  key={part.id}
                  className="stop-link"
                  href={stopPathForPart(part)}
                >
                  <span>{part.id}</span>
                  {part.name}
                </a>
              ))}
            </nav>
          </main>

          <aside
            className="lede-card"
            onClick={(event) =>
              speak(
                INTRO_NARRATION,
                {
                  force: true,
                  audioSrc: INTRO_AUDIO,
                  failureTarget: event.currentTarget,
                },
              )
            }
          >
            <div className="lede-eyebrow">what is it?</div>
            <p>
              A monstrance is a beautiful golden vessel used in church to{" "}
              <em>show</em> the Eucharist during Adoration. Catholics
              believe Jesus is truly present in the Host, so the
              monstrance helps everyone look, pray, and spend quiet time
              with Him.
            </p>
          </aside>

          <div
            className="foot"
            aria-label={`${visited.size} of 5 parts found`}
          >
            <span className="pips">
              {[1, 2, 3, 4, 5].map((n) => (
                <span
                  key={n}
                  className={`pip ${visited.has(n) ? "on" : ""}`}
                />
              ))}
            </span>
            <span>{visited.size}/5 parts found</span>
          </div>
        </div>
      </div>

      {modal === "parts" && (
        <PartsModal
          onClose={() => {
            stopSpeech();
            setModal(null);
          }}
          speak={speak}
          stopSpeech={stopSpeech}
        />
      )}
      {modal === "prayer" && (
        <PrayerModal
          onClose={() => {
            stopSpeech();
            setModal(null);
          }}
          speak={speak}
          stopSpeech={stopSpeech}
        />
      )}

      {caption && (
        <div className="caption" aria-live="polite">
          {audioPlaying ? "Playing Grok audio: " : ""}
          {caption}
        </div>
      )}
      {audioSetupNeeded && (
        <div className="audio-setup" role="status">
          Grok MP3 files are missing. Add the xAI key to Keychain, then
          run <code>{GROK_AUDIO.setupCommand}</code> from this project.
          Expected manifest: <code>{GROK_AUDIO.manifest}</code>.
        </div>
      )}
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
