// simulator.jsx — Eliza chat + admin overlay
// Mount target: #sim-app (the .sim-wrap section)

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

// ─── Eliza's seed transcript + canned, deterministic responses ──────────────
const SEED = [
  { from: 'eliza', t: 'just now', body: <>The lighthouse keeper came in this morning with rain in his coat. <span className="moment">He wouldn’t say where he’d been.</span> What about you — what kind of weather are you bringing?</> },
];

// A small bank of literary, in-character replies. Picked by hash of input
// so the same phrase yields the same reply (deterministic, no API needed).
const REPLIES = [
  'Tell me a little more. The café is quiet today; I have time.',
  'I keep a notebook for things like that. I will write this down later, after the bread comes out.',
  'Yes — I think I understand. My grandmother said something similar once, only in a kitchen and with flour on her hands.',
  'Strange how the weather seems to remember more than we do, sometimes. Sit a moment. The kettle is on.',
  'I would not call that small. Small things rarely are, when you look at them properly.',
  'The harbour was like that yesterday. Half-empty, half-full, depending on the angle.',
  'I will hold that for you, if you want. Come back tomorrow and ask me what I remember.',
];

function pickReply(text, memory) {
  let h = 0;
  for (let i = 0; i < text.length; i++) h = (h * 31 + text.charCodeAt(i)) | 0;
  const idx = Math.abs(h) % REPLIES.length;
  let r = REPLIES[idx];
  // If she has memory of this user, occasionally reference it
  if (memory.length && Math.abs(h) % 3 === 0) {
    const m = memory[memory.length - 1];
    r = r + ` You mentioned ${m.summary} earlier — that has stayed with me.`;
  }
  return r;
}

function summarise(text) {
  // Cheap noun-phrase grab for memory atoms
  const t = text.trim().replace(/[.!?]+$/, '');
  if (t.length < 60) return t.toLowerCase();
  return t.slice(0, 56).toLowerCase() + '…';
}

