/* Artemis chat — root app. Owns chat state, wiring, LLM calls. */

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

function App() {
  const [user, setUser] = useState(null);

  // chat / thread state
  const [threads, setThreads] = useState(SEED_THREADS);
  const [activeId, setActiveId] = useState('t_kernel');
  const [byThread, setByThread] = useState({
    t_kernel: [],
    t_atp: [
      { role: 'user', content: 'Quick check: what fields does ATP v0.4 require in the envelope?', agent: 'planner' },
      { role: 'assistant', agent: 'planner', content: 'ATP v0.4 envelope requires:\n\n- [ ] **agent** — sender id (registry-scoped)\n- [ ] **intent** — one of: request / propose / commit / reject\n- [ ] **payload** — typed body (Pydantic-validated against intent)\n- [ ] **governance** — `{policy, trust_min, tool_whitelist}`\n- [ ] **trust_decay** — float 0-1, deducted from trust score on failure\n\nReview the governance block — the kernel rejects envelopes whose trust_min ≥ the sender\'s current trust score.' },
    ],
    t_bench: [
      { role: 'user', content: 'TL;DR on the latest Hebbian routing benchmark?', agent: 'research' },
      { role: 'assistant', agent: 'research', content: 'Across 12,400 routed tasks against the static baseline:\n\n• **+18.3%** mean success rate at iter ≥ 4\n• **−42%** kernel-to-agent round trips\n• **1.4** Sharpe-of-quality (vs. baseline 1.0)\n\nWeights converged after ~3.2k runs. Source: [obsidian:Hebbian routing benchmarks]. Caveat: dataset skewed toward summarization tasks; planning tasks underperform until iter ≥ 8.' },
    ],
    t_okrs: [],
  });
  const [pending, setPending] = useState(false);

  // composer state
  const [draft, setDraft] = useState('');
  const [agent, setAgent] = useState('artemis');
  const [composerTools, setComposerTools] = useState({ 'memory.read': true, 'kb.search': true });

  // right panel
  const [rightTab, setRightTab] = useState('agents');
  const [agentsState, setAgentsState] = useState({
    artemis: { status: 'idle', task: 'awaiting work' },
    planner: { status: 'idle', task: 'idle' },
    research: { status: 'idle', task: 'idle' },
    summarizer: { status: 'idle', task: 'idle' },
  });
  const [allTools, setAllTools] = useState({
    'memory.read': true, 'memory.write': false, 'kb.search': true,
    'web.fetch': false, 'web.search': false, 'code.execute': false,
    'shell.run': false, 'notion.api': true, 'github.api': false,
  });

  const [taskOpen, setTaskOpen] = useState(false);

  const scrollRef = useRef(null);
  useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [byThread, activeId, pending]);

  const activeMessages = byThread[activeId] || [];
  const activeThread = threads.find((t) => t.id === activeId);

  // Helpers
  function bump(threadId, msg) {
    setByThread((prev) => ({ ...prev, [threadId]: [...(prev[threadId] || []), msg] }));
  }
  function updateLast(threadId, patch) {
    setByThread((prev) => {
      const arr = [...(prev[threadId] || [])];
      arr[arr.length - 1] = { ...arr[arr.length - 1], ...patch };
      return { ...prev, [threadId]: arr };
    });
  }

  /* --- Send a message --- */
  async function send(textArg, agentArg) {
    const text = (textArg ?? draft).trim();
    if (!text || pending) return;
    const useAgent = agentArg || agent;
    const a = AGENTS.find((x) => x.id === useAgent);

    setDraft('');
    setPending(true);

    bump(activeId, { role: 'user', content: text, agent: useAgent });

    // Activity panel: this agent is now running
    setAgentsState((s) => ({ ...s, [useAgent]: { status: 'running', task: text.slice(0, 60) + (text.length > 60 ? '…' : '') } }));

    // Push other relevant agents into a brief "running" state for visual texture
    if (useAgent === 'artemis' && /research|find|sources|cite/i.test(text)) {
      setAgentsState((s) => ({ ...s, research: { status: 'running', task: 'searching memory bus' } }));
    }
    if (useAgent === 'artemis' && /plan|steps|how do|how to/i.test(text)) {
      setAgentsState((s) => ({ ...s, planner: { status: 'running', task: 'decomposing into steps' } }));
    }

    // Placeholder assistant bubble for streaming feel
    bump(activeId, { role: 'assistant', agent: useAgent, content: '', typing: true });

    try {
      const history = (byThread[activeId] || []).map((m) => ({
        role: m.role,
        content: typeof m.content === 'string' ? m.content : '',
      }));
      const messages = [
        ...history.filter((m) => m.content),
        { role: 'user', content: text },
      ];
      // System prompt baked into a leading user turn the model treats as instruction.
      const systemPrefixed = [{ role: 'user', content: `System: ${a.prompt}\n\nRespond as ${a.name}. Be concrete and brief.` }, ...messages];
      const reply = await window.claude.complete({ messages: systemPrefixed });

      updateLast(activeId, { content: reply, typing: false });

      // Mark active agent done, then idle after a moment
      setAgentsState((s) => ({ ...s, [useAgent]: { status: 'success', task: 'completed · 1 reply' } }));
      setTimeout(() => {
        setAgentsState((s) => ({
          ...s,
          [useAgent]: { status: 'idle', task: 'awaiting work' },
          research: s.research.status === 'running' ? { status: 'idle', task: 'idle' } : s.research,
          planner: s.planner.status === 'running' ? { status: 'idle', task: 'idle' } : s.planner,
        }));
      }, 2000);

      // Touch thread metadata
      setThreads((ts) => ts.map((t) => t.id === activeId ? { ...t, last: reply.slice(0, 36), when: 'Just now', agent: useAgent } : t));
    } catch (err) {
      updateLast(activeId, {
        content: '⚠ Kernel error: could not reach LLM provider. Check session token & try again.\n\n' + (err?.message || ''),
        typing: false, error: true,
      });
      setAgentsState((s) => ({ ...s, [useAgent]: { status: 'idle', task: 'errored · retry available' } }));
    } finally {
      setPending(false);
    }
  }

  function newThread() {
    const id = 't_' + Math.random().toString(16).slice(2, 8);
    const t = { id, title: 'New thread', agent, when: 'Just now', last: '—' };
    setThreads((ts) => [t, ...ts]);
    setByThread((b) => ({ ...b, [id]: [] }));
    setActiveId(id);
  }

  function dispatchTask(task) {
    // Open or reuse current thread, send a structured kick-off message
    const id = 't_' + Math.random().toString(16).slice(2, 8);
    const t = { id, title: task.goal.slice(0, 38) + (task.goal.length > 38 ? '…' : ''), agent: task.agent, when: 'Just now', last: 'dispatched · ' + task.policy };
    setThreads((ts) => [t, ...ts]);
    setByThread((b) => ({ ...b, [id]: [] }));
    setActiveId(id);
    setAgent(task.agent);
    setComposerTools(task.tools);
    setTaskOpen(false);

    const lines = [
      `**New task dispatched**`,
      ``,
      `**Goal:** ${task.goal}`,
      task.desc ? `**Context:** ${task.desc}` : null,
      `**Policy:** \`${task.policy}\`  ·  **Max iter:** \`${task.maxIter}\`  ·  **Trust ≥** \`${task.trust.toFixed(2)}\``,
      `**Tools:** ${Object.entries(task.tools).filter(([, v]) => v).map(([k]) => '`' + k + '`').join(', ') || '_none_'}`,
    ].filter(Boolean).join('\n');

    setTimeout(() => send(lines, task.agent), 150);
  }

  /* --- Render --- */
  if (!user) return <><AuroraBg /><Auth onIn={setUser} /></>;

  return (
    <>
      <AuroraBg />
      <div className="app">
        <div className="topbar">
          <div className="brand">
            <span className="glyph">A</span>
            <span className="name">Artemis <span className="city">City</span></span>
            <span className="session-chip"><Icon name="cpu" size={12} /> kernel #a4f3 · live</span>
          </div>
          <div className="right" style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
            <span className="session-chip" style={{ background: 'rgba(34,197,94,0.08)', borderColor: 'rgba(34,197,94,0.30)', color: '#86efac' }}>
              <span style={{ width: 6, height: 6, borderRadius: '50%', background: '#22c55e', boxShadow: '0 0 8px #22c55e' }}></span>
              Descope · session valid
            </span>
            <div className="user-chip">
              <span className="av">{(user.name || 'U').slice(0, 2).toUpperCase()}</span>
              <span>{user.name}</span>
            </div>
          </div>
        </div>

        <Sidebar
          threads={threads}
          activeId={activeId}
          onPickThread={setActiveId}
          onNewThread={newThread}
          onOpenTask={() => setTaskOpen(true)}
        />

        <Chat
          thread={activeThread}
          messages={activeMessages}
          pending={pending}
          draft={draft}
          setDraft={setDraft}
          agent={agent}
          setAgent={setAgent}
          tools={composerTools}
          setTools={setComposerTools}
          scrollRef={scrollRef}
          onSend={() => send()}
          onPrompt={(p) => send(p)}
        />

        <RightPanel tab={rightTab} setTab={setRightTab} agentsState={agentsState} tools={allTools} setTools={setAllTools} />
      </div>

      {taskOpen && <TaskModal onClose={() => setTaskOpen(false)} onCreate={dispatchTask} defaultTools={composerTools} />}
    </>
  );
}

