// app.jsx — Mat.ai UI (revised: quiet host, scene-image swap, top-right icons,
// scroll-zoom on canvas, no numbered choices, no waveforms.)

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

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "environment": "room",
  "character": "self",
  "audioEnabled": true,
  "volume": 70,
  "typeSpeed": 26,
  "cameraMode": "drift"
}/*EDITMODE-END*/;

// ── Typewriter ─────────────────────────────────────────
function useTypewriter(text, speed) {
  const [out, setOut] = useState("");
  const [done, setDone] = useState(false);
  useEffect(() => {
    setOut(""); setDone(false);
    if (!text) { setDone(true); return; }
    let i = 0;
    const id = setInterval(() => {
      i++;
      setOut(text.slice(0, i));
      if (i >= text.length) { clearInterval(id); setDone(true); }
    }, Math.max(8, 1000 / speed));
    return () => clearInterval(id);
  }, [text, speed]);
  return { out, done, skip: () => { setOut(text); setDone(true); } };
}

// ── Splash ─────────────────────────────────────────────
function Splash({ ready, onBegin }) {
  const [hidden, setHidden] = useState(false);
  if (hidden) return null;
  return (
    <div id="splash" className={hidden ? "hidden" : ""}>
      <div className="mark" />
      <h1 className="title">Mat.ai</h1>
      <div className="sub">a conversation</div>
      <button className="begin" disabled={!ready} onClick={() => {
        setHidden(true);
        setTimeout(() => onBegin && onBegin(), 200);
      }}>enter</button>
    </div>
  );
}

// ── Icons ──────────────────────────────────────────────
const IconSettings = () => (
  <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.6 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9 1.65 1.65 0 0 0 4.27 7.18l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9c.36.16.68.4.92.69"/></svg>
);
const IconCast = () => (
  <svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4 21v-1a8 8 0 0 1 16 0v1"/></svg>
);
const IconGames = () => (
  <svg viewBox="0 0 24 24"><rect x="3" y="7" width="18" height="12" rx="2"/><path d="M8 12h2M9 11v2M15 12h.01M17 14h.01"/></svg>
);

// ── Settings panel (top-right) ─────────────────────────
function SettingsPanel({ open, t, setTweak, environments, characters }) {
  return (
    <div id="settings" className={open ? "open" : ""}>
      <h3>environment</h3>
      <div className="setting-row">
        <select value={t.environment} onChange={(e) => setTweak("environment", e.target.value)}>
          {environments.map((env) => (
            <option key={env.id} value={env.id}>{env.name}</option>
          ))}
        </select>
      </div>

      <h3 style={{ marginTop: 18 }}>voice</h3>
      <div className="setting-row">
        <div className="label"><span>audio</span>
          <span className="val">{t.audioEnabled ? "on" : "off"}</span></div>
        <input type="range" min="0" max="1" step="1" value={t.audioEnabled ? 1 : 0}
               onChange={(e) => setTweak("audioEnabled", e.target.value === "1")} />
      </div>
      <div className="setting-row">
        <div className="label"><span>volume</span><span className="val">{t.volume}</span></div>
        <input type="range" min="0" max="100" step="1" value={t.volume}
               onChange={(e) => setTweak("volume", Number(e.target.value))} />
      </div>
      <div className="setting-row">
        <div className="label"><span>cadence</span><span className="val">{t.typeSpeed}</span></div>
        <input type="range" min="10" max="60" step="1" value={t.typeSpeed}
               onChange={(e) => setTweak("typeSpeed", Number(e.target.value))} />
      </div>

      <h3 style={{ marginTop: 18 }}>camera</h3>
      <div className="setting-row">
        <select value={t.cameraMode} onChange={(e) => setTweak("cameraMode", e.target.value)}>
          <option value="drift">drift (drag to look)</option>
          <option value="locked">locked</option>
        </select>
      </div>
    </div>
  );
}

// ── Cast panel (top-right alt) ─────────────────────────
function CastPanel({ open, characters, currentId, onPick }) {
  return (
    <div id="settings" className={open ? "open" : ""}>
      <h3>the cast</h3>
      {characters.map((c) => (
        <div key={c.id}
             className={`char-row ${c.id === currentId ? "active" : ""} ${c.locked ? "locked" : ""}`}
             onClick={() => !c.locked && onPick(c.id)}>
          <div className="portrait-mini">{c.name[0]}</div>
          <div className="meta">
            <div className="char-name">{c.name}</div>
            <div className="char-title">{c.locked ? "soon" : c.title}</div>
          </div>
        </div>
      ))}
    </div>
  );
}

