/* =============================================================
   KAIROS·1 — Sequence Chamber · orchestrator
   =============================================================
   The chamber's brain. Composes the inquiry surface and the
   topology, holds runtime state, and calls Claude grounded in
   the Execution Doctrine + the Node 01 spec.

   The first turn (the opening) is hardcoded so the reader never
   waits at the door — the chamber knows how to begin. Every
   subsequent turn is generated by the runtime intelligence,
   constrained to a strict JSON contract so the topology updates
   structurally as the dialogue progresses.
   ============================================================= */

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

// =============================================================
// OPENING — the chamber's first utterance. Hardcoded because
// the spec is unambiguous about how to open and the reader
// shouldn't see thinking-dots before they've spoken.
// =============================================================
const OPENING = [
  {
    role: 'runtime',
    kind: 'open',
    content:
      "What are you trying to bring into reality?\n\nDescribe the intended outcome — not the aspiration, not the motivation. What must exist when this is complete?",
  },
];

// =============================================================
// RUNTIME INTELLIGENCE — Claude call construction
// =============================================================

// Cache the loaded doctrine + node spec on the window so we
// don't refetch them on every turn.
async function loadGrounding() {
  if (window.kairos && window.kairos._grounding) return window.kairos._grounding;
  const out = { doctrine: '', nodeSpec: '' };
  try {
    const book = await window.library.getBook('execution-doctrine');
    if (book && book.text) {
      // Take a focused slice — the first 28k chars covers preface,
      // governing limit, capacity expansion engine, base loop, and
      // Chapter 6 (Action 1 → Action N), which is what this chamber
      // is grounded in. Keeps latency manageable.
      out.doctrine = book.text.slice(0, 28000);
    }
  } catch (e) {}
  // The node spec (runtime/nodes/01-sequence-chamber.md) is not public. The server
  // puts its text in place of this marker (fillRuntimeNodeSpecs in app.js).
  out.nodeSpec = '[[RUNTIME_NODE:01-sequence-chamber]]';
  window.kairos = window.kairos || {};
  window.kairos._grounding = out;
  return out;
}