function AuroraBg() {
  return (
    <div className="aurora">
      <div className="blob b1"></div>
      <div className="blob b2"></div>
      <div className="blob b3"></div>
    </div>
  );
}

function Chat({ thread, messages, pending, draft, setDraft, agent, setAgent, tools, setTools, scrollRef, onSend, onPrompt }) {
  const a = AGENTS.find((x) => x.id === agent);

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

  return (
    <section className="chat">
      <div className="chat-head">
        <div>
          <h3 className="ttl">{thread?.title || 'New thread'}</h3>
          <span className="pol">
            <span className="agent-dot" style={{ background: a.color, color: a.color }}></span>
            lead: <b>{a.name}</b> · policy <b style={{ color: '#fcd34d' }}>approval</b> · trust ≥ <b>0.75</b>
          </span>
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="tool-btn"><Icon name="search" size={12} /> Find</button>
          <button className="tool-btn"><Icon name="zap" size={12} /> Replay</button>
        </div>
      </div>

      <div className="thread-body" ref={scrollRef}>
        {messages.length === 0 && <EmptyState onPick={onPrompt} agent={a} />}
        {messages.map((m, i) => <Bubble key={i} msg={m} />)}
      </div>

      <div className="composer">
        <div className="composer-tools">
          <div className="agent-pick">
            {AGENTS.map((x) => (
              <button key={x.id} className={x.id === agent ? 'on' : ''} onClick={() => setAgent(x.id)}>
                <span className="agent-dot" style={{ background: x.color, color: x.color }}></span>
                {x.name}
              </button>
            ))}
          </div>
          <div style={{ flex: 1 }}></div>
          {['memory.read', 'kb.search', 'web.search'].map((id) => {
            const t = TOOLS.find((tt) => tt.id === id);
            return (
              <button key={id} className={'tool-btn' + (tools[id] ? ' on' : '')} onClick={() => setTools({ ...tools, [id]: !tools[id] })}>
                <Icon name="wrench" size={11} /> {t.name}
              </button>
            );
          })}
        </div>
        <div className="input-row">
          <textarea
            placeholder={`Ask ${a.name} something… (Enter to send · Shift+Enter for newline)`}
            value={draft}
            onChange={(e) => setDraft(e.target.value)}
            onKeyDown={handleKey}
            rows={1}
          />
          <button className="send-btn" disabled={pending || !draft.trim()} onClick={onSend} title="Send">
            <Icon name="send" size={16} />
          </button>
        </div>
      </div>
    </section>
  );
}

