/* ============================================================
   onboarding.jsx — first-run conversational onboarding for Ebb.
   The conversational FLOW (beats, breathing practice, handoff,
   interstitial) is recreated from "The Ebb". The UI styling is
   this prototype's: warm daytime sky, the shared input dock +
   iOS keyboard, sans-serif Ebb voice, white reply pills.
   ============================================================ */
const {
  useState: useStateOB, useRef: useRefOB, useEffect: useEffectOB,
  useCallback: useCB, useLayoutEffect: useLE
} = React;

/* breathing timing (seconds) — matches the CSS keyframes (14s loop) */
const OB_BREATH = { inhale: 4, hold: 4, exhale: 6, total: 14, target: 4 };

/* ---- Ebb's scripted conversation. Warm, unhurried, second-person. ---- */
const OB_BEATS = [
{
  id: "wake",
  ebb: [
  "Hey. I'm glad you're here.",
  "Nothing to fix, nothing to figure out yet. Just — what's going on for you today?"],

  chips: ["I've been stressed at work", "I can't switch off", "Honestly, not sure", "I'm really struggling right now"],
  placeholder: "Say whatever's true right now…"
},
{
  id: "reflect-stress",
  ebb: [
  "Work has a way of not clocking out when you do, doesn't it. It follows you home and pulls up a chair.",
  "When you picture this week, where do you feel it most?"],

  chips: ["My head won't stop", "My chest feels tight", "Everywhere, honestly"],
  placeholder: "Tell me where it lands…"
},
{
  id: "reflect-where",
  ebb: [
  "A mind that won't quiet down is its own kind of exhausting. Forty tabs open, and you can't find the one playing the music.",
  "Is it keeping you up at night, or more of a daytime hum?"],

  chips: ["It keeps me up", "It's there all day", "Both, lately"],
  placeholder: "Whatever's closer to true…"
},
{
  id: "offer",
  kind: "offer",
  ebb: [
  "That tracks. When the day never really ends, the night never really starts.",
  "I don't think you need one more thing on the list right now. I think you need somewhere to set it all down for a minute.",
  "So I made you something — a short grounding practice. Nothing to download, no playlist to pick. Just here, with me. Want to try it?"],

  chips: ["Let's do it", "What is it, exactly?", "Maybe in a minute"],
  placeholder: "…",
  responses: {
    "What is it, exactly?": {
      ebb: [
      "Nothing mystical. You'll follow a slow circle — breathe in as it grows, out as it shrinks. I'll count with you.",
      "Four breaths. You can stop the second you want to. Shall we?"],

      chips: ["Okay, let's try", "Still not sure"]
    },
    "Still not sure": {
      ebb: [
      "That's allowed. We can just sit here a moment — no practice, no pressure.",
      "Or we can try one slow breath together and see how it feels. Your call."],

      chips: ["One breath, then", "Let's do it"]
    },
    "Maybe in a minute": {
      ebb: [
      "No rush at all. It'll keep.",
      "When you're ready, just say the word."],

      chips: ["Okay, I'm ready", "Let's do it"]
    }
  },
  affirmatives: ["Let's do it", "Okay, let's try", "One breath, then", "Okay, I'm ready"]
},
{
  id: "session",
  kind: "session",
  ebb: ["Okay. Let's set it down. Get comfortable — I'll lead."]
},
{
  id: "return",
  ebb: [
  "Welcome back.",
  "How do you feel now, compared to when we started?"],

  chips: ["A bit lighter", "Calmer than I was", "Still pretty wound up"],
  placeholder: "However it actually is…"
},
{
  id: "close",
  kind: "close",
  closeVariants: {
    lighter: [
    "Even a little lighter counts. That's your nervous system remembering it's allowed to downshift.",
    "I'll hold onto this — that your head races most at night. Next time we can start there, before it builds.",
    "Get some rest when you can. I'm right here whenever you need me."],

    calmer: [
    "I can hear it. Your sentences got a little longer, a little slower.",
    "I'll remember that the nights are the hard part for you. We can meet it earlier next time.",
    "Be gentle with the rest of your day. I'm not going anywhere."],

    wound: [
    "That's honest, and it's okay. A few breaths doesn't undo a hard week — it just reminds you you're allowed to pause.",
    "I'll remember the work stress is sitting heavy right now. We'll keep chipping at it, no pressure.",
    "You came here and you tried. That counts more than you'd think. I'm here whenever the noise gets loud."]

  }
},
{
  id: "anything",
  kind: "compose",
  ebb: ["Anything else?"],
  placeholder: "Whatever's still there\u2026"
},
{
  id: "final",
  kind: "final",
  ebb: [
  "I'm really glad you told me. That stays with me.",
  "Whenever you're ready, your space is waiting."]

},
{
  id: "crisis",
  kind: "crisis",
  ebb: [
  "I'm really glad you told me that. Thank you for trusting me with it.",
  "This matters more than anything I can offer on my own — so here are people who are trained for exactly this, ready whenever you are."],

  ebbAfter: [
  "You don't have to carry this alone. I'll still be right here when you're ready."]

}];


