// 真实数据版 App：从本地 daemon 的 REST 拉取、订阅 WS 增量刷新，
// 通过 src/adapt.js 映射成各 surface 期望的形状（替代 window.MAESTRO_MOCK）。
import { createRoot } from 'react-dom/client';
import { api, subscribeEvents } from './api.js';
import {
  adaptProject, buildTaskTree, adaptApproval, adaptEvent, adaptQuota,
  deriveGlobal, deriveAgentSummary, adaptActiveAgents,
} from './adapt.js';

const { Toast } = window.MaestroDesignSystem_a6a290;

const USER = { name: 'local', handle: '@local', plan: 'Claude Code', initial: 'L', hue: 200 };

// 新建项目模态（design 无现成 surface，用 DS 组件就地拼装）。标签 zh/en 自含。
function NewProjectModal({ t, lang, onCreate, onClose }) {
  const { Button, Input, Select } = window.MaestroDesignSystem_a6a290;
  const L = lang === 'zh'
    ? { title: '新建项目', name: '项目名', repo: '仓库路径', branch: '默认分支', mode: '工作模式', namePh: 'my-app', repoPh: '/Users/you/code/my-app', create: '创建' }
    : { title: 'New project', name: 'Name', repo: 'Repo path', branch: 'Default branch', mode: 'Mode', namePh: 'my-app', repoPh: '/Users/you/code/my-app', create: 'Create' };
  const [name, setName] = React.useState('');
  const [repoPath, setRepoPath] = React.useState('');
  const [defaultBranch, setDefaultBranch] = React.useState('main');
  const [autonomy, setAutonomy] = React.useState('manual');
  const submit = (e) => {
    e.preventDefault();
    if (!name.trim() || !repoPath.trim()) return;
    onCreate({ name: name.trim(), repoPath: repoPath.trim(), defaultBranch: defaultBranch.trim() || 'main', autonomy });
  };
  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 1100, display: 'grid', placeItems: 'center' }}>
      <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(4,6,5,.86)', backdropFilter: 'blur(3px)' }}></div>
      <form onSubmit={submit} style={{ position: 'relative', width: 'min(520px, 92vw)', background: 'var(--panel)', border: '1px solid var(--green-dim)', borderRadius: 'var(--radius-lg,10px)', padding: 18, boxShadow: '0 0 0 1px var(--line-soft), 0 24px 64px rgba(0,0,0,.6)', animation: 'maestro-rise .18s ease both' }}>
        <div style={{ fontWeight: 700, fontSize: 14, letterSpacing: '.06em', marginBottom: 14, color: 'var(--green)' }}>▍ {L.title}</div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <Input label={L.name} required placeholder={L.namePh} value={name} onChange={setName} style={{ width: '100%' }} />
          <Input label={L.repo} required placeholder={L.repoPh} value={repoPath} onChange={setRepoPath} style={{ width: '100%' }} />
          <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
            <Input label={L.branch} value={defaultBranch} onChange={setDefaultBranch} width={140} />
            <Select label={L.mode} value={autonomy} onChange={setAutonomy} options={Object.entries(t.autonomy).map(([value, label]) => ({ value, label }))} />
          </div>
        </div>
        <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 18, paddingTop: 12, borderTop: '1px solid var(--line-soft)' }}>
          <Button onClick={onClose}>{t.cancel}</Button>
          <Button variant="solid" type="submit">{L.create}</Button>
        </div>
      </form>
    </div>
  );
}

// SDK 流式消息 → 一行可读文本（助手文本 / 工具调用 / 终态 / maestro 元事件）
function summarizeMessage(m) {
  if (!m || !m.type) return '';
  const content = m.message && m.message.content;
  if (m.type === 'assistant' && Array.isArray(content)) {
    return content.map((c) => (c.type === 'text' ? c.text : c.type === 'tool_use' ? `⚙ ${c.name}` : '')).filter(Boolean).join('  ');
  }
  if (m.type === 'user' && Array.isArray(content)) {
    return content.some((c) => c.type === 'tool_result') ? '↩ tool result' : '';
  }
  if (m.type === 'result') return `■ ${m.subtype || 'done'}`;
  if (typeof m.type === 'string' && m.type.startsWith('maestro.')) return `· ${m.type}`;
  return '';
}