function EmptyState({ onPick, agent }) {
  const prompts = {
    artemis: [
      ['Route', 'Route this question to the right agent: which kernel topics handle download progress?'],
      ['Decompose', 'Break down "ship governance audit dashboard" into a plan.'],
      ['Inspect', 'Show me which agents have low trust scores right now.'],
      ['Quote', 'Summarize the kernel-first philosophy in three sentences I can paste in a deck.'],
    ],
    planner: [
      ['Plan', 'Plan a 3-day spike to add Notion two-way sync.'],
      ['Risks', 'Identify governance risks in shipping tool-execute to the public beta.'],
      ['Checklist', 'Generate a release checklist for ATP v0.5.'],
      ['Order', 'Order these tasks by dependency: vault sync, schema migration, audit log, UI.'],
    ],
    research: [
      ['Synthesize', 'What does my vault say about Hebbian routing benchmarks?'],
      ['Cite', 'Find sources in Notion that mention "trust decay."'],
      ['Compare', 'Compare ATP v0.3 and v0.4 envelopes — what changed?'],
      ['Crosswalk', 'Crosswalk our memory bus terms with MCP terms.'],
    ],
    summarizer: [
      ['TL;DR', 'TL;DR the Q1 Artemis OKR doc for the board.'],
      ['Brief', 'One-paragraph brief on quantum security crossover.'],
      ['Distill', 'Distill last week\'s desk notes into 5 bullets.'],
      ['Hook', 'Give me a 50-word hook for the kernel-first essay.'],
    ],
  };
  const list = prompts[agent.id] || prompts.artemis;
  return (
    <div className="empty-state">
      <h2>Talk to {agent.name}</h2>
      <p>{agent.desc} The kernel decides routing — not the LLM. Try one:</p>
      <div className="empty-prompts">
        {list.map(([eye, p], i) => (
          <button key={i} className="empty-prompt" onClick={() => onPick(p)}>
            <span className="eye">{eye}</span>
            {p}
          </button>
        ))}
      </div>
    </div>
  );
}