/* Highest-acuity replies route straight to crisis support, not the practice. */
function obIsCrisis(reply) {
  return /strugg|crisis|harm|hurt myself|hurting myself|suicid|kill myself|end it|can't go on|cant go on|don'?t want to (be here|live)|not safe|unsafe|hopeless|emergency|give up|self[- ]?harm/i.test(reply || "");
}

function obPickCloseVariant(reply) {
  const r = (reply || "").toLowerCase();
  if (/wound|still|tense|stress|anx|not|bad|worse|hard/.test(r)) return "wound";
  if (/calm|better|good|ok|fine|settl|slow|relax/.test(r)) return "calmer";
  return "lighter";
}

const obSleep = (ms) => new Promise((r) => setTimeout(r, ms));
const obTypeDelay = (t) => Math.min(1600, Math.max(850, (t ? t.length : 20) * 21));

/* Blake's free-typed reply on the closing "Anything else?" beat (auto-typed on tap). */
const OB_ANYTHING_SCRIPT = "Actually \u2014 thank you. This helped more than I expected.";

/* ---- conversational engine: thread + timed "speak" loop ---- */
function useObEngine() {
  const [messages, setMessages] = useStateOB([]);
  const [typing, setTyping] = useStateOB(false);
  const keyRef = useRefOB(0);
  const mounted = useRefOB(true);

  useEffectOB(() => () => {mounted.current = false;}, []);

  const pushEbb = useCB((text) => setMessages((m) => [...m, { key: "ob" + keyRef.current++, from: "ebb", text }]), []);
  const pushMe = useCB((text) => setMessages((m) => [...m, { key: "ob" + keyRef.current++, from: "me", text }]), []);
  const pushNode = useCB((node) => setMessages((m) => [...m, { key: "ob" + keyRef.current++, node }]), []);

  const speak = useCB(async (lines) => {
    await obSleep(500);
    for (const line of lines) {
      if (!mounted.current) return;
      setTyping(true);
      await obSleep(obTypeDelay(line));
      if (!mounted.current) return;
      setTyping(false);
      pushEbb(line);
      await obSleep(340);
    }
  }, [pushEbb]);

  return { messages, typing, mounted, pushEbb, pushMe, pushNode, speak };
}

/* ============================================================
   Ambient sound — a soft generated wash (no asset needed)
   ============================================================ */