// ── Game launcher panel ────────────────────────────────
function GamesPanel({ open, onLaunch }) {
  return (
    <div id="settings" className={open ? "open" : ""}>
      <h3>diversions</h3>
      <div className="char-row" onClick={() => onLaunch("basketball")}>
        <div className="portrait-mini">·</div>
        <div className="meta">
          <div className="char-name">Free throws</div>
          <div className="char-title">aim · charge · shoot</div>
        </div>
      </div>
      <div className="char-row" onClick={() => onLaunch("chase")}>
        <div className="portrait-mini">·</div>
        <div className="meta">
          <div className="char-name">The chase</div>
          <div className="char-title">gather · dodge · run</div>
        </div>
      </div>
    </div>
  );
}

// ── Game runtime ───────────────────────────────────────
function GameLayer({ activeGame, onExit }) {
  const hudRef = useRef(null);
  useEffect(() => {
    if (!activeGame || !window.GAMES || !window.SCENE) return;
    const fn = window.GAMES[activeGame];
    if (!fn) return;
    const renderer = window.SCENE.state.renderer;
    window.SCENE.pauseRender();
    const inst = fn({
      renderer,
      hud: hudRef.current,
      onScore: () => {},
      onExit: () => {
        window.SCENE.resumeRender();
        onExit && onExit();
      },
    });
    return () => { inst && inst.stop && inst.stop("exit"); };
  }, [activeGame]);

  if (!activeGame) return null;
  return <div id="game-hud-wrap" ref={hudRef} />;
}
// Two layered divs cross-fade as the scene changes.
function SceneImage({ src }) {
  const [layers, setLayers] = useState([{ src: null, key: 0 }, { src: null, key: 1 }]);
  const [activeIdx, setActiveIdx] = useState(0);

  useEffect(() => {
    if (!src) return;
    const next = activeIdx === 0 ? 1 : 0;
    setLayers((ls) => {
      const copy = ls.slice();
      copy[next] = { src, key: copy[next].key + 2 };
      return copy;
    });
    // wait a frame, then activate
    requestAnimationFrame(() => requestAnimationFrame(() => setActiveIdx(next)));
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [src]);

  return (
    <>
      <div id="scene-image"
           className={activeIdx === 0 && layers[0].src ? "visible" : ""}
           style={layers[0].src ? { backgroundImage: `url("${layers[0].src}")` } : {}} />
      <div id="scene-image-next"
           className={activeIdx === 1 && layers[1].src ? "visible" : ""}
           style={layers[1].src ? { backgroundImage: `url("${layers[1].src}")` } : {}} />
    </>
  );
}

// ── Dialogue ───────────────────────────────────────────
function Dialogue({ node, typeSpeed, onChoose }) {
  const { out, done, skip } = useTypewriter(node?.line || "", typeSpeed);
  const audioRef = useRef(null);

  useEffect(() => {
    if (!node) return;
    if (window.SCENE && node.animation) window.SCENE.playAnimation(node.animation);
    if (node.audio && audioRef.current) {
      audioRef.current.src = node.audio;
      audioRef.current.play().catch(() => {});
    }
  }, [node]);

  // Space skips. No number keys (we removed numbered choices).
  useEffect(() => {
    const onKey = (e) => {
      if (e.code === "Space" && !done) { e.preventDefault(); skip(); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [done, skip]);

  if (!node) return null;

  return (
    <div id="dialogue">
      <audio ref={audioRef} preload="auto" />
      <div id="dialogue-inner">
        <div className="host-line" onClick={() => !done && skip()}>
          <span className="text">{out}</span>
          {!done && <span className="caret" />}
        </div>
        <div className={`choices ${done ? "" : "locked"}`} key={node.line /* re-trigger animation */}>
          <div className="heading">your reply</div>
          {node.choices?.map((c, i) => (
            <button key={i} className="choice" onClick={() => done && onChoose(c)}>
              {c.label}
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

// ── Caption ────────────────────────────────────────────
function Caption({ text, visible }) {
  return (
    <div id="caption" className={visible ? "visible" : ""}>{text}</div>
  );
}

// ── App ────────────────────────────────────────────────
function App() {
  const [t, setTweakRaw] = useTweaks(TWEAK_DEFAULTS);
  const setTweak = setTweakRaw;
  const [splashGone, setSplashGone] = useState(false);
  const [sceneReady, setSceneReady] = useState(false);
  const [nodeId, setNodeId] = useState(null);
  const [panel, setPanel] = useState(null); // "settings" | "cast" | "games" | null
  const [activeGame, setActiveGame] = useState(null);

  const characters = window.CHARACTERS || [];
  const environments = window.ENVIRONMENTS || [];
  const dialogue = window.DIALOGUE;

  const character = characters.find((c) => c.id === t.character) || characters[0];
  const environment = environments.find((e) => e.id === t.environment) || environments[0];
  const node = nodeId ? dialogue?.nodes[nodeId] : null;

  // Init scene
  useEffect(() => {
    const canvas = document.getElementById("three-canvas");
    if (!canvas || !window.SCENE) return;
    window.SCENE.init(canvas);
    setSceneReady(true);
  }, []);

  // Bind interactive hover/click → jump dialogue to topic
  useEffect(() => {
    if (!sceneReady) return;
    window.SCENE.bindInteractions({
      onTopic: (topicId) => {
        if (topicId && dialogue?.nodes[topicId]) setNodeId(topicId);
      },
    });
  }, [sceneReady, dialogue]);

  useEffect(() => {
    if (!sceneReady || !character) return;
    window.SCENE.loadCharacter(character);
  }, [sceneReady, character?.id]);

  useEffect(() => {
    if (!sceneReady) return;
    window.SCENE.setAmbient(environment);
    window.SCENE.setRoom(environment?.id);
  }, [sceneReady, environment?.id]);

  useEffect(() => {
    if (!sceneReady) return;
    window.SCENE.setCameraMode(t.cameraMode);
  }, [sceneReady, t.cameraMode]);

  // Audio volume
  useEffect(() => {
    document.querySelectorAll("audio").forEach((a) => { a.volume = t.volume / 100; });
  }, [t.volume, nodeId]);

  const onBegin = useCallback(() => {
    setSplashGone(true);
    setTimeout(() => setNodeId(dialogue?.start || "root"), 600);
  }, [dialogue]);

  const onChoose = useCallback((c) => { if (c) setNodeId(c.target); }, []);

  // Close panels when clicking outside
  useEffect(() => {
    if (!panel) return;
    const onDoc = (e) => {
      if (!e.target.closest("#settings") && !e.target.closest("#topbar")) {
        setPanel(null);
      }
    };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [panel]);

  return (
    <>
      <SceneImage src={node?.scene?.image || null} />
      <Caption text={node?.scene?.caption || ""} visible={!!(node && splashGone)} />

      <div id="topbar">
        <button className="icon-btn" aria-label="games"
                onClick={() => setPanel(panel === "games" ? null : "games")}>
          <IconGames />
        </button>
        <button className="icon-btn" aria-label="cast"
                onClick={() => setPanel(panel === "cast" ? null : "cast")}>
          <IconCast />
        </button>
        <button className="icon-btn" aria-label="settings"
                onClick={() => setPanel(panel === "settings" ? null : "settings")}>
          <IconSettings />
        </button>
      </div>

      {panel === "games" && (
        <GamesPanel open={true}
                    onLaunch={(id) => { setActiveGame(id); setPanel(null); }} />
      )}

      {panel === "settings" && (
        <SettingsPanel open={true} t={t} setTweak={setTweak}
                       environments={environments} characters={characters} />
      )}
      {panel === "cast" && (
        <CastPanel open={true} characters={characters} currentId={t.character}
                   onPick={(id) => { setTweak("character", id); setPanel(null); }} />
      )}

      {splashGone && node && (
        <Dialogue node={node} typeSpeed={t.typeSpeed} onChoose={onChoose} />
      )}

      <Splash ready={sceneReady} onBegin={onBegin} />
      <GameLayer activeGame={activeGame} onExit={() => setActiveGame(null)} />
    </>
  );
}

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