// ── 报告渲染：把 markdown 按「## 章节」切成可折叠卡片，章节内容交 window.MaestroRichDoc 渲染 ──
function mdSections(md) {
  if (!md) return [];
  const out = []; const lines = md.split('\n');
  let cur = null;
  for (const line of lines) {
    const h = line.match(/^##\s+(.+)$/);
    if (h) { if (cur) out.push(cur); cur = { title: h[1].trim(), body: '' }; }
    else if (cur) { cur.body += (cur.body ? '\n' : '') + line; }
    else { if (line.trim()) { cur = { title: '', body: line }; } }   // 首章节前的前言
  }
  if (cur) out.push(cur);
  return out;
}
function ReportSectionCard({ title, body, t, defaultOpen }) {
  const [open, setOpen] = React.useState(defaultOpen);
  return (
    <div style={{ background: 'var(--bg-deep)', border: '1px solid var(--line-soft)', borderRadius: 6, overflow: 'hidden' }}>
      <div onClick={() => setOpen((o) => !o)} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '9px 12px', cursor: 'pointer', background: 'var(--panel-2)', userSelect: 'none' }}>
        <span style={{ flex: 'none', color: 'var(--green)', fontSize: 9, transition: 'transform .12s', transform: open ? 'none' : 'rotate(-90deg)' }}>▼</span>
        <span style={{ flex: 1, fontSize: 12.5, fontWeight: 700, color: 'var(--ink)', letterSpacing: '.02em' }}>{title || '说明'}</span>
      </div>
      {open ? <div style={{ padding: '11px 14px' }}><window.MaestroRichDoc md={body} /></div> : null}
    </div>
  );
}
function ReportCards({ md, t }) {
  const secs = mdSections(md);
  if (!secs.length) return null;
  return <div style={{ display: 'grid', gap: 8 }}>{secs.map((s, i) => <ReportSectionCard key={i} title={s.title} body={s.body} t={t} defaultOpen />)}</div>;
}

// 单文件 diff 渲染（+/- 行着色）
function DiffBody({ diff }) {
  return (
    <pre style={{ margin: 0, padding: '10px 12px', fontSize: 11.5, lineHeight: 1.5, overflowX: 'auto', background: 'var(--bg-deep)', fontFamily: 'var(--mono)' }}>
      {diff.split('\n').map((ln, i) => {
        const c = (ln.startsWith('+') && !ln.startsWith('+++')) ? 'var(--green)'
          : (ln.startsWith('-') && !ln.startsWith('---')) ? 'var(--red)'
          : ln.startsWith('@@') ? 'var(--cyan)'
          : (ln.startsWith('diff --git') || ln.startsWith('index ') || ln.startsWith('+++') || ln.startsWith('---')) ? 'var(--faint)'
          : 'var(--muted)';
        return <div key={i} style={{ color: c, whiteSpace: 'pre' }}>{ln || ' '}</div>;
      })}
    </pre>
  );
}
// 改动文件列表：点击文件展开其 diff（按需向 daemon 取）
function FileDiffs({ taskId, t }) {
  const [files, setFiles] = React.useState(null);
  const [openPath, setOpenPath] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    fetch('/api/tasks/' + taskId + '/diff').then((r) => r.json()).then((d) => { if (alive) setFiles(d.files || []); }).catch(() => { if (alive) setFiles([]); });
    return () => { alive = false; };
  }, [taskId]);
  if (!files) return <div style={{ fontSize: 11, color: 'var(--faint)', padding: '4px 2px' }}>… diff</div>;
  if (!files.length) return null;
  return (
    <div style={{ display: 'grid', gap: 6, marginTop: 9 }}>
      {files.map((f) => (
        <div key={f.path} style={{ border: '1px solid var(--line-soft)', borderRadius: 5, overflow: 'hidden' }}>
          <div onClick={() => setOpenPath((p) => (p === f.path ? null : f.path))} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '7px 11px', cursor: 'pointer', background: 'var(--panel-2)' }}>
            <span style={{ flex: 'none', color: 'var(--green)', fontSize: 9, transform: openPath === f.path ? 'none' : 'rotate(-90deg)' }}>▼</span>
            <span style={{ flex: 1, fontSize: 12, color: 'var(--cyan)', wordBreak: 'break-all' }}>{f.path}</span>
            <span style={{ flex: 'none', fontSize: 10, color: 'var(--faint)' }}>{t.clickToView || ''}</span>
          </div>
          {openPath === f.path ? <DiffBody diff={f.diff} /> : null}
        </div>
      ))}
    </div>
  );
}