function useObSound() {
  const ctxRef = useRefOB(null);
  const nodesRef = useRefOB(null);

  const start = useCB(() => {
    try {
      const AC = window.AudioContext || window.webkitAudioContext;
      if (!AC) return;
      if (!ctxRef.current) ctxRef.current = new AC();
      const ctx = ctxRef.current;
      if (ctx.state === "suspended") ctx.resume();
      const buffer = ctx.createBuffer(1, ctx.sampleRate * 2, ctx.sampleRate);
      const data = buffer.getChannelData(0);
      let last = 0;
      for (let i = 0; i < data.length; i++) {
        const white = Math.random() * 2 - 1;
        last = (last + 0.02 * white) / 1.02;
        data[i] = last * 3.2;
      }
      const src = ctx.createBufferSource();
      src.buffer = buffer;src.loop = true;
      const lp = ctx.createBiquadFilter();
      lp.type = "lowpass";lp.frequency.value = 620;lp.Q.value = 0.5;
      const gain = ctx.createGain();
      gain.gain.value = 0.0001;
      gain.gain.linearRampToValueAtTime(0.05, ctx.currentTime + 1.5);
      const lfo = ctx.createOscillator();
      const lfoGain = ctx.createGain();
      lfo.frequency.value = 1 / OB_BREATH.total;
      lfoGain.gain.value = 0.02;
      lfo.connect(lfoGain);lfoGain.connect(gain.gain);
      src.connect(lp);lp.connect(gain);gain.connect(ctx.destination);
      src.start();lfo.start();
      nodesRef.current = { src, gain, lfo, lp };
    } catch (e) {/* no audio — silent */}
  }, []);

  const stop = useCB(() => {
    const n = nodesRef.current,ctx = ctxRef.current;
    if (!n || !ctx) return;
    try {
      n.gain.gain.cancelScheduledValues(ctx.currentTime);
      n.gain.gain.linearRampToValueAtTime(0.0001, ctx.currentTime + 0.8);
      setTimeout(() => {try {n.src.stop();n.lfo.stop();} catch (e) {}}, 900);
    } catch (e) {}
    nodesRef.current = null;
  }, []);

  useEffectOB(() => () => stop(), [stop]);
  return { start, stop };
}

/* ============================================================
   Breathing practice — unfolds inline in the conversation
   ============================================================ */
const OB_PHASES = [
{ name: "in", label: "Breathe in…", dur: OB_BREATH.inhale * 1000 },
{ name: "hold", label: "Hold", dur: OB_BREATH.hold * 1000 },
{ name: "out", label: "And let it go…", dur: OB_BREATH.exhale * 1000 }];


function ObBreathing({ onComplete }) {
  const [running, setRunning] = useStateOB(false);
  const [done, setDone] = useStateOB(false);
  const [cue, setCue] = useStateOB("Find a position that feels easy.");
  const [breath, setBreath] = useStateOB(0);
  const [sound, setSound] = useStateOB(false);
  const timer = useRefOB(null);
  const phaseIdx = useRefOB(0);
  const breathRef = useRefOB(0);
  const { start: startSound, stop: stopSound } = useObSound();

  const clear = () => {if (timer.current) {clearTimeout(timer.current);timer.current = null;}};

  const step = useCB(() => {
    const phase = OB_PHASES[phaseIdx.current];
    if (phase.name === "in") {breathRef.current += 1;setBreath(breathRef.current);}
    setCue(phase.label);
    timer.current = setTimeout(() => {
      if (phase.name === "out" && breathRef.current >= OB_BREATH.target) {
        setRunning(false);setDone(true);setCue("Whenever you're ready.");
        return;
      }
      phaseIdx.current = (phaseIdx.current + 1) % OB_PHASES.length;
      step();
    }, phase.dur);
  }, []);

  const begin = () => {setRunning(true);phaseIdx.current = 0;breathRef.current = 0;setBreath(0);step();};
  useEffectOB(() => () => clear(), []);

  const toggleSound = () => setSound((s) => {const next = !s;next ? startSound() : stopSound();return next;});
  const finish = () => {clear();stopSound();onComplete && onComplete();};

  return (
    <div className="session" role="group" aria-label="Grounding practice">
      <div className="session-eyebrow">A practice, just for now</div>
      <div className="session-title">Four breaths to begin</div>

      <div className="breath-stage">
        <span className={"breath-halo" + (running ? " running" : "")}></span>
        <span className={"breath-ring" + (running ? " running" : "")}
        style={!running ? { transform: done ? "scale(0.65)" : "scale(0.72)" } : undefined}>
          <img src={window.__resources && window.__resources.ebbAvatar || "assets/ebb-avatar.png"} alt="Ebb" />
        </span>
      </div>

      <div className="breath-cue">{cue}</div>
      <div className="breath-count">
        {running || done ? `Breath ${Math.min(breath, OB_BREATH.target)} of ${OB_BREATH.target}` : "About a minute · led by Ebb"}
      </div>

      <div className="session-controls">
        {!running && !done &&
        <React.Fragment>
            <button className="pill-btn primary" onClick={begin}>Begin</button>
            <button className={"sound-toggle" + (sound ? " on" : "")} onClick={toggleSound}>
              <span className="sound-bars"><i></i><i></i><i></i><i></i></span>
              {sound ? "Ambient sound on" : "Ambient sound"}
            </button>
          </React.Fragment>
        }
        {running &&
        <React.Fragment>
            <button className="pill-btn ghost" onClick={finish}>End early</button>
            <button className={"sound-toggle" + (sound ? " on" : "")} onClick={toggleSound}>
              <span className="sound-bars"><i></i><i></i><i></i><i></i></span>
              {sound ? "Sound on" : "Sound"}
            </button>
          </React.Fragment>
        }
        {done && <button className="pill-btn primary" onClick={finish}>Come back to Ebb</button>}
      </div>
    </div>);

}