function Bubble({ msg }) {
  const a = AGENTS.find((x) => x.id === msg.agent) || AGENTS[0];
  const isUser = msg.role === 'user';
  return (
    <div className={'msg' + (isUser ? ' user' : '')}>
      <div className="av" style={{ background: isUser ? undefined : a.color }}>
        {isUser ? 'YOU' : a.name[0]}
      </div>
      <div className="bubble">
        {!isUser && (
          <div className="who"><b>{a.name}</b><span>· {a.role.toLowerCase()}</span></div>
        )}
        {msg.typing && msg.content === '' ? <span className="typing"><span></span><span></span><span></span></span> : renderMd(msg.content)}
      </div>
    </div>
  );
}

/* Minimal markdown: bold, code, inline `code`, headings (#), list dashes, links omitted. */
function renderMd(text) {
  if (!text) return null;
  const parts = text.split('\n');
  return parts.map((line, i) => {
    let l = line;
    // bold
    l = l.replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>');
    // inline code
    l = l.replace(/`([^`]+)`/g, '<code style="font-family: var(--font-mono); font-size: 0.9em; background: rgba(255,255,255,0.08); padding: 1px 6px; border-radius: 4px; color: #67e8f9;">$1</code>');
    // citations [obsidian:...] [notion:...]
    l = l.replace(/\[(obsidian|notion|vector):([^\]]+)\]/g, '<span class="citation">$1 · $2</span>');
    return <div key={i} style={{ minHeight: line === '' ? '0.6em' : undefined }} dangerouslySetInnerHTML={{ __html: l }} />;
  });
}

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