// 单段复审报告卡片：默认折叠，标题显示复审类型 + 裁决徽章，点击展开看完整报告（markdown）。
function ReviewReportCard({ label, verdict, report, t, first }) {
  const [open, setOpen] = React.useState(false);
  const vb = verdict === 'reject' ? { c: 'var(--red)', bd: 'var(--red-dim)', txt: '✗ ' + t.vReject }
    : verdict === 'approve' ? { c: 'var(--green)', bd: 'var(--green-dim)', txt: '✓ ' + t.vApprove } : null;
  return (
    <div style={{ marginTop: first ? 0 : 8, background: 'var(--bg-deep)', border: '1px solid var(--line-soft)', borderRadius: 6, overflow: 'hidden' }}>
      <div onClick={() => setOpen((o) => !o)} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', cursor: 'pointer', userSelect: 'none', background: 'var(--panel-2)' }}>
        <span style={{ color: 'var(--green)', fontSize: 9, transition: 'transform .12s', transform: open ? 'none' : 'rotate(-90deg)' }}>▼</span>
        <span style={{ fontSize: 11, fontWeight: 700, letterSpacing: '.14em', color: 'var(--muted)' }}>{label}</span>
        {vb ? <span style={{ fontSize: 10, fontWeight: 700, color: vb.c, border: '1px solid ' + vb.bd, borderRadius: 3, padding: '0 6px' }}>{vb.txt}</span> : null}
        <span style={{ marginLeft: 'auto', fontSize: 10, color: 'var(--faint)' }}>{open ? t.gateCollapse : t.gateExpand}</span>
      </div>
      {open ? <div style={{ padding: '11px 13px' }}><window.MaestroRichDoc md={report} /></div> : null}
    </div>
  );
}

// 需人工卡片：拉最近的 reviewer / security run 的「完整复审报告」（脱敏 transcript 末块=最终报告），markdown 渲染——
// 替代被截断到 200 字的 lastRunError，把驳回原因罗列清楚。无复审报告（执行崩溃/冲突/重评估等）则回退显示 lastRunError。
function ReviewReports({ taskId, fallback, t }) {
  const [st, setSt] = React.useState({ loading: true, reports: null });
  React.useEffect(() => {
    let alive = true;
    (async () => {
      try {
        const runs = await fetch('/api/tasks/' + taskId + '/runs').then((r) => r.ok ? r.json() : []);
        const list = Array.isArray(runs) ? runs : [];
        const latest = (kind) => list.filter((r) => r.kind === kind)
          .slice().sort((a, b) => String(b.startedAt || '').localeCompare(String(a.startedAt || '')))[0];
        const out = [];
        for (const [kind, label] of [['reviewer', t.lblCodeReview], ['security', t.lblSecurity]]) {
          const run = latest(kind);
          if (!run) continue;
          const d = await fetch('/api/runs/' + run.id + '/transcript').then((r) => r.ok ? r.json() : null);
          const texts = ((d && d.messages) || []).filter((m) => m.type === 'text' && m.text && m.text.trim());
          if (texts.length) {
            const report = texts[texts.length - 1].text;
            const vm = /VERDICT:\s*(approve|reject)/i.exec(report);
            out.push({ label, report, verdict: vm ? vm[1].toLowerCase() : null });
          }
        }
        if (alive) setSt({ loading: false, reports: out });
      } catch (e) { if (alive) setSt({ loading: false, reports: [] }); }
    })();
    return () => { alive = false; };
  }, [taskId]);

  if (st.loading) return <div style={{ fontSize: 11.5, color: 'var(--faint)', padding: '4px 2px' }}>…</div>;
  if (st.reports && st.reports.length) {
    return (
      <div>
        {st.reports.map((r, i) => (
          <ReviewReportCard key={i} label={r.label} verdict={r.verdict} report={r.report} t={t} first={i === 0} />
        ))}
      </div>
    );
  }
  return fallback ? <window.MaestroRichDoc md={fallback} /> : null;
}

// 拆解评审「拆解轨迹」：默认折叠，点击展开后懒加载该任务 planner run 的完整 transcript（表格/分析+工具调用）。
function GateTranscript({ taskId, t }) {
  const [open, setOpen] = React.useState(false);
  return (
    <div style={{ marginTop: 18 }}>
      <div onClick={() => setOpen((o) => !o)} style={{ display: 'inline-flex', alignItems: 'center', gap: 8, cursor: 'pointer', userSelect: 'none', padding: '4px 0' }}>
        <span style={{ color: 'var(--green)', fontSize: 9, transition: 'transform .12s', transform: open ? 'none' : 'rotate(-90deg)' }}>▼</span>
        <span style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--muted)', letterSpacing: '.18em' }}>{t.planTranscript}</span>
      </div>
      {open ? (
        <div style={{ marginTop: 8, background: 'var(--bg-deep)', border: '1px solid var(--line-soft)', borderRadius: 6, padding: '8px 12px' }}>
          <window.MaestroTranscript taskId={taskId} kind="planner" />
        </div>
      ) : null}
    </div>
  );
}