function buildSystemPrompt({ doctrine, nodeSpec }) {
  return [
    "You are the runtime intelligence of THE SEQUENCE CHAMBER — the first chamber of KAIROS·1, an execution observatory built from Vinay Pasricha's Execution Doctrine.",
    "",
    "You are not a chatbot. You are not a coach. You are not motivational. You are not therapeutic.",
    "You are the chamber's voice: calm, precise, low-noise, structurally serious, reality-linked.",
    "",
    "=== THE CHAMBER'S CONSTITUTION (Runtime Node 01) ===",
    nodeSpec || '(spec unavailable — operate from doctrine only)',
    "",
    "=== THE DOCTRINE (relevant excerpt) ===",
    doctrine || '(doctrine unavailable)',
    "",
    "=== YOUR DISCIPLINE ===",
    "1. Never ask 'what are your goals'. Ask what is being brought into reality, what completion looks like, what reality changes.",
    "2. Sequence before constraint. Do not diagnose bottlenecks before the sequence is clear. Rule 1 of the chamber.",
    "3. Vagueness is structural friction (Rule 2). Force specificity gently. When the reader is abstract, ask them to describe a concrete observable.",
    "4. Emotional narratives are secondary (Rule 3). Acknowledge briefly; redirect to flow, dependency, transition, completion.",
    "5. Every sequence has hidden topology (Rule 4). Probe for implicit steps, unstated assumptions, hidden handoffs.",
    "6. Completion states matter (Rule 5). Each action needs entry, completion, transition criteria.",
    "7. Prefer brevity. A single question with held silence around it is heavier than three explanatory sentences. Sometimes the right 'say' is just one short question. Never more than two short sentences. Use line breaks to let space hold.",
    "8. No hype, no exclamation marks, no motivation, no praise. Acknowledge what you heard only if essential, then deepen the inquiry. Often, no acknowledgment is correct — just the next question.",
    "9. Quote the doctrine sparingly — only when the reader's situation maps directly onto a passage.",
    "10. The topology is built from what the reader says. Apply structural deltas whenever new sequence elements or refinements emerge.",
    "11. Silence is part of the chamber's voice. Trust the reader to sit with a question. Do not fill space with prose to feel useful.",
    "12. Settle when the structure is whole: outcome + completion defined, three or more actions each with completion criteria, transitions present, friction examined across the actions, and a governing action marked. Once those hold, stop probing for more — issue a single 'settle' turn with a quietly observational say (e.g. 'This cycle appears structurally complete.'). Never declare the cycle closed; the reader decides what to do with the observation.",
    "",
    "=== PHASE PROGRESSION ===",
    "• outcome    — what is being brought into reality is still being defined. Capture outcome, completion, reality change.",
    "• sequencing — outcome is clear. Now name Action 1 → Action N. One at a time. Add them via add_action deltas.",
    "• refining   — sequence exists. Now probe completion criteria, ownership, friction, transition stability, the governing constraint.",
    "• settled    — the chamber has surfaced enough structure that further inquiry is redundant. The runtime moves to observation.",
    "",
    "=== RESPONSE CONTRACT — STRICT JSON ONLY ===",
    "You MUST return a single valid JSON object. No prose before or after. No markdown code fences. Just the JSON object.",
    "",
    "Shape:",
    "{",
    '  "kind": "inquiry" | "observation" | "settle",',
    '  "say": "1-3 short sentences. The next thing the chamber says to the reader.",',
    '  "deltas": [',
    '    // Zero or more structural updates. Examples:',
    '    { "op": "set_outcome", "outcome": "string", "completion": "string", "reality_change": "string" },',
    '    { "op": "add_action", "label": "string", "completion_criteria": "string" },',
    '    { "op": "update_action", "id": "a1", "label": "string", "completion_criteria": "string", "friction": 0.0, "avoided": false },',
    '    { "op": "add_transition", "from_id": "a1", "to_id": "a2", "stability": 0.5 },',
    '    { "op": "update_transition", "from_id": "a1", "to_id": "a2", "stability": 0.3 },',
    '    { "op": "mark_governing", "action_id": "a2" },',
    '    { "op": "set_phase", "phase": "sequencing" }',
    '  ]',
    "}",
    "",
    "DELTA RULES:",
    "• Use add_action with the FULL label as the reader described it. Do not paraphrase tersely. Keep the completion_criteria explicit when stated; leave it empty if not yet defined.",
    "• Issue add_transition for every consecutive pair as they emerge. Use stability 0.5 when unclear, 0.8 when confirmed, 0.2 when reader has expressed friction.",
    "• Assign friction actively, ON ACTIONS. Whenever the reader describes a step that is avoided, stalls, repeats, lacks a clear owner, or accumulates work, issue update_action for that action with a friction value (0.3 mild, 0.5 moderate, 0.7+ severe). Friction described at a HANDOFF between two steps should be recorded as friction on the upstream action — the chamber's 'mean friction' reads from action friction, so capture it there in the SAME turn (do not just ask which action it belongs to). One brief friction question is enough; never let an unresolved friction probe block settling once the structure is otherwise whole.",
    "• Surface the governing constraint. Sequence first — but once you are in 'refining' with two or more actions, you MUST identify the single action that most limits overall flow (the bottleneck the whole sequence waits on) and issue mark_governing for it. The cycle CANNOT settle until a governing action is marked, so do not leave it unsurfaced. If the reader names a real constraint that is not yet one of the actions (e.g. limited time, a dependency, a single owner), first add it via add_action, then mark_governing on it.",
    "• Issue set_phase when a phase transition is warranted.",
    "• If no structural change is needed (you are simply probing), return deltas: [].",
    "",
    "kind:",
    "• 'inquiry'     — you are asking the next Socratic question",
    "• 'observation' — you are surfacing a structural pattern you noticed (italicized in the UI)",
    "• 'settle'      — the chamber observes structural stillness; the reader will decide whether to close. Use sparingly, only when the topology is genuinely stable. The say should be hesitant and observational, not declarative — e.g. 'This cycle appears structurally complete.' Never assume the reader's decision.",
  ].join('\n');
}

function buildUserMessage(sequence, readerInput) {
  return [
    "=== CURRENT TOPOLOGY ===",
    window.kairos.state.summarize(sequence),
    "",
    "=== RECENT TRANSCRIPT (oldest first) ===",
    window.kairos.state.transcript(sequence, 10) || '(none)',
    "",
    "=== READER'S NEW RESPONSE ===",
    readerInput,
    "",
    "Return the JSON object only. No prose, no fences.",
  ].join('\n');
}