// 实时流模态：EventSource 跟随某 run 的 transcript，逐条渲染 agent 输出
function StreamModal({ agent, onClose }) {
  const [lines, setLines] = React.useState([]);
  const [status, setStatus] = React.useState('connecting');
  const boxRef = React.useRef(null);
  React.useEffect(() => {
    const es = new EventSource(`/api/tasks/${agent.taskId}/stream`);
    es.addEventListener('open', () => setStatus('streaming'));
    es.onmessage = (e) => {
      let text = '';
      try { text = summarizeMessage(JSON.parse(e.data)); } catch { text = ''; }
      if (text) setLines((ls) => [...ls.slice(-400), text]);
    };
    es.addEventListener('end', () => { setStatus('done'); es.close(); });
    es.onerror = () => { setStatus('disconnected'); es.close(); };
    return () => es.close();
  }, [agent.taskId]);
  React.useEffect(() => { if (boxRef.current) boxRef.current.scrollTop = boxRef.current.scrollHeight; }, [lines]);
  const dot = { connecting: 'var(--amber)', streaming: 'var(--cyan)', done: 'var(--green)', disconnected: 'var(--faint)' }[status];
  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 1100, display: 'grid', placeItems: 'center' }}>
      <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(4,6,5,.86)', backdropFilter: 'blur(3px)' }}></div>
      <div style={{ position: 'relative', display: 'flex', flexDirection: 'column', width: 'min(960px,94vw)', height: '82vh', background: 'var(--panel)', border: '1px solid var(--cyan-dim)', borderRadius: 'var(--radius-lg,10px)', overflow: 'hidden', boxShadow: '0 0 0 1px var(--line-soft), 0 24px 64px rgba(0,0,0,.6)' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '12px 16px', borderBottom: '1px solid var(--line)', background: 'var(--panel-2)' }}>
          <span style={{ width: 7, height: 7, borderRadius: '50%', background: dot, boxShadow: `0 0 8px ${dot}` }}></span>
          <b style={{ fontSize: 13 }}>{agent.title}</b>
          <span style={{ fontSize: 10, letterSpacing: '.1em', color: 'var(--cyan)', border: '1px solid var(--cyan-dim)', padding: '0 6px', borderRadius: 3 }}>{agent.kind}</span>
          <span style={{ fontSize: 11, color: 'var(--faint)' }}>实时输出 · {status}</span>
          <span style={{ flex: 1 }}></span>
          <button onClick={onClose} style={{ fontFamily: 'var(--mono)', fontSize: 13, background: 'transparent', color: 'var(--muted)', border: 'none', cursor: 'pointer' }}>✕</button>
        </div>
        <div ref={boxRef} style={{ flex: 1, overflowY: 'auto', padding: '12px 16px', fontFamily: 'var(--mono)', fontSize: 12.5, lineHeight: 1.6, whiteSpace: 'pre-wrap', wordBreak: 'break-word', color: 'var(--ink)' }}>
          {lines.length ? lines.map((l, i) => <div key={i} style={{ paddingBottom: 2 }}>{l}</div>)
            : <div style={{ color: 'var(--faint)' }}>等待输出…</div>}
        </div>
      </div>
    </div>
  );
}