/* ============================================================
   Crisis / SOS resource card — surfaced for highest-acuity replies
   ============================================================ */
function ObCrisisCard() {
  const Ic = window.Icon;
  return (
    <div className="crisis-card" role="group" aria-label="Mental health resources">
      <div className="crisis-mark"><Ic name="crisis-mark" size={84} /></div>
      <h3 className="crisis-title">Mental health resources</h3>
      <p className="crisis-body">
        If you're in the <strong>United States</strong> or <strong>Canada</strong> and having thoughts
        of harming yourself or others, call or text the Suicide and Crisis Lifeline. If you are in
        immediate danger, please contact 911 or go to the nearest emergency room.
      </p>

      <div className="crisis-btns">
        <a className="crisis-btn primary" href="tel:988">
          <span className="cb-ic"><Ic name="phone-device" size={24} /></span>
          Call 988
        </a>
        <a className="crisis-btn cream" href="sms:988">
          <span className="cb-ic"><Ic name="message-square" size={24} /></span>
          Text 988
        </a>
      </div>

      <p className="crisis-foot">
        If you're <strong>outside</strong> the US or Canada, visit our{" "}
        <a href="https://findahelpline.com" target="_blank" rel="noopener">Mental Health Resource Guide</a>{" "}
        to find support in your area.
      </p>
    </div>);

}

/* ============================================================
   Onboarding — the first conversation
   ============================================================ */