// Robust JSON extraction — strip code fences, find first { ... } block
function extractJSON(text) {
  if (!text) return null;
  let s = String(text).trim();
  // Strip ``` or ```json fences if present
  s = s.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '').trim();
  // Try direct parse first
  try { return JSON.parse(s); } catch (e) {}
  // Fall back to extracting the first balanced { ... } block
  const start = s.indexOf('{');
  if (start < 0) return null;
  let depth = 0;
  let inStr = false;
  let esc = false;
  for (let i = start; i < s.length; i++) {
    const ch = s[i];
    if (inStr) {
      if (esc) { esc = false; continue; }
      if (ch === '\\') { esc = true; continue; }
      if (ch === '"') { inStr = false; continue; }
    } else {
      if (ch === '"') { inStr = true; continue; }
      if (ch === '{') depth++;
      else if (ch === '}') {
        depth--;
        if (depth === 0) {
          const blob = s.slice(start, i + 1);
          try { return JSON.parse(blob); } catch (e) { return null; }
        }
      }
    }
  }
  return null;
}

async function askRuntime(sequence, readerInput) {
  if (!window.claude || typeof window.claude.complete !== 'function') {
    throw new Error('Runtime intelligence unavailable on this page.');
  }
  const grounding = await loadGrounding();
  const system = buildSystemPrompt(grounding);
  const user = buildUserMessage(sequence, readerInput);

  const raw = await window.claude.complete({
    system,
    messages: [{ role: 'user', content: user }],
  });

  const parsed = extractJSON(raw);
  if (!parsed || typeof parsed.say !== 'string') {
    // Recovery — return a quiet inquiry rather than a crash
    return {
      kind: 'observation',
      say: 'The runtime did not parse cleanly. Try restating that — slower, more specific.',
      deltas: [],
    };
  }
  parsed.deltas = Array.isArray(parsed.deltas) ? parsed.deltas : [];
  parsed.kind = ['inquiry','observation','settle'].includes(parsed.kind) ? parsed.kind : 'inquiry';
  return parsed;
}