// 全屏预览：读完整方案 + 就地裁决
function GatePreview({ item, t, onDecide, onClose }) {
  const { ComplexityBadge, StatusChip, Button, Textarea } = window.MaestroDesignSystem_a6a290;
  const [rejecting, setRejecting] = React.useState(false);
  const [reason, setReason] = React.useState('');
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  if (!item) return null;
  // 拆解子任务序号映射（依赖用 #序号 引用）
  const subIdx = {};
  (item.subtasks || []).forEach((s, i) => { subIdx[s.id] = i + 1; });
  const rv = item.review;
  const vBadge = (label, v) => {
    const ok = v === 'approve', bad = v === 'reject';
    return (
      <span style={{
        display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 11.5, fontWeight: 700, padding: '4px 11px', borderRadius: 5,
        color: ok ? 'var(--green)' : bad ? 'var(--red)' : 'var(--faint)',
        border: '1px solid ' + (ok ? 'var(--green-dim)' : bad ? 'var(--red-dim)' : 'var(--line)'),
        background: ok ? 'rgba(95,221,125,.08)' : bad ? 'rgba(255,93,93,.08)' : 'transparent',
      }}>
        <span style={{ opacity: .75, fontWeight: 600 }}>{label}</span>
        <span>{ok ? '✓ ' + t.vApprove : bad ? '✗ ' + t.vReject : t.vNone}</span>
      </span>
    );
  };
  const secLabel = (txt) => <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--muted)', letterSpacing: '.18em', margin: '18px 0 8px' }}>{txt}</div>;
  const reportBox = (txt) => <div style={{ background: 'var(--bg-deep)', border: '1px solid var(--line-soft)', borderRadius: 4, padding: '12px 16px', fontSize: 12.5, lineHeight: 1.7, whiteSpace: 'pre-wrap', wordBreak: 'break-word', color: 'var(--ink)' }}>{txt}</div>;
  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(1080px, 94vw)', height: '94vh', background: 'var(--panel)', border: '1px solid var(--violet-dim)', borderRadius: 'var(--radius-lg, 10px)', overflow: 'hidden', boxShadow: '0 0 0 1px var(--line-soft), 0 24px 64px rgba(0,0,0,.6)', animation: 'maestro-rise .18s ease both' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', padding: '12px 16px', borderBottom: '1px solid var(--line)', background: 'var(--panel-2)' }}>
          <span style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: '.18em', color: 'var(--bg)', background: 'var(--violet)', padding: '2px 8px', borderRadius: 3 }}>{t.gates[item.gate]}</span>
          <span style={{ fontWeight: 700, fontSize: 15 }}>{item.title}</span>
          <span style={{ fontSize: 11, color: 'var(--muted)' }}>{item.meta}</span>
          <span style={{ flex: 1 }}></span>
          <button onClick={onClose} title={t.closeTip} style={{ fontFamily: 'var(--mono)', fontSize: 13, background: 'transparent', color: 'var(--muted)', border: 'none', cursor: 'pointer', padding: '4px 8px' }}>✕</button>
        </div>
        <div style={{ flex: 1, overflowY: 'auto', padding: '16px 20px 28px', fontFamily: 'var(--mono)' }}>
          {item.gate === 'exec' && rv ? (
            <div style={{ display: 'flex', gap: 10, marginBottom: 14, flexWrap: 'wrap' }}>
              {vBadge(t.lblCodeReview, rv.verdict)}
              {vBadge(t.lblSecurity, rv.securityVerdict)}
            </div>
          ) : null}
          {item.docLabel ? <div style={{ fontSize: 10.5, color: 'var(--muted)', letterSpacing: '.18em', marginBottom: 6 }}>{item.docLabel}</div> : null}
          {item.doc ? <ReportCards md={item.doc} t={t} /> : null}
          {item.gate === 'exec' && rv && rv.securitySummary ? (
            <React.Fragment>{secLabel(t.lblSecurity)}<ReportCards md={rv.securitySummary} t={t} /></React.Fragment>
          ) : null}
          {item.gate === 'exec' && rv && (rv.commits.length || rv.diffSummary) ? (
            <React.Fragment>
              {secLabel(t.lblChanges + (rv.branch ? ' · ' + rv.branch : ''))}
              <div style={{ background: 'var(--bg-deep)', border: '1px solid var(--line-soft)', borderRadius: 4, padding: '12px 16px', fontSize: 12.5, lineHeight: 1.7 }}>
                {rv.commits.map((c) => <div key={c} style={{ color: 'var(--green)' }}>· {c}</div>)}
                {rv.diffSummary ? <div style={{ marginTop: rv.commits.length ? 9 : 0, color: 'var(--faint)', whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>{rv.diffSummary}</div> : null}
              </div>
              <FileDiffs taskId={item.taskId} t={t} />
            </React.Fragment>
          ) : null}
          {item.gate === 'plan' && item.subtasks && item.subtasks.length ? (
            <div style={{ marginTop: 20 }}>
              <div style={{ fontSize: 10.5, fontWeight: 700, color: 'var(--muted)', letterSpacing: '.18em', marginBottom: 11 }}>{t.gateSubtasks} · {item.subtasks.length}</div>
              <div style={{ display: 'grid', gap: 9 }}>
                {item.subtasks.map((s, i) => {
                  const deps = (s.deps || []).map((d) => (subIdx[d] ? '#' + subIdx[d] : null)).filter(Boolean);
                  return (
                    <div key={s.id} style={{ background: 'var(--bg-deep)', border: '1px solid var(--line-soft)', borderRadius: 6, padding: '11px 13px' }}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 9, flexWrap: 'wrap' }}>
                        <span style={{ flex: 'none', width: 22, height: 22, borderRadius: '50%', background: 'var(--panel-2)', color: 'var(--muted)', fontSize: 11, fontWeight: 700, display: 'grid', placeItems: 'center' }}>{i + 1}</span>
                        <ComplexityBadge complexity={s.complexity} />
                        <span style={{ flex: 'none', fontSize: 10, fontWeight: 700, color: 'var(--muted)', border: '1px solid var(--line)', borderRadius: 3, padding: '1px 6px' }}>P{s.priority}</span>
                        <span style={{ flex: 1, fontSize: 13.5, fontWeight: 600, color: 'var(--ink)', minWidth: 120 }}>{s.title}</span>
                        {StatusChip ? <StatusChip status="paused" label={t.frozenPending} /> : null}
                      </div>
                      <div style={{ marginTop: 7, paddingLeft: 31, fontSize: 11.5, color: 'var(--faint)' }}>
                        {t.dependsOn}: {deps.length ? <span style={{ color: 'var(--amber)', fontWeight: 600 }}>{deps.join('、')}</span> : t.noDeps}
                      </div>
                      {s.scopeFiles && s.scopeFiles.length ? (
                        <div style={{ marginTop: 5, paddingLeft: 31, display: 'flex', flexWrap: 'wrap', gap: 5, alignItems: 'center' }}>
                          <span style={{ flex: 'none', fontSize: 11.5, color: 'var(--faint)' }}>{t.scopeFiles}:</span>
                          {s.scopeFiles.map((f) => (
                            <span key={f} style={{ fontSize: 10.5, fontFamily: 'var(--mono)', color: 'var(--cyan)', background: 'var(--panel-2)', border: '1px solid var(--line-soft)', borderRadius: 3, padding: '1px 6px' }}>{f}</span>
                          ))}
                        </div>
                      ) : null}
                    </div>
                  );
                })}
              </div>
            </div>
          ) : null}
          {item.gate === 'plan' ? <GateTranscript taskId={item.taskId} t={t} /> : null}
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', padding: '12px 16px', borderTop: '1px solid var(--line)', background: 'var(--panel-2)' }}>
          {rejecting ? (
            <React.Fragment>
              <span style={{ flexBasis: '100%', display: 'flex' }}>
                <Textarea danger placeholder={t.rejectPlaceholder} minHeight={56} value={reason} onChange={setReason} />
              </span>
              <Button variant="reject" disabled={!reason.trim()} onClick={() => { onDecide(item, 'reject', reason); onClose(); }}>{t.confirmReject}</Button>
              <Button onClick={() => { setRejecting(false); setReason(''); }}>{t.cancel}</Button>
            </React.Fragment>
          ) : (
            <React.Fragment>
              <Button variant="accept" onClick={() => { onDecide(item, 'accept'); onClose(); }}>{t.accept}</Button>
              <Button variant="reject" onClick={() => setRejecting(true)}>{t.reject}</Button>
            </React.Fragment>
          )}
        </div>
      </div>
    </div>
  );
}