export function App() {
  const I18N = window.MAESTRO_I18N;
  const [lang, setLang] = React.useState(() => {
    const saved = localStorage.getItem('maestro-kit-lang');
    return I18N[saved] ? saved : 'zh';
  });
  const t = I18N[lang];

  // ── 真实数据 state ──────────────────────────────────
  const [projectsRaw, setProjectsRaw] = React.useState([]);
  const [tasksRaw, setTasksRaw] = React.useState([]);
  const [approvalsRaw, setApprovalsRaw] = React.useState([]);
  const [eventsRaw, setEventsRaw] = React.useState([]);
  const [agentsResp, setAgentsResp] = React.useState({ totalActive: 0, agents: [] });
  const [usage, setUsage] = React.useState(null);
  const [currentId, setCurrentId] = React.useState(null);
  const [wsState, setWsState] = React.useState('closed');

  // ── UI state（沿用原型）─────────────────────────────
  const [showConfig, setShowConfig] = React.useState(false);
  const [archiveItem, setArchiveItem] = React.useState(null);
  const [streamAgent, setStreamAgent] = React.useState(null);
  const [toasts, setToasts] = React.useState([]);
  const [theme, setTheme] = React.useState(() => localStorage.getItem('maestro-kit-theme') || 'dark');
  React.useEffect(() => {
    document.documentElement.dataset.theme = theme;
    localStorage.setItem('maestro-kit-theme', theme);
  }, [theme]);
  React.useEffect(() => { localStorage.setItem('maestro-kit-lang', lang); }, [lang]);
  const [collapsed, setCollapsed] = React.useState(() => localStorage.getItem('maestro-kit-sidebar') === 'collapsed');
  const [evCollapsed, setEvCollapsed] = React.useState(() => localStorage.getItem('maestro-kit-events') === 'collapsed');
  const toggleEvents = () => setEvCollapsed((c) => { localStorage.setItem('maestro-kit-events', c ? 'open' : 'collapsed'); return !c; });
  const toggleCollapse = () => setCollapsed((c) => { localStorage.setItem('maestro-kit-sidebar', c ? 'open' : 'collapsed'); return !c; });
  const SIDE_MIN = 200, SIDE_MAX = 420, EV_MIN = 240, EV_MAX = 520, CENTER_MIN = 480;
  const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
  const sideMax = () => Math.min(SIDE_MAX, window.innerWidth - (evCollapsed ? 52 : evW) - CENTER_MIN);
  const evMax = () => Math.min(EV_MAX, window.innerWidth - (collapsed ? 52 : sideW) - CENTER_MIN);
  const [sideW, setSideW] = React.useState(() => clamp(Number(localStorage.getItem('maestro-kit-sidew')) || 232, SIDE_MIN, SIDE_MAX));
  const [evW, setEvW] = React.useState(() => clamp(Number(localStorage.getItem('maestro-kit-evw')) || 320, EV_MIN, EV_MAX));
  const [dragging, setDragging] = React.useState(null);
  React.useEffect(() => { localStorage.setItem('maestro-kit-sidew', sideW); }, [sideW]);
  React.useEffect(() => { localStorage.setItem('maestro-kit-evw', evW); }, [evW]);
  const startDrag = (which) => (e) => {
    e.preventDefault();
    setDragging(which);
    const startX = e.clientX;
    const startW = which === 'side' ? sideW : evW;
    const onMove = (ev) => {
      if (which === 'side') setSideW(clamp(startW + (ev.clientX - startX), SIDE_MIN, Math.max(SIDE_MIN, sideMax())));
      else setEvW(clamp(startW - (ev.clientX - startX), EV_MIN, Math.max(EV_MIN, evMax())));
    };
    const onUp = () => {
      setDragging(null);
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
      document.body.style.userSelect = '';
    };
    document.body.style.userSelect = 'none';
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onUp);
  };
  const Resizer = ({ which }) => (
    <div onPointerDown={startDrag(which)} title="拖拽调整宽度"
      style={{
        position: 'absolute', top: 0, bottom: 0, width: 9, cursor: 'col-resize', zIndex: 40,
        ...(which === 'side' ? { left: sideW - 4 } : { right: evW - 4 }),
        display: 'flex', justifyContent: 'center',
      }}>
      <span style={{ width: 1, background: dragging === which ? 'var(--green)' : 'var(--line-soft)', boxShadow: dragging === which ? '0 0 8px var(--green)' : 'none', transition: 'background .12s' }}></span>
    </div>
  );

  // ── 数据加载 ────────────────────────────────────────
  const toast = (kind, text) => {
    const id = Date.now() + Math.random();
    setToasts((list) => [...list, { id, kind, text }]);
    setTimeout(() => setToasts((list) => list.filter((x) => x.id !== id)), 2600);
  };

  const loadGlobal = React.useCallback(async () => {
    try {
      const [pj, ag, us, ap] = await Promise.all([api.listProjects(), api.agents(), api.usage(), api.approvals()]);
      setProjectsRaw(pj); setAgentsResp(ag); setUsage(us); setApprovalsRaw(ap);
      setCurrentId((cur) => cur || (pj[0] && pj[0].id) || null);
    } catch (e) { toast('warn', '加载失败：' + e.message); }
  }, []);

  const loadProject = React.useCallback(async (pid) => {
    if (!pid) return;
    try {
      const [tasks, events] = await Promise.all([api.projectTasks(pid), api.projectEvents(pid)]);
      setTasksRaw(tasks);
      setEventsRaw(events.slice(0, 60));
    } catch (e) { toast('warn', '加载任务失败：' + e.message); }
  }, []);

  React.useEffect(() => { loadGlobal(); }, [loadGlobal]);
  React.useEffect(() => { loadProject(currentId); }, [currentId, loadProject]);

  // WS：增量刷新。事件即时入流；任务/审批/agent 做轻量去抖刷新。
  const refreshTimer = React.useRef(null);
  React.useEffect(() => {
    const unsub = subscribeEvents((evt) => {
      if (!evt || !evt.type) return;
      if (!evt.projectId || evt.projectId === currentId) {
        setEventsRaw((list) => [evt, ...list].slice(0, 60));
      }
      if (evt.type === 'budget.exceeded') {
        const pay = evt.payload || {};
        toast('warn', `⚠ 项目超预算已暂停 · 已用 $${(pay.spend ?? 0).toFixed?.(2) ?? pay.spend} / $${pay.budget}`);
      }
      clearTimeout(refreshTimer.current);
      refreshTimer.current = setTimeout(() => {
        loadProject(currentId);
        api.approvals().then(setApprovalsRaw).catch(() => {});
        api.agents().then(setAgentsResp).catch(() => {});
        api.listProjects().then(setProjectsRaw).catch(() => {});
      }, 350);
    }, setWsState);
    return () => { unsub(); clearTimeout(refreshTimer.current); };
  }, [currentId, loadProject]);

  // ── 派生：映射成 surface 形状 ───────────────────────
  const projects = projectsRaw.map(adaptProject);
  const project = projects.find((p) => p.id === currentId) || projects[0];
  const projNameOf = (pid) => (projectsRaw.find((p) => p.id === pid) || {}).name;
  const { active: tasks, archived } = buildTaskTree(tasksRaw);
  const REVIEW = new Set(['plan_review', 'spec_review', 'exec_review']);
  const gates = approvalsRaw
    .filter((a) => a.projectId === currentId && REVIEW.has(a.status))
    .map((a) => adaptApproval(a, projNameOf(a.projectId)));
  const agents = adaptActiveAgents(agentsResp, currentId);
  const events = eventsRaw.map(adaptEvent);
  const quota = adaptQuota(usage);
  const global = deriveGlobal(projects, agentsResp);
  const agentSummary = deriveAgentSummary(projects, agentsResp, usage?.cost);
  const NONTERMINAL = (s) => !['done', 'cancelled', 'decomposed'].includes(s);
  const counts = {
    gate: gates.length,
    ready: tasksRaw.filter((x) => x.projectId === currentId && (x.status === 'ready' || x.status === 'queued')).length,
    run: tasksRaw.filter((x) => x.projectId === currentId && x.status === 'executing').length,
    blocked: tasksRaw.filter((x) => x.projectId === currentId && x.status === 'blocked').length,
    total: tasksRaw.filter((x) => x.projectId === currentId && NONTERMINAL(x.status)).length,
  };

  // ── 动作（真实写入）────────────────────────────────
  const decide = async (a, action, reason) => {
    setApprovalsRaw((list) => list.filter((x) => x.id !== a.id)); // 乐观移除
    try {
      await api.decide(a.id, action, reason);
      toast(action === 'accept' ? 'ok' : 'warn', (action === 'accept' ? t.toastAccepted : t.toastRejected) + (action === 'accept' ? a.title : ''));
    } catch (e) {
      toast('warn', '裁决失败：' + e.message);
      api.approvals().then(setApprovalsRaw).catch(() => {}); // 回滚
    }
  };
  const onSync = async () => {
    try { await api.syncProject(currentId); toast('ok', t.toastSynced); loadProject(currentId); loadGlobal(); }
    catch (e) { toast('warn', '同步失败：' + e.message); }
  };
  const createTask = async (payload) => {
    const { files, ...body } = payload;
    try {
      const task = await api.createTask(currentId, body);
      if (files && files.length) {
        try { await api.uploadAttachments(task.id, files); toast('ok', `附件已上传 · ${files.length} 个`); }
        catch (e) { toast('warn', e.message); }
      }
      toast('ok', t.toastCreated);
      loadProject(currentId); loadGlobal();
    } catch (e) { toast('warn', '创建失败：' + e.message); }
  };
  const saveConfig = async (patch) => {
    try {
      await api.patchProject(currentId, patch);
      setShowConfig(false); toast('ok', t.toastSaved); loadGlobal();
    } catch (e) { toast('warn', '保存失败：' + e.message); }
  };
  const [showNewProject, setShowNewProject] = React.useState(false);
  const createProject = async (body) => {
    try {
      const p = await api.createProject(body);
      setShowNewProject(false); toast('ok', '项目已创建 · ' + body.name);
      await loadGlobal(); setCurrentId(p.id);
    } catch (e) { toast('warn', '创建项目失败：' + e.message); }
  };

  return (
    <div style={{ position: 'relative', display: 'grid', gridTemplateColumns: (collapsed ? '52px' : sideW + 'px') + ' minmax(0,1fr) ' + (evCollapsed ? '52px' : evW + 'px'), height: '100vh' }}>
      {!collapsed ? <Resizer which="side" /> : null}
      {!evCollapsed ? <Resizer which="ev" /> : null}
      <window.MaestroKitSidebar projects={projects} currentId={currentId} t={t}
        global={global} user={USER} summary={agentSummary} onGlobalConfig={() => toast('warn', t.toastModal)}
        collapsed={collapsed} onToggleCollapse={toggleCollapse}
        onSelect={setCurrentId} onNewProject={() => setShowNewProject(true)} />
      <main style={{ overflowY: 'auto', padding: '0 22px 60px' }}>
        {project ? <window.MaestroKitTopbar project={project} t={t} theme={theme}
          onToggleTheme={() => setTheme(theme === 'light' ? 'dark' : 'light')}
          lang={lang} onSelectLang={setLang}
          counts={counts}
          onSync={onSync}
          onToggleConfig={() => setShowConfig(!showConfig)} /> : null}
        {showConfig && project ? <window.MaestroKitConfigPanel project={project} t={t} lang={lang}
          onSave={saveConfig}
          onSync={onSync}
          onClose={() => setShowConfig(false)} /> : null}
        <window.MaestroKitAgentSection agents={agents} quota={quota} t={t} onOpenStream={setStreamAgent} />
        <window.MaestroKitGateSection approvals={gates} onDecide={decide} t={t} />
        {tasks.length ? (
          <window.MaestroKitTaskSection tasks={tasks} onToast={toast} onCreate={createTask} t={t} />
        ) : (
          <div style={{ padding: '46px 0', textAlign: 'center', color: 'var(--faint)', border: '1px dashed var(--line)', borderRadius: 6, marginTop: 18 }}>
            <b style={{ display: 'block', fontSize: 15, color: 'var(--muted)', marginBottom: 6, letterSpacing: '.2em' }}>{t.empty}</b>
            {t.emptyTasks}
          </div>
        )}
        <window.MaestroKitArchiveSection items={archived} t={t} onOpen={setArchiveItem} />
      </main>
      <window.MaestroKitEventPanel events={events} collapsed={evCollapsed} onToggleCollapse={toggleEvents} t={t} />
      {archiveItem ? <window.MaestroKitArchiveModal item={archiveItem} t={t} onClose={() => setArchiveItem(null)} /> : null}
      {showNewProject ? <NewProjectModal t={t} lang={lang} onCreate={createProject} onClose={() => setShowNewProject(false)} /> : null}
      {streamAgent ? <StreamModal agent={streamAgent} onClose={() => setStreamAgent(null)} /> : null}
      <div style={{ position: 'fixed', bottom: 18, left: '50%', transform: 'translateX(-50%)', zIndex: 1200, display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center' }}>
        {toasts.map((x) => <Toast key={x.id} kind={x.kind}>{x.text}</Toast>)}
      </div>
    </div>
  );
}

export function mount() {
  createRoot(document.getElementById('root')).render(<App />);
}