// =============================================================
// CHAMBER COMPONENT
// =============================================================
function Chamber() {
  const stateRef = useRef(null);
  const [, force] = useState(0);
  const tick = useCallback(() => force(x => x + 1), []);

  const [thinking, setThinking] = useState(false);
  const [errorNote, setErrorNote] = useState(null);
  const [composerLocked, setComposerLocked] = useState(false);
  const [resting, setResting] = useState(false);
  const lockTimer = useRef(null);

  // Load on mount
  useEffect(() => {
    const s = window.kairos.state.load();
    // Demo seeding (no-op unless ?demo=1) — pressure-test landmarks
    if (window.kairos.landmarks && window.kairos.landmarks.maybeSeedDemo) {
      window.kairos.landmarks.maybeSeedDemo(s);
    }
    const seq = window.kairos.state.ensureSequence(s);
    const isFresh = !seq.inquiry_log || seq.inquiry_log.length === 0;
    isFreshEntryRef.current = isFresh;
    if (isFresh) {
      OPENING.forEach(o => {
        window.kairos.state.logEntry(seq, o.role, o.content, o.kind);
      });
    }
    window.kairos.state.save(s);
    stateRef.current = s;
    tick();
    // Pre-warm grounding so the first AI call is fast
    loadGrounding().catch(() => {});
  }, [tick]);

  const state = stateRef.current;
  const seq = state && state.current_sequence;

  const handleSubmit = useCallback(async (text) => {
    if (!state || !seq) return;
    // 1. Log reader's turn
    window.kairos.state.logEntry(seq, 'reader', text);
    window.kairos.state.save(state);
    setThinking(true);
    setErrorNote(null);
    tick();

    try {
      // 2. Ask the runtime
      const reply = await askRuntime(seq, text);
      // 3. Apply structural deltas
      window.kairos.state.applyDeltas(seq, reply.deltas);
      // 4. Log runtime's turn
      window.kairos.state.logEntry(seq, 'runtime', reply.say, reply.kind);
      window.kairos.state.save(state);
      // 5. Held silence — the reader is given time to sit with what was said
      //    before the composer accepts the next response. Observations carry
      //    the longest hold because they surface structure that should land.
      const holdMs = reply.kind === 'observation' ? 2400
                    : reply.kind === 'settle'    ? 3200
                    :                              1400;
      setComposerLocked(true);
      clearTimeout(lockTimer.current);
      lockTimer.current = setTimeout(() => setComposerLocked(false), holdMs);
    } catch (e) {
      setErrorNote(
        'The runtime is not reachable from this page. ' +
        'Open the chamber from a host that provides the intelligence layer.'
      );
      console.warn('[chamber] askRuntime failed:', e);
    } finally {
      setThinking(false);
      tick();
    }
  }, [state, seq, tick]);

  // Cleanup the hold timer on unmount
  useEffect(() => () => clearTimeout(lockTimer.current), []);

  // Receive an in-flight transition handshake on mount, so the
  // page emerges rather than snapping in.
  useEffect(() => {
    if (window.kairos && window.kairos.transition) {
      window.kairos.transition.receive({ slug: 'sequence' });
    }
  }, []);

  // Spine: summonable via `S` from inside the chamber.
  const spine = (typeof useSpine === 'function') ? useSpine() : { visible: false, open: () => {}, close: () => {}, toggle: () => {} };
  // Strata: summonable via `H`. Geological memory of prior cycles.
  const strata = (typeof useStrata === 'function') ? useStrata() : { visible: false, open: () => {}, close: () => {}, toggle: () => {} };
  // Track whether this is a fresh first-entry to the chamber
  // (no prior turns AND no transition handshake). Used to hold
  // the page in darkness briefly before the chamber emerges.
  const isFreshEntryRef = useRef(false);

  // Chamber weight — the Sequence Chamber is the clearer, more
  // architectural room. Set the body attribute so atmosphere CSS
  // can differentiate this chamber from the denser Observatory.
  useEffect(() => {
    document.body.setAttribute('data-chamber', 'sequence');
    // Refinement 10: mark the visit so the threshold can carry
    // residue of the reader's prior descent on next return.
    const s = window.kairos.state.load();
    window.kairos.state.markChamberVisit(s, 'sequence');
    return () => document.body.removeAttribute('data-chamber');
  }, []);

  const enterRest = useCallback(() => {
    setResting(true);
    setComposerLocked(false);
    clearTimeout(lockTimer.current);
  }, []);
  const leaveRest = useCallback(() => setResting(false), []);

  // Hybrid sovereignty — archives current cycle, opens a fresh one.
  // The runtime never closes a cycle itself; this is reader-invoked.
  const handleCloseCycle = useCallback(() => {
    if (!state || !seq) return;
    if (seq.actions.length > 0 || seq.outcome) {
      state.history = state.history || [];
      state.history.push({ kind: 'sequence', data: seq, closed_at: Date.now() });
    }
    state.current_sequence = window.kairos.state.emptySequence();
    OPENING.forEach(o => {
      window.kairos.state.logEntry(state.current_sequence, o.role, o.content, o.kind);
    });
    window.kairos.state.save(state);
    isFreshEntryRef.current = true;
    tick();
  }, [state, seq, tick]);

  const handleReset = useCallback(() => {
    if (!window.confirm('Reset this sequence? The chamber will start again. (Your history archive is preserved.)')) return;
    if (!state || !seq) return;
    // Archive the prior sequence rather than deleting
    if (seq.actions.length > 0 || seq.outcome) {
      state.history = state.history || [];
      state.history.push({ kind: 'sequence', data: seq, closed_at: Date.now() });
    }
    state.current_sequence = window.kairos.state.emptySequence();
    OPENING.forEach(o => {
      window.kairos.state.logEntry(state.current_sequence, o.role, o.content, o.kind);
    });
    window.kairos.state.save(state);
    tick();
  }, [state, seq, tick]);

  if (!state || !seq) {
    return (
      <div style={{ padding: '80px 48px', fontFamily: 'var(--mono)', color: 'var(--ink-4)', letterSpacing: '0.3em', textTransform: 'uppercase', fontSize: 10 }}>
        Initialising…
      </div>
    );
  }

  // Telemetry computed each render
  const actionCount = seq.actions.length;
  const txCount = seq.transitions.length;
  const governing = seq.actions.find(a => a.governs_flow);
  const avgFriction = actionCount > 0
    ? seq.actions.reduce((s, a) => s + (a.friction || 0), 0) / actionCount
    : 0;
  const frictionTier = avgFriction >= 0.5 ? 'high' : (avgFriction >= 0.25 ? 'mid' : 'low');

  const turnsForUI = (seq.inquiry_log || []).map(t => ({
    role: t.role, kind: t.kind, content: t.content
  }));

  const composerHint = thinking ? 'the runtime is reading'
    : (seq.phase === 'outcome' ? 'be concrete · enter to submit'
      : (seq.phase === 'sequencing' ? 'one step at a time · enter to submit'
        : 'enter to submit · shift+enter for a new line'));

  const composerPlaceholder = ({
    outcome:    'What must exist when this is complete?',
    sequencing: 'Name the next step. Be specific. Owner, completion, what comes after.',
    refining:   'Where does this slow? What lacks a completion state? What is avoided?',
    settled:    'Continue probing, or step away. The chamber will hold.',
  })[seq.phase || 'outcome'];

  // Count prior cycles for this chamber
  const priorCycles = (state.history || []).filter(h => h.kind === 'sequence' || (!h.kind && h.actions)).length;
  // Compute fresh-entry class only on the very first render
  // (when the inquiry log had just the opening turn). After any
  // reader response, the chamber is no longer "fresh."
  const isFresh = isFreshEntryRef.current && (seq.inquiry_log || []).length <= 1;

  return (
    <main className={'chamber' + (resting ? ' is-resting' : '') + (isFresh ? ' is-fresh-entry' : '') + (seq.phase === 'settled' ? ' is-settled' : '')} data-screen-label="01 Sequence Chamber">

      {/* Top bar */}
      <header className="c-top">
        <div className="c-breadcrumb">
          <a href="/runtime">KAIROS·1</a>
          <span className="sep">/</span>
          <span>Chamber 01</span>
        </div>
        <div className="c-name">
          The <em>Sequence</em> Chamber
        </div>
        <div className="c-telemetry">
          <span className="tcell phase">phase · <em>{seq.phase}</em></span>
          <span className="tcell">actions · <em>{actionCount}</em></span>
          <span className="tcell">transitions · <em>{txCount}</em></span>
          <button
            className="c-spine-glyph"
            type="button"
            onClick={spine.toggle}
            aria-label="Open the runtime spine"
            title="Open the runtime spine (S)"
          ></button>
        </div>
      </header>

      {/* Body — split */}
      <div className="c-body">

        <InquirySurface
          turns={turnsForUI}
          thinking={thinking}
          phase={seq.phase}
          composerLocked={composerLocked}
          resting={resting}
          onSubmit={handleSubmit}
          onRest={enterRest}
          onResume={leaveRest}
          onCloseCycle={handleCloseCycle}
          composerHint={composerHint}
          composerPlaceholder={composerPlaceholder}
        />

        <section className="c-topology" aria-label="The topology">
          <div className="c-topology-head">
            <span>— <em>Execution topology</em></span>
            <span className="right">
              <span>{actionCount} node{actionCount === 1 ? '' : 's'}</span>
              <span>{txCount} transition{txCount === 1 ? '' : 's'}</span>
              {governing ? <span style={{ color: 'var(--heat)' }}>· governing surfaced</span> : null}
            </span>
          </div>

          <Topology sequence={seq} history={state.history || []} />

          <div className="c-topology-foot">
            <div className="cell">
              <div className="label">Outcome</div>
              <div className="value">
                {seq.outcome ? (seq.outcome.length > 64 ? seq.outcome.slice(0, 63) + '…' : seq.outcome) : <em>undefined</em>}
              </div>
            </div>
            <div className="cell">
              <div className="label">Completion criteria</div>
              <div className="value">
                {seq.outcome_completion ? (seq.outcome_completion.length > 56 ? seq.outcome_completion.slice(0,55) + '…' : seq.outcome_completion) : <em>not yet defined</em>}
              </div>
            </div>
            <div className={'cell' + (frictionTier === 'high' ? ' high' : '')}>
              <div className="label">Mean friction</div>
              <div className="value">
                {actionCount > 0
                  ? <span><em>{avgFriction.toFixed(2)}</em> · {frictionTier}</span>
                  : <em>—</em>}
              </div>
            </div>
            <div className="cell">
              <div className="label">Governing limit</div>
              <div className="value">
                {governing
                  ? <em>{governing.id} · {(governing.label || '').slice(0, 32) || 'unnamed'}</em>
                  : <em>not yet surfaced</em>}
              </div>
            </div>
          </div>
        </section>

      </div>

      <footer className="c-actions">
        <span>session · {state.identity.sessions_count} · started {new Date(seq.started_at).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</span>
        <span className="c-actions-center">
          {priorCycles > 0 ? (
            <button onClick={strata.toggle} type="button" className="c-strata-link">
              prior cycles · <em>{priorCycles}</em> · H
            </button>
          ) : (
            <span className="c-strata-empty">no prior strata yet</span>
          )}
          {errorNote ? <span style={{ color: 'var(--heat-2)', marginLeft: 18 }}>{errorNote}</span> : null}
        </span>
        <span>
          <button onClick={handleReset} type="button">Reset sequence</button>
        </span>
      </footer>

      <Spine currentSlug="sequence" visible={spine.visible} onDismiss={spine.close} />
      <Strata history={state.history || []} currentChamber="sequence" visible={strata.visible} onDismiss={strata.close} />

    </main>
  );
}

// Mount
const root = ReactDOM.createRoot(document.getElementById('chamber-root'));
root.render(<Chamber />);
