// Phase 2B — Custom Agents panel.
//
// A focused CRUD UI on top of the existing /api/vault/note plumbing. Lists
// every agent in brain/0-Agents/ with metadata, lets the user create new
// custom agents, edit their bodies, and delete the ones they own. Built-in
// agents (the 10 ships-with-the-repo personas) are flagged read-only here;
// the Brain tab editor can still modify them if the user really wants.
//
// On save, we POST/PUT to /api/agents/* which writes the markdown file with
// proper YAML frontmatter (`type: agent-prompt`, role, title, created_by,
// created_at, optional model + tagline) so load_roster() picks it up on the
// next team run. The backend reload_prompt is called automatically.

const { useState: agUseState, useEffect: agUseEffect, useCallback: agUseCallback, useRef: agUseRef } = React;

function AgentsPanel({ open, onClose }) {
  if (!open) return null;
  const [roster, setRoster] = agUseState([]);
  const [loading, setLoading] = agUseState(false);
  const [err, setErr] = agUseState('');
  const [activeRole, setActiveRole] = agUseState(null);
  const [mode, setMode] = agUseState('view'); // 'view' | 'edit' | 'new'
  const [draft, setDraft] = agUseState({ title: '', tagline: '', model: '', body: '' });
  const [saving, setSaving] = agUseState(false);

  const refresh = agUseCallback(async () => {
    setLoading(true);
    setErr('');
    try {
      const r = await fetch('/api/agents/roster');
      if (!r.ok) {
        // Surface the actual response body so the user sees WHY the
        // request failed (e.g. 'token expired' vs 'invalid token: …').
        // The fetch interceptor in index.html already retries 401s
        // with refreshed tokens — if we still got a non-200 here, the
        // soft-fallback didn't catch it either, which is informative.
        let detail = '';
        try {
          const txt = await r.text();
          // Detail might be JSON {"detail":"..."} or plain text
          try { detail = (JSON.parse(txt) || {}).detail || txt; }
          catch { detail = txt; }
        } catch {}
        throw new Error(`HTTP ${r.status}${detail ? ` · ${String(detail).slice(0, 140)}` : ''}`);
      }
      const j = await r.json();
      const agents = j.agents || [];
      if (!Array.isArray(agents)) {
        throw new Error(`bad response shape — agents field is ${typeof agents}`);
      }
      setRoster(agents);
      if (agents.length === 0) {
        // Backend returned 200 but with no personas. The brain volume
        // probably hasn't been seeded with the persona files. Surface
        // a hint that points at the fix instead of leaving the user
        // staring at an empty list.
        setErr('Roster is empty — backend returned 200 but found 0 personas. Try POST /api/admin/sync_brain to copy the image\'s 0-Agents/ files onto the volume.');
      }
    } catch (e) {
      setErr(`Failed to load roster — ${e.message || e}`);
    } finally {
      setLoading(false);
    }
  }, []);

  agUseEffect(() => { if (open) refresh(); }, [open, refresh]);

  const active = roster.find(a => a.role === activeRole) || null;

  const startNew = () => {
    setActiveRole(null);
    setMode('new');
    setDraft({
      title: '',
      tagline: '',
      model: '',
      body: 'You are a [DESCRIBE THE AGENT] analyst. Your lens:\n\n- [Lens point 1]\n- [Lens point 2]\n- [Lens point 3]\n\nWhen you analyze a thesis:\n1. [How you start]\n2. [What you weigh]\n3. [How you conclude]\n\nVoice: [terse / discursive / contrarian / etc.]\n',
    });
  };

  const startEdit = (agent) => {
    setActiveRole(agent.role);
    setMode('edit');
    setDraft({
      title: agent.title || '',
      tagline: agent.tagline || '',
      model: agent.model || '',
      body: agent.body || '',
    });
  };

  const cancelEdit = () => {
    setMode('view');
    setDraft({ title: '', tagline: '', model: '', body: '' });
  };

  const save = async () => {
    if (saving) return;
    setSaving(true);
    setErr('');
    try {
      const body = (draft.body || '').trim();
      if (body.length < 40) {
        throw new Error('Body needs at least 40 characters — describe how this agent reasons.');
      }
      if (mode === 'new') {
        if (!draft.title.trim()) throw new Error('Title is required.');
        const r = await fetch('/api/agents/create', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            title: draft.title.trim(),
            body,
            tagline: draft.tagline.trim() || undefined,
            model: draft.model.trim() || undefined,
            created_by: (window.TIBEB_USER && window.TIBEB_USER.email) || 'user',
          }),
        });
        if (!r.ok) {
          const t = await r.text().catch(() => '');
          throw new Error(`Create failed (${r.status}): ${t.slice(0, 140)}`);
        }
        const j = await r.json();
        await refresh();
        setActiveRole(j.role);
        setMode('view');
      } else if (mode === 'edit' && activeRole) {
        const r = await fetch(`/api/agents/${encodeURIComponent(activeRole)}`, {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            body,
            tagline: draft.tagline.trim(),
            model: draft.model.trim(),
          }),
        });
        if (!r.ok) {
          const t = await r.text().catch(() => '');
          throw new Error(`Save failed (${r.status}): ${t.slice(0, 140)}`);
        }
        await refresh();
        setMode('view');
      }
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setSaving(false);
    }
  };

  const deleteAgent = async () => {
    if (!active || !active.is_custom) return;
    if (!window.confirm(`Delete "${active.title}"? This removes the markdown file from brain/0-Agents/. The next team run won't include it.`)) return;
    setSaving(true);
    setErr('');
    try {
      const r = await fetch(`/api/agents/${encodeURIComponent(active.role)}`, { method: 'DELETE' });
      if (!r.ok) {
        const t = await r.text().catch(() => '');
        throw new Error(`Delete failed (${r.status}): ${t.slice(0, 140)}`);
      }
      setActiveRole(null);
      setMode('view');
      await refresh();
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setSaving(false);
    }
  };

  const builtIn = roster.filter(a => !a.is_custom);
  const custom  = roster.filter(a =>  a.is_custom);

  return (
    <div className="agents-overlay" onClick={(e) => { if (e.target.classList.contains('agents-overlay')) onClose(); }}>
      <div className="agents-shell">
        <div className="agents-head">
          <div>
            <div className="agents-eyebrow">Custom analysts · brain/0-Agents/</div>
            <div className="agents-title">Build your <em>own analyst team</em></div>
            <div className="agents-sub">Each agent is a Markdown file with a personality + lens. The team rotates through every agent in the roster on Mad Max + Team runs. Built-ins lock you in to a specific framework lineup; custom agents let you add your own (e.g. "TMT contrarian", "energy specialist", "crypto skeptic").</div>
          </div>
          <button className="agents-close" onClick={onClose}>Close</button>
        </div>

        <div className="agents-grid">
          {/* Left: roster list */}
          <div className="agents-list">
            <button className="agents-new-btn" onClick={startNew}>+ New custom agent</button>
            {loading && <div className="agents-loading">Loading roster…</div>}
            {err && !loading && (
              <div className="agents-err" style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                <span>{err}</span>
                <button onClick={refresh} style={{
                  padding: '4px 10px', fontSize: 10,
                  fontFamily: "'JetBrains Mono', monospace",
                  background: 'transparent', color: 'var(--accent)',
                  border: '1px solid var(--accent)', borderRadius: 3,
                  cursor: 'pointer', alignSelf: 'flex-start',
                }}>
                  ↻ Retry
                </button>
              </div>
            )}

            {custom.length > 0 && (
              <>
                <div className="agents-group-label">Yours ({custom.length})</div>
                {custom.map(a => (
                  <button
                    key={a.role}
                    className={`agents-row ${activeRole === a.role ? 'active' : ''} custom`}
                    onClick={() => { setActiveRole(a.role); setMode('view'); }}
                  >
                    <span className="agents-row-title">{a.title}</span>
                    <span className="agents-row-tag">custom</span>
                  </button>
                ))}
              </>
            )}

            <div className="agents-group-label">Built-in ({builtIn.length})</div>
            {builtIn.map(a => (
              <button
                key={a.role}
                className={`agents-row ${activeRole === a.role ? 'active' : ''} ${a.is_synthesizer ? 'synth' : ''}`}
                onClick={() => { setActiveRole(a.role); setMode('view'); }}
              >
                <span className="agents-row-title">{a.title}</span>
                <span className="agents-row-tag">{a.is_synthesizer ? 'synth' : 'built-in'}</span>
              </button>
            ))}
          </div>

          {/* Right: detail / editor */}
          <div className="agents-detail">
            {mode === 'new' && (
              <AgentEditor
                draft={draft} setDraft={setDraft}
                isNew={true}
                saving={saving} err={err}
                onSave={save} onCancel={cancelEdit}
              />
            )}

            {mode === 'edit' && active && (
              <AgentEditor
                draft={draft} setDraft={setDraft}
                isNew={false}
                isCustom={active.is_custom}
                saving={saving} err={err}
                onSave={save} onCancel={cancelEdit}
              />
            )}

            {mode === 'view' && active && (
              <div className="agents-viewer">
                <div className="agents-viewer-head">
                  <div>
                    <div className="agents-viewer-title">{active.title}</div>
                    <div className="agents-viewer-meta">
                      <span className="agents-meta-chip">role: <code>{active.role}</code></span>
                      {active.model && <span className="agents-meta-chip">model: <code>{active.model}</code></span>}
                      {active.tagline && <span className="agents-meta-tag">{active.tagline}</span>}
                    </div>
                    {active.created_by && (
                      <div className="agents-viewer-author">By {active.created_by}{active.created_at ? ` · ${active.created_at}` : ''}</div>
                    )}
                    <div className="agents-viewer-path">
                      <code>{active.file_path}</code>
                    </div>
                  </div>
                  <div className="agents-viewer-actions">
                    <button className="agents-action-btn" onClick={() => startEdit(active)}>
                      ✎ Edit
                    </button>
                    {active.is_custom && (
                      <button className="agents-action-btn danger" onClick={deleteAgent} disabled={saving}>
                        ✕ Delete
                      </button>
                    )}
                  </div>
                </div>
                <pre className="agents-viewer-body">{active.body}</pre>
                {!active.is_custom && (
                  <div className="agents-builtin-note">
                    Built-in agent. Edits ship for everyone — only do it if you're improving the framework. To make a one-off, click <b>+ New custom agent</b> and copy the body as a starting point.
                  </div>
                )}
              </div>
            )}

            {mode === 'view' && !active && (
              <div className="agents-empty">
                <div style={{ fontSize: 14, color: 'var(--text-2)', marginBottom: 8 }}>Select an agent on the left to view its prompt.</div>
                <div style={{ fontSize: 12, color: 'var(--text-dim)', lineHeight: 1.55 }}>
                  Each agent is a markdown file in <code>brain/0-Agents/</code> with a frontmatter contract. The TeamRunner auto-discovers them and the Mad Max overlay rotates through every analyst, one per iteration. Build your own when you want a lens the built-ins don't cover.
                </div>
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

function AgentEditor({ draft, setDraft, isNew, isCustom, saving, err, onSave, onCancel }) {
  const titleRef = agUseRef(null);
  agUseEffect(() => {
    if (isNew && titleRef.current) titleRef.current.focus();
  }, [isNew]);
  return (
    <div className="agents-editor">
      <div className="agents-editor-head">
        <div className="agents-eyebrow">{isNew ? 'New custom agent' : 'Edit agent'}</div>
        {!isNew && !isCustom && (
          <div className="agents-warning">⚠ This is a built-in agent. Edits affect every team run for every user.</div>
        )}
      </div>

      <label className="agents-field">
        <span className="agents-label">Title</span>
        <input
          ref={titleRef}
          className="agents-input"
          type="text"
          value={draft.title}
          disabled={!isNew}
          onChange={e => setDraft(d => ({ ...d, title: e.target.value }))}
          placeholder="e.g. TMT Contrarian"
          maxLength={80}
        />
        {!isNew && <span className="agents-hint">Title is immutable — changing it would orphan the file.</span>}
      </label>

      <label className="agents-field">
        <span className="agents-label">Tagline (optional)</span>
        <input
          className="agents-input"
          type="text"
          value={draft.tagline}
          onChange={e => setDraft(d => ({ ...d, tagline: e.target.value }))}
          placeholder="One line about how this agent thinks"
          maxLength={120}
        />
      </label>

      <label className="agents-field">
        <span className="agents-label">Model override (optional)</span>
        <input
          className="agents-input"
          type="text"
          value={draft.model}
          onChange={e => setDraft(d => ({ ...d, model: e.target.value }))}
          placeholder="claude-opus-4-7 (default), claude-sonnet-4-6, claude-haiku-4-5"
        />
        <span className="agents-hint">Leave blank for the team default. Override only if this agent benefits from a different tier (e.g. a fast scanner agent on haiku).</span>
      </label>

      <label className="agents-field">
        <span className="agents-label">Prompt body (markdown)</span>
        <textarea
          className="agents-textarea"
          value={draft.body}
          onChange={e => setDraft(d => ({ ...d, body: e.target.value }))}
          rows={18}
        />
        <span className="agents-hint">This is the agent's personality + reasoning lens. Min 40 characters. Use markdown for structure. The TeamRunner injects this verbatim into the system prompt for every iteration where this agent runs.</span>
      </label>

      {err && <div className="agents-err">⚠ {err}</div>}

      <div className="agents-editor-actions">
        <button className="agents-action-btn" onClick={onCancel} disabled={saving}>Cancel</button>
        <button className="agents-action-btn primary" onClick={onSave} disabled={saving}>
          {saving ? 'Saving…' : (isNew ? 'Create agent' : 'Save changes')}
        </button>
      </div>
    </div>
  );
}

window.AgentsPanel = AgentsPanel;