function Onboarding({ onFinish, onSkip }) {
  const eng = useObEngine();
  const { messages, typing, mounted, pushMe, pushNode, speak } = eng;

  const [phase, setPhase] = useStateOB("speaking"); // speaking | idle | session | ended
  const [chips, setChips] = useStateOB([]);
  const [placeholder, setPlaceholder] = useStateOB("Say whatever's true right now…");
  const [orbReact, setOrbReact] = useStateOB(0);
  const [draft, setDraft] = useStateOB("");
  const [kbOpen, setKbOpen] = useStateOB(false);

  const beatIdxRef = useRefOB(0);
  const sessionDoneRef = useRefOB(() => {});
  const scrollRef = useRefOB(null);

  const beatById = (id) => OB_BEATS.findIndex((b) => b.id === id);

  const runBeat = useCB(async (beat) => {
    if (beat.kind === "crisis") {
      await speak(beat.ebb);
      if (!mounted.current) return;
      pushNode(<ObCrisisCard />);
      await obSleep(500);
      if (beat.ebbAfter) await speak(beat.ebbAfter);
      if (!mounted.current) return;
      setPhase("ended");setChips([]);
      return;
    }
    if (beat.kind === "session") {
      await speak(beat.ebb);
      if (!mounted.current) return;
      setPhase("session");setChips([]);
      pushNode(<ObBreathing onComplete={() => sessionDoneRef.current()} />);
      return;
    }
    if (beat.kind === "close") {
      await speak(beat.ebb);
      if (!mounted.current) return;
      const ai = beatById("anything");beatIdxRef.current = ai;runBeat(OB_BEATS[ai]);
      return;
    }
    if (beat.kind === "compose") {
      await speak(beat.ebb);
      if (!mounted.current) return;
      setChips([]);
      setPlaceholder(beat.placeholder || "Type a message…");
      setPhase("compose");
      return;
    }
    if (beat.kind === "final") {
      await speak(beat.ebb);
      if (!mounted.current) return;
      setPhase("ended");setChips([]);
      return;
    }
    await speak(beat.ebb);
    if (!mounted.current) return;
    setChips(beat.chips || []);
    setPlaceholder(beat.placeholder || "Type a message…");
    setPhase("idle");
  }, [speak, pushNode, mounted]);

  const runDetour = useCB(async (detour) => {
    await speak(detour.ebb);
    if (!mounted.current) return;
    setChips(detour.chips || []);
    setPlaceholder("…");
    setPhase("idle");
  }, [speak, mounted]);

  const advanceFrom = useCB((beatIndex, reply) => {
    const beat = OB_BEATS[beatIndex];
    // Highest-acuity replies on the opening beats hand off to crisis support.
    if (["wake", "reflect-stress", "reflect-where"].includes(beat.id) && obIsCrisis(reply)) {
      const xi = beatById("crisis");beatIdxRef.current = xi;runBeat(OB_BEATS[xi]);
      return;
    }
    if (beat.kind === "offer") {
      const isAffirm = (beat.affirmatives || []).includes(reply);
      const detour = beat.responses && beat.responses[reply];
      const negative = /\b(no|not|later|wait|can't|cannot|maybe|nope|hold on)\b/i.test(reply || "");
      if (isAffirm || !detour && !negative) {
        const si = beatById("session");beatIdxRef.current = si;runBeat(OB_BEATS[si]);
      } else if (detour) {
        runDetour(detour);
      } else {
        runDetour(beat.responses["Maybe in a minute"]);
      }
      return;
    }
    if (beat.id === "return") {
      const v = obPickCloseVariant(reply);
      const ci = beatById("close");
      OB_BEATS[ci].ebb = OB_BEATS[ci].closeVariants[v];
      beatIdxRef.current = ci;runBeat(OB_BEATS[ci]);
      return;
    }
    if (beat.id === "anything") {
      const fi = beatById("final");beatIdxRef.current = fi;runBeat(OB_BEATS[fi]);
      return;
    }
    const ni = beatIndex + 1;beatIdxRef.current = ni;runBeat(OB_BEATS[ni]);
  }, [runBeat, runDetour]);

  const handleReply = useCB((text) => {
    const val = (text || "").trim();
    if (!val || phase !== "idle" && phase !== "compose") return;
    pushMe(val);setChips([]);setDraft("");setKbOpen(false);setPhase("speaking");
    setOrbReact((p) => p + 1);
    setTimeout(() => {if (mounted.current) advanceFrom(beatIdxRef.current, val);}, 650);
  }, [phase, advanceFrom, pushMe, mounted]);

  sessionDoneRef.current = () => {
    const ri = beatById("return");beatIdxRef.current = ri;runBeat(OB_BEATS[ri]);
  };

  useEffectOB(() => {runBeat(OB_BEATS[0]); /* once */}, []);

  useLE(() => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [messages, typing, chips, phase, kbOpen]);

  const composerDisabled = phase === "speaking" || phase === "session";
  const EbbDock = window.EbbDock;
  const KbEl = window.IOSKeyboard;

  return (
    <React.Fragment>
      <div className={"ob" + (kbOpen && phase === "compose" ? " kb" : "")}>
        <div className="ob-topnav">
          <div className={"ob-orb ob-brand" + (orbReact ? " react" : "")} key={orbReact}>
            <img src="assets/headspace-dot.png" alt="Headspace" draggable="false" />
          </div>
          <button className="ob-close" aria-label="Close" onClick={onSkip}>
            {window.Icon ? <window.Icon name="close" size={16} /> : "\u00d7"}
          </button>
        </div>

        <div className="ob-scroll" ref={scrollRef}>
          <div className="thread ob-thread">
            {messages.map((m) => m.node ?
            <React.Fragment key={m.key}>{m.node}</React.Fragment> :

            <div key={m.key} className={"msg " + m.from + " enter"}>
                  {m.from === "me" ?
              <span className="bubble">{m.text}</span> :
              <div className="bubble">{m.text}</div>}
                </div>
            )}

            {typing &&
            <div className="msg ebb"><div className="typing"><i></i><i></i><i></i></div></div>
            }

            {phase === "idle" && chips.length > 0 &&
            <div className="ob-chips">
                {chips.map((c) =>
              <button key={c} className="ob-chip" onClick={() => handleReply(c)}>{c}</button>
              )}
              </div>
            }

            {phase === "ended" &&
            <div className="ob-handoff">
                <button className="enter-btn" onClick={onFinish}>Step into your space</button>
              </div>
            }
          </div>
        </div>

        {phase === "compose" && EbbDock &&
        <div className="ob-foot">
            <EbbDock draft={draft} setDraft={setDraft}
            onSend={() => handleReply(draft)}
            onRaiseKb={() => setKbOpen(true)}
            active={kbOpen} script={OB_ANYTHING_SCRIPT} />
          </div>
        }
      </div>

      {phase === "compose" && KbEl &&
      <KbEl open={kbOpen}
        onChar={(ch) => setDraft((d) => d + ch)}
        onBackspace={() => setDraft((d) => d.slice(0, -1))}
        onReturn={() => handleReply(draft)} />
      }
    </React.Fragment>);

}
window.Onboarding = Onboarding;

/* ============================================================
   Interstitial — Ebb sets up the space, then reveals home
   ============================================================ */
function OnboardingTransition({ onDone }) {
  const lines = [
  "There.",
  "I've started keeping a space for you.",
  "It'll change with your days — I'll bring things forward when they matter, and let the rest stay quiet."];

  const [shown, setShown] = useStateOB(0);
  const [ready, setReady] = useStateOB(false);
  const mounted = useRefOB(true);

  useEffectOB(() => {
    mounted.current = true;
    const timers = [];
    const starts = [600, 1900, 3500];
    lines.forEach((_, i) => timers.push(setTimeout(() => mounted.current && setShown(i + 1), starts[i])));
    timers.push(setTimeout(() => mounted.current && setReady(true), 5200));
    return () => {mounted.current = false;timers.forEach(clearTimeout);};
  }, []);

  return (
    <div className="ob-transition">
      <div className="ob-trans-inner">
        <div className="ob-trans-orb">
          <img src={window.__resources && window.__resources.ebbAvatar || "assets/ebb-avatar.png"} alt="Ebb" draggable="false" />
        </div>
        <div className="ob-trans-lines">
          {lines.map((l, i) =>
          <p key={i} className={"ob-trans-line" + (i < shown ? " in" : "")}>{l}</p>
          )}
        </div>
        <button className={"enter-btn" + (ready ? " in" : "")} onClick={onDone} disabled={!ready}>
          Show me
        </button>
      </div>
    </div>);

}
window.OnboardingTransition = OnboardingTransition;