// 「需你处理」区：审批闸（accept/reject + 驳回必填理由）+ 需人工（requeue/接管/取消）
function GateSection({ approvals, onDecide, taskOps, onTakeover, t }) {
  const { GateCard, GateStripe, Button, Textarea } = window.MaestroDesignSystem_a6a290;
  const [rejecting, setRejecting] = React.useState(null);
  const [reason, setReason] = React.useState('');
  const [preview, setPreview] = React.useState(null);
  const [sectionFolded, setSectionFolded] = React.useState(() => localStorage.getItem('maestro-kit-fold-gate') === '1');
  const toggleSectionFold = () => setSectionFolded((f) => { localStorage.setItem('maestro-kit-fold-gate', f ? '0' : '1'); return !f; });
  const [folded, setFolded] = React.useState(() => new Set());
  const toggleFold = (id) => setFolded((prev) => {
    const next = new Set(prev); next.has(id) ? next.delete(id) : next.add(id); return next;
  });
  if (approvals.length === 0) return null;
  return (
    <section>
      <window.MaestroKitSectionHead icon="gate" title={t.gateSection} count={approvals.length} accent="var(--violet)" folded={sectionFolded} onToggleFold={toggleSectionFold} t={t} sticky />
      {!sectionFolded ? <GateStripe /> : null}
      {!sectionFolded ? approvals.map((a) => {
        const isFolded = folded.has(a.id);
        return (
        <div key={a.id} style={{ marginBottom: 12 }}>
          <GateCard gate={a.gate} kindLabel={a.gate === 'attention' ? t.gateNeedsHuman : t.gates[a.gate]} title={a.title} meta={a.meta} docLabel={a.docLabel} doc={a.gate === 'attention' ? undefined : a.doc}
            collapsed={isFolded} foldable onToggleFold={() => toggleFold(a.id)}
            foldTitle={isFolded ? t.gateExpand : t.gateCollapse}
            onHeaderDoubleClick={a.gate === 'attention' ? undefined : () => setPreview(a)} headerTitle={a.gate === 'attention' ? null : t.gateDblTip}
            headerExtra={a.gate === 'attention' ? null : (<button type="button" title={t.fullRead} onClick={() => setPreview(a)}
              style={{ fontFamily: 'var(--mono)', fontSize: 12, lineHeight: 1, cursor: 'pointer', background: 'transparent', color: 'var(--muted)', border: '1px solid var(--line)', borderRadius: 'var(--radius-sm,4px)', padding: '3px 7px' }}
              onMouseEnter={(e) => { e.currentTarget.style.color = 'var(--violet)'; e.currentTarget.style.borderColor = 'var(--violet-dim)'; }}
              onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--muted)'; e.currentTarget.style.borderColor = 'var(--line)'; }}>⛶</button>)}
            actions={
              a.gate === 'attention' ? (
                <React.Fragment>
                  <Button variant="accept" onClick={() => taskOps && taskOps.requeue(a.taskId)}>{t.gRequeue}</Button>
                  {onTakeover ? <Button variant="ghost" onClick={() => onTakeover({ id: a.taskId, title: a.title })}>{t.gTakeover}</Button> : null}
                  <span style={{ marginLeft: 'auto' }}>
                    <Button variant="reject" size="xs" onClick={() => taskOps && taskOps.cancel(a.taskId)}>{t.gCancelTask}</Button>
                  </span>
                </React.Fragment>
              ) : (
                <React.Fragment>
                  <Button variant="accept" onClick={() => onDecide(a, 'accept')}>{t.accept}</Button>
                  <Button variant="reject" onClick={() => setRejecting(rejecting === a.id ? null : a.id)}>{t.reject}</Button>
                  <span style={{ marginLeft: 'auto' }}>
                    <Button variant="ghost" size="xs" title={t.fullRead} onClick={() => setPreview(a)}>⛶ {t.fullRead}</Button>
                  </span>
                </React.Fragment>
              )
            }>
            {a.gate === 'attention' ? (
              <ReviewReports taskId={a.taskId} fallback={a.doc} t={t} />
            ) : rejecting === a.id ? (
              <div style={{ display: 'flex', gap: 10, alignItems: 'flex-start', marginTop: 10 }}>
                <Textarea danger placeholder={t.rejectPlaceholder} minHeight={56} value={reason} onChange={setReason} />
                <Button variant="reject" disabled={!reason.trim()}
                  onClick={() => { onDecide(a, 'reject', reason); setRejecting(null); setReason(''); }}>
                  {t.confirmReject}
                </Button>
              </div>
            ) : null}
          </GateCard>
        </div>
        );
      }) : null}
      <GatePreview item={preview} t={t} onDecide={onDecide} onClose={() => setPreview(null)} />
    </section>
  );
}
window.MaestroKitGateSection = GateSection;