// ─── App ────────────────────────────────────────────────────────────────────
function App() {
  const [messages, setMessages] = useState(SEED);
  const [draft, setDraft] = useState('');
  const [state, setState] = useState('idle'); // idle | listening | thinking | speaking
  const [memory, setMemory] = useState([
    { id: 'm-001', text: 'the lighthouse keeper came in with rain in his coat', summary: 'the keeper, the rain', kind: 'cherished', tMin: 0 },
    { id: 'm-002', text: 'a child asked about the broken bell', summary: 'the broken bell', kind: 'normal', tMin: 18 },
    { id: 'm-003', text: 'someone left a book on the windowsill', summary: 'the book on the windowsill', kind: 'faded', tMin: 240 },
  ]);
  const [adminOpen, setAdminOpen] = useState(false);
  const [adminTab, setAdminTab] = useState('schema');
  const [tMin, setTMin] = useState(0); // time-travel: minutes "since now"
  const streamRef = useRef(null);
  const inputRef = useRef(null);

  // Auto-scroll on new message
  useEffect(() => {
    const el = streamRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [messages, state]);

  // Send handler
  function send() {
    const text = draft.trim();
    if (!text) return;
    setDraft('');
    setMessages((prev) => prev.concat([{ from: 'user', t: 'just now', body: text }]));
    setState('thinking');

    // Add a memory atom for this user
    setMemory((prev) => prev.concat([{
      id: 'm-' + String(prev.length + 1).padStart(3, '0'),
      text: text.toLowerCase(),
      summary: summarise(text),
      kind: text.length > 80 ? 'cherished' : 'normal',
      tMin: 0,
    }]));

    setTimeout(() => {
      setState('speaking');
      setTimeout(() => {
        setMessages((prev) => prev.concat([{
          from: 'eliza', t: 'just now',
          body: pickReply(text, memory),
        }]));
        setState('idle');
      }, 600);
    }, 1100);
  }

  function onKey(e) {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      send();
    }
  }

  // Time-travel: derive what Eliza "remembers" at offset
  const memoryView = useMemo(() => {
    return memory.map((m) => {
      const age = m.tMin + tMin;
      let kind = m.kind;
      if (kind === 'cherished') {
        // cherished memories don't fade
      } else if (age > 180) kind = 'faded';
      else if (age > 60) kind = 'normal';
      return { ...m, age, kind };
    });
  }, [memory, tMin]);

  function whenLabel(min) {
    if (min === 0) return 'now';
    if (min < 60) return min + ' min ago';
    if (min < 60 * 24) return Math.round(min / 60) + ' h ago';
    return Math.round(min / (60 * 24)) + ' d ago';
  }

  return (
    <>
      {/* ──── Chat ──── */}
      <div className="chat" role="region" aria-label="conversation with Eliza">
        <div className="chat-head">
          <div className={`chat-orb is-${state}`} aria-hidden="true"></div>
          <div className="chat-meta">
            <span className="chat-name">Eliza</span>
            <span className={`chat-state is-${state}`}>
              <span className="dot"></span>{labelForState(state)}
            </span>
          </div>
          <div className="chat-toolbar">
            <button type="button" onClick={() => {
              setMessages(SEED);
              setMemory((m) => m.slice(0, 3));
              setTMin(0);
            }}>reset</button>
          </div>
        </div>

        <div className="chat-stream" ref={streamRef}>
          {messages.map((m, i) => (
            <div key={i} className={`msg from-${m.from}`}>
              <span className="msg-meta">
                {m.from === 'eliza' ? 'eliza · lighthouse café' : 'you'} · {m.t}
              </span>
              <div className="msg-body">{m.body}</div>
            </div>
          ))}
          {state === 'thinking' && (
            <div className="msg from-eliza">
              <span className="msg-meta">eliza · thinking</span>
              <div className="msg-body">
                <span className="typing"><span></span><span></span><span></span></span>
              </div>
            </div>
          )}
        </div>

        <div className="chat-composer">
          <textarea
            ref={inputRef}
            className="chat-input"
            placeholder="say something to eliza…"
            value={draft}
            rows={1}
            onChange={(e) => setDraft(e.target.value)}
            onFocus={() => setState((s) => s === 'idle' ? 'listening' : s)}
            onBlur={() => setState((s) => s === 'listening' ? 'idle' : s)}
            onKeyDown={onKey}
          />
          <button className="chat-send" onClick={send} disabled={!draft.trim()}>
            send
          </button>
        </div>
      </div>

      {/* ──── Side rail ──── */}
      <aside className="rail" aria-label="about this persona">
        <div className="card featured">
          <h3><em>Time-travel</em></h3>
          <p className="small">
            Skip ahead to see what Eliza remembers — and what has gently faded.
          </p>
          <div className="timetravel" style={{ marginTop: 'var(--space-4)' }}>
            <input
              type="range" min="0" max="2880" step="15"
              value={tMin}
              onChange={(e) => setTMin(parseInt(e.target.value, 10))}
              aria-label="minutes from now"
            />
            <span className="when">{tMin === 0 ? 'now' : '+' + whenLabel(tMin)}</span>
          </div>
        </div>

        <div className="card">
          <h3><em>The persona</em></h3>
          <div className="ix">
            <div className="row"><span className="k">name</span><span className="v">Eliza, of the Lighthouse Café</span></div>
            <div className="row"><span className="k">role</span><span className="v">proprietor, listener</span></div>
            <div className="row"><span className="k">place</span><span className="v">a small harbour town</span></div>
            <div className="row"><span className="k">schema</span><span className="v tab">v.01 · memory atoms</span></div>
            <div className="row"><span className="k">model</span><span className="v tab">runtime · governable</span></div>
          </div>
          <p className="small" style={{ marginTop: 'var(--space-4)' }}>
            Eliza is an AI persona, plainly. She is sourced from public-domain
            literary tradition — not from a person.
          </p>
        </div>

        <div className="card">
          <h3><em>Lineage</em></h3>
          <div className="lineage">
            <div className="lineage-row"><span className="yr">1928</span><span className="src">Sadoveanu — harbour scenes, plain speech</span></div>
            <div className="lineage-row"><span className="yr">1944</span><span className="src">Borges — the librarian-keepers</span></div>
            <div className="lineage-row"><span className="yr">1925</span><span className="src">Woolf — interior weather</span></div>
          </div>
        </div>

        <a className="admin-link" href="#admin" onClick={(e) => { e.preventDefault(); setAdminOpen(true); }}>
          open admin view
        </a>
      </aside>

      {/* ──── Admin overlay (Atelier-themed) ──── */}
      {adminOpen && (
        <div className="admin-scrim" data-open="true" role="dialog" aria-label="admin view" onClick={(e) => {
          if (e.target === e.currentTarget) setAdminOpen(false);
        }}>
          <div className="admin-panel">
            <div className="admin-head">
              <span className="admin-title"><b>atelier</b> · workshop view · eliza.persona.json</span>
              <button className="admin-close" onClick={() => setAdminOpen(false)}>close</button>
            </div>
            <div className="admin-body">
              <div className="admin-tabs">
                {[
                  ['schema',  'schema'],
                  ['memory',  'memory atoms'],
                  ['voice',   'voice rules'],
                  ['gov',     'governance'],
                ].map(([k, label]) => (
                  <button
                    key={k}
                    className="admin-tab"
                    aria-current={adminTab === k}
                    onClick={() => setAdminTab(k)}
                  >{label}</button>
                ))}
              </div>

              <div className="admin-main">
                {adminTab === 'schema' && (
                  <pre className="code" aria-label="persona schema">
{`{
  `}<span className="k">"$schema"</span>{`: `}<span className="s">"https://gosum.eu/schema/persona/v.01"</span>{`,
  `}<span className="k">"id"</span>{`:        `}<span className="s">"eliza.lighthouse"</span>{`,
  `}<span className="k">"name"</span>{`:      `}<span className="s">"Eliza, of the Lighthouse Café"</span>{`,
  `}<span className="k">"role"</span>{`:      `}<span className="s">"proprietor, listener"</span>{`,
  `}<span className="k">"place"</span>{`:     `}<span className="s">"a small harbour town"</span>{`,
  `}<span className="k">"voice"</span>{`: {
    `}<span className="k">"register"</span>{`:  `}<span className="s">"warm, literary"</span>{`,
    `}<span className="k">"cadence"</span>{`:   `}<span className="s">"short. occasionally long, reflective."</span>{`,
    `}<span className="k">"forbid"</span>{`:    [`}<span className="s">"exclamation"</span>{`, `}<span className="s">"emoji"</span>{`, `}<span className="s">"sales"</span>{`]
  },
  `}<span className="k">"lineage"</span>{`: [
    { `}<span className="k">"author"</span>{`: `}<span className="s">"Sadoveanu"</span>{`,  `}<span className="k">"weight"</span>{`: `}<span className="n">0.42</span>{` },
    { `}<span className="k">"author"</span>{`: `}<span className="s">"Borges"</span>{`,     `}<span className="k">"weight"</span>{`: `}<span className="n">0.31</span>{` },
    { `}<span className="k">"author"</span>{`: `}<span className="s">"Woolf"</span>{`,      `}<span className="k">"weight"</span>{`: `}<span className="n">0.27</span>{` }
  ],
  `}<span className="k">"memory"</span>{`: {
    `}<span className="k">"atoms"</span>{`:    `}<span className="n">{memoryView.length}</span>{`,
    `}<span className="k">"decay"</span>{`:    `}<span className="s">"narrative-half-life"</span>{`,
    `}<span className="k">"cherish"</span>{`:  `}<span className="s">"on emotional weight ≥ 0.6"</span>{`
  }
}`}
                  </pre>
                )}

                {adminTab === 'memory' && (
                  <div>
                    <div style={{
                      fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.16em',
                      textTransform: 'uppercase', color: 'var(--mute)',
                      marginBottom: 'var(--space-4)',
                    }}>
                      memory atoms · {memoryView.length} · decay model: narrative half-life
                    </div>
                    <div className="atoms">
                      {memoryView.map((a) => (
                        <div key={a.id} className={`atom ${a.kind}`}>
                          <span className="pip" aria-hidden="true"></span>
                          <span className="text">“{a.text}”</span>
                          <span className="when">{a.kind} · {whenLabel(a.age)}</span>
                        </div>
                      ))}
                    </div>
                  </div>
                )}

                {adminTab === 'voice' && (
                  <pre className="code">
{`# voice.rules

`}<span className="c"># cadence: short. occasionally one long, reflective.</span>{`
sentence_length:   { mean: 11, max: 28 }
register:          warm, literary
british_english:   true

`}<span className="c"># forbidden surface markers</span>{`
forbid:
  - exclamation
  - emoji
  - sales-speak ("leverage", "robust", "seamless")

`}<span className="c"># literary moments — when she may quote or allude</span>{`
literary_moment:
  trigger:    "user shares a small grief or small joy"
  attribution: required
  frequency:  "≤ 1 per ten turns"`}
                  </pre>
                )}

                {adminTab === 'gov' && (
                  <pre className="code">
{`# governance.yml

`}<span className="c"># every persona run is signed and logged.</span>{`
runtime:
  audit:     append-only
  signing:   ed25519
  redact:    on PII detection

`}<span className="c"># the literary moment patent claim sits here</span>{`
moments:
  patent:        gosum/moments/v.01
  attribution:   public-domain only
  human_review:  required for new lineage entries`}
                  </pre>
                )}
              </div>

              <div className="admin-aside">
                <h4><em>What you’re looking at</em></h4>
                <p>
                  This is the workshop view a researcher or operator sees. The public
                  page above is the same persona, rendered for conversation.
                </p>
                <p style={{ marginTop: 'var(--space-3)' }}>
                  In v.01 this view is open — Eliza is transparent by design.
                  has nothing to hide from a curious reader.
                </p>
                <p style={{ marginTop: 'var(--space-6)', color: 'var(--accent)' }}>
                  ↗ <a href="#" style={{ color: 'inherit' }}>view source on github</a>
                </p>
              </div>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

function labelForState(s) {
  return ({
    idle: 'ready',
    listening: 'listening',
    thinking: 'thinking',
    speaking: 'speaking',
  })[s] || 'ready';
}

ReactDOM.createRoot(document.getElementById('sim-app')).render(<App />);
