﻿import { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import { api } from '../services/api';

async function downloadPdf(url: string, options?: { silent?: boolean }) {
  try {
    const res = await fetch(url, { credentials: 'include' });
    if (!res.ok) {
      let msg = '下载失败';
      try { msg = (await res.json()).error || msg; } catch { /* 非 JSON 响应 */ }
      alert(msg);
      return false;
    }
    const blob = await res.blob();
    // 优先 filename*（RFC 5987）；其次 filename（剥离引号）；最后兜底
    const disposition = res.headers.get('Content-Disposition') || '';
    let filename = '';
    const star = disposition.match(/filename\*=(?:UTF-8'')?([^;\s]+)/i);
    if (star) {
      try { filename = decodeURIComponent(star[1]); } catch { filename = star[1]; }
    } else {
      const plain = disposition.match(/filename="?([^";\s]+)"?/i);
      filename = plain ? plain[1] : '';
    }
    if (!filename) filename = url.includes('summary') ? '汇总报告.pdf' : url.includes('deliverables') ? '成果物清单.csv' : '报告.pdf';
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    setTimeout(() => URL.revokeObjectURL(a.href), 1000);
    if (!options?.silent) alert(`已开始下载：${filename}（保存到浏览器下载目录）`);
    return true;
  } catch (e: any) {
    alert('下载失败: ' + (e?.message || e));
    return false;
  }
}

type Tab = 'standards' | 'entries' | 'deliverables' | 'summary';

export default function ProjectView() {
  const { id } = useParams<{ id: string }>();
  const [project, setProject] = useState<any>(null);
  const [projectError, setProjectError] = useState('');
  const [tab, setTab] = useState<Tab>('entries');
  const [editingName, setEditingName] = useState(false);
  const [newName, setNewName] = useState('');

  useEffect(() => {
    if (id) {
      setProjectError('');
      let cancelled = false;
      api.getProject(id).then(p => { if (!cancelled) setProject(p); }).catch((err: any) => {
        if (!cancelled) {
          setProjectError(err.message || '项目不存在');
          setProject(null);
        }
      });
      return () => { cancelled = true; };
    }
  }, [id]);

  if (projectError) return <div className="project-view"><div className="empty">{projectError}</div></div>;
  if (!project) return <div className="loading">加载中...</div>;

  return (
    <div className="project-view">
      <div className="project-header">
        <div className="flex justify-between items-center">
          <div className="flex items-center gap-10">
            {editingName ? (
              <input value={newName} onChange={e => setNewName(e.target.value)} autoFocus
                style={{ fontSize: 22, fontWeight: 700, padding: '4px 10px', border: '2px solid var(--primary)', borderRadius: 8, outline: 'none', width: 400 }}
                onKeyDown={async e => {
                  if (e.key === 'Enter' && newName.trim()) {
                    try {
                      await api.request('PUT', `/projects/${id}`, { name: newName.trim() });
                      setProject({ ...project, name: newName.trim() });
                      setEditingName(false);
                    } catch (err: any) { alert(err.message || '重命名失败'); }
                  }
                  if (e.key === 'Escape') { setNewName(project.name); setEditingName(false); }
                }}
                onBlur={async () => {
                  if (newName.trim() && newName.trim() !== project.name) {
                    try {
                      await api.request('PUT', `/projects/${id}`, { name: newName.trim() });
                      setProject({ ...project, name: newName.trim() });
                    } catch (err: any) { alert(err.message || '重命名失败'); }
                  }
                  setEditingName(false);
                }}
              />
            ) : (
              <h2 style={{ cursor: 'pointer' }} onClick={() => { setNewName(project.name); setEditingName(true); }} title="点击重命名">{project.name} ✎</h2>
            )}
            {project.track && <span className="badge" style={{ background: '#6366f1', marginLeft: 8, fontSize: 12 }}>{project.track}</span>}
          </div>
          <button className="btn-danger-outline" onClick={async () => { if (confirm(`删除项目"${project.name}"？所有关联数据将丢失`)) { try { await api.request('DELETE', `/projects/${id}?force=true`); window.location.href = '/'; } catch (err: any) { alert(err.message); } } }}>删除项目</button>
        </div>
        <div className="project-meta">
          <span>总计 {project.total}</span>
          <span>✓ {project.reviewed || 0}</span>
          <span>▶ {project.active || 0}</span>
          <span>✕ {project.failed || 0}</span>
        </div>
      </div>
      <div className="tabs">
        <button className={`tab ${tab === 'standards' ? 'active' : ''}`} onClick={() => setTab('standards')}>标准</button>
        <button className={`tab ${tab === 'entries' ? 'active' : ''}`} onClick={() => setTab('entries')}>条目</button>
        <button className={`tab ${tab === 'deliverables' ? 'active' : ''}`} onClick={() => setTab('deliverables')}>成果物</button>
        <button className={`tab ${tab === 'summary' ? 'active' : ''}`} onClick={() => setTab('summary')}>汇总</button>
      </div>
      <div className="tab-content">
        {tab === 'standards' && <StandardsManager projectId={id!} />}
        {tab === 'entries' && <EntryManager projectId={id!} track={project.track} />}
        {tab === 'deliverables' && <DeliverablesView projectId={id!} />}
        {tab === 'summary' && <SummaryView projectId={id!} />}
      </div>
    </div>
  );
}

function StandardsManager({ projectId }: { projectId: string }) {
  const [standards, setStandards] = useState<any[]>([]);
  const [showForm, setShowForm] = useState(false);
  const [name, setName] = useState('');
  const [content, setContent] = useState('');
  const [catTag, setCatTag] = useState('');
  const [maxScore, setMaxScore] = useState('150');

  const load = () => api.listStandards(projectId).then(setStandards).catch(() => {});
  useEffect(() => { load(); }, [projectId]);

  const create = async () => {
    if (!name.trim() || !content.trim()) return;
    try {
      await api.createStandard(projectId, { name: name.trim(), content, category_tag: catTag, max_score: parseInt(maxScore) || 150 });
      setName(''); setContent(''); setCatTag(''); setMaxScore('150'); setShowForm(false); await load();
    } catch (err: any) { alert(err.message || '保存失败，请检查服务端'); }
  };

  const remove = async (sid: string) => {
    if (!confirm('确定删除？')) return;
    try { await api.deleteStandard(projectId, sid); await load(); } catch (err: any) { alert(err.message || '删除失败'); }
  };

  return (
    <div>
      <div className="section-header"><h3>评审标准 ({standards.length})</h3><button onClick={() => setShowForm(true)}>+ 上传标准</button></div>
      {showForm && (
        <div className="standard-form">
          <input value={name} onChange={e => setName(e.target.value)} placeholder="标准名称" />
          <input value={catTag} onChange={e => setCatTag(e.target.value)} placeholder="分类标签（留空为默认标准）" />
          <input value={maxScore} onChange={e => setMaxScore(e.target.value)} placeholder="总分上限" type="number" min={1} style={{ width: 120, padding: '10px 14px', border: '2px solid var(--border)', borderRadius: 8, fontSize: 14, outline: 'none' }} />
          <textarea value={content} onChange={e => setContent(e.target.value)} placeholder={'## 维度名（分值）\n评审要点\n可选：文件关键词: data,report,benchmark（限定该维度只看这些文件，留空则按系统规则）'} rows={8} />
          <div className="form-actions">
            <button onClick={create}>保存</button>
            <button onClick={() => setShowForm(false)}>取消</button>
          </div>
        </div>
      )}
      <div className="standard-list">
        {standards.length === 0 && <div className="empty">暂无评审标准</div>}
        {standards.map(s => (
          <div key={s.id} className="standard-card" style={{ flexDirection: 'column', alignItems: 'stretch' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 8 }}>
              <div>
                <strong style={{ fontSize: 15 }}>{s.name}</strong>
                {s.category_tag && <span className="tag">{s.category_tag}</span>}
                <span className="tag" style={{ background: '#e0e7ff', color: '#4338ca' }}>上限{s.max_score || 150}分</span>
              </div>
              <button className="btn-danger" onClick={() => remove(s.id)}>删除</button>
            </div>
            <details style={{ fontSize: 13 }}>
              <summary style={{ cursor: 'pointer', color: 'var(--primary)', fontWeight: 500, marginBottom: 4 }}>
                查看维度详情（{s.dimensions?.length || 0}个维度）
              </summary>
              {s.dimensions?.map((d: any, i: number) => (
                <div key={i} style={{ padding: '10px 12px', margin: '6px 0', background: 'var(--bg-subtle)', borderRadius: 8, border: '1px solid var(--border)' }}>
                  <div style={{ fontWeight: 600, marginBottom: 4 }}>{d.name}（{d.maxScore}分）</div>
                  {d.group && d.group !== 'common' && <span className="tag" style={{ background: '#f3e8ff', color: '#7c3aed', marginBottom: 4, display: 'inline-block' }}>{d.group}</span>}
                  <div style={{ whiteSpace: 'pre-wrap', color: 'var(--text-secondary)', lineHeight: 1.6, fontSize: 12 }}>{d.content || '无详细说明'}</div>
                </div>
              ))}
            </details>
          </div>
        ))}
      </div>
    </div>
  );
}

function EntryManager({ projectId, track }: { projectId: string; track?: string }) {
  const [entries, setEntries] = useState<any[]>([]);
  const [total, setTotal] = useState(0);
  const [status, setStatus] = useState('');
  const [search, setSearch] = useState('');
  const [selected, setSelected] = useState<Set<string>>(new Set());
  const [detail, setDetail] = useState<any>(null);
  const [showImport, setShowImport] = useState(false);
  const [csvText, setCsvText] = useState('');
  const [importResult, setImportResult] = useState<any>(null);
  const [editEntry, setEditEntry] = useState<any>(null);
  const [showAdd, setShowAdd] = useState(false);
  const [addForm, setAddForm] = useState({ title: '', repo_url: '', participant: '', sub_type: '', branch: '', selected_topic: 'self', reg_no: '', reg_features: '' });
  const [l2Topics, setL2Topics] = useState<any[]>([]);
  const [offset, setOffset] = useState(0);
  const PAGE_LIMIT = 50;

  // L2考核：加载选题元数据（命题01~11），供条目表单选题下拉
  useEffect(() => {
    if (track === 'L2考核') {
      api.request('GET', '/projects/l2-topics').then((d: any) => setL2Topics(d?.topics || [])).catch(() => {});
    }
  }, [track]);

  const latestSearchRef = useRef('');

  const doSearch = (term: string, pageOffset = offset) => {
    latestSearchRef.current = term;
    const params: any = { limit: PAGE_LIMIT, offset: pageOffset };
    if (status) params.status = status;
    if (term) params.search = term;
    api.listEntries(projectId, params).then(r => {
      if (latestSearchRef.current === term) {
        setEntries(r.items);
        setTotal(r.total);
      }
    }).catch(() => {});
  };
  const load = () => doSearch(search, offset);

  const goPage = (newOffset: number) => {
    setOffset(newOffset);
    setTimeout(() => doSearch(search, newOffset), 0);
  };

  useEffect(() => { setOffset(0); doSearch(search, 0); }, [projectId, status, search]);

  const toggleSelect = (id: string) => {
    const next = new Set(selected);
    if (next.has(id)) next.delete(id); else next.add(id);
    setSelected(next);
  };

  const doAction = async (action: string, entryId: string) => {
    try {
      if (action === 'start') await api.request('POST', `/projects/${projectId}/entries/${entryId}/start`);
      else if (action === 'cancel') await api.request('POST', `/projects/${projectId}/entries/${entryId}/cancel`);
      else if (action === 'retry') await api.request('POST', `/projects/${projectId}/entries/${entryId}/retry`);
      await load();
    } catch (err: any) { alert(err.message || '操作失败'); }
  };

  // §2.2 人工构建确认：a_done 提供「确认构建完成 / 构建失败」两按钮，结果传给 /verify
  const doVerify = async (entryId: string, buildStatus: 'done' | 'failed') => {
    try {
      await api.request('POST', `/projects/${projectId}/entries/${entryId}/verify`, { build_status: buildStatus });
      await load();
    } catch (err: any) { alert(err.message || '启动系统验证失败'); }
  };

  const doDelete = async (entryId: string, title: string) => {
    if (!confirm(`删除条目"${title}"？`)) return;
    try { await api.request('DELETE', `/projects/${projectId}/entries/${entryId}`); await load(); } catch (err: any) { alert(err.message || '删除失败'); }
  };

  const doEditSave = async () => {
    if (!editEntry) return;
    try {
      const payload: any = {
        title: editEntry.title,
        repo_url: editEntry.repo_url,
        participant: editEntry.participant,
        branch: editEntry.branch,
        sub_type: editEntry.sub_type,
        service_url: editEntry.service_url,
        build_status: editEntry.build_status,
      };
      if (track === 'L2考核') {
        payload.selected_topic = editEntry.selected_topic || 'self';
        if (payload.selected_topic === 'self') {
          payload.self_registration = { no: editEntry.reg_no, features: editEntry.reg_features };
        }
      }
      await api.request('PUT', `/projects/${projectId}/entries/${editEntry.id}`, payload);
      setEditEntry(null);
      await load();
    } catch (err: any) { alert(err.message || '保存失败'); }
  };

  const openEdit = (e: any) => {
    let reg = { no: '', features: '' };
    try { const p = JSON.parse(e.self_registration || '{}'); reg = { no: p.no || '', features: Array.isArray(p.features) ? p.features.join('\n') : (p.features || '') }; } catch { }
    setEditEntry({
      id: e.id, title: e.title, repo_url: e.repo_url, participant: e.participant || '',
      sub_type: e.sub_type || '', branch: e.branch || '',
      service_url: e.service_url || '', build_status: e.build_status || '',
      selected_topic: e.selected_topic || 'self', reg_no: reg.no, reg_features: reg.features,
    });
  };

  const doAdd = async () => {
    if (!addForm.title.trim() || !addForm.repo_url.trim()) return;
    const payload: any = { ...addForm };
    delete payload.reg_no;
    delete payload.reg_features;
    if (track === 'L2考核') {
      payload.selected_topic = addForm.selected_topic || 'self';
      if (payload.selected_topic === 'self') {
        payload.self_registration = { no: addForm.reg_no, features: addForm.reg_features };
      }
    }
    try {
      await api.request('POST', `/projects/${projectId}/entries`, payload);
      setAddForm({ title: '', repo_url: '', participant: '', sub_type: '', branch: '', selected_topic: 'self', reg_no: '', reg_features: '' });
      setShowAdd(false);
      await load();
    } catch (err: any) { alert(err.message || '添加失败'); }
  };

  const batchStart = async () => {
    const ids = Array.from(selected);
    if (ids.length === 0) return;
    try { await api.request('POST', `/projects/${projectId}/entries/batch-start`, { entryIds: ids }); await load(); } catch (err: any) { alert(err.message || '启动失败'); }
  };

  const doImport = async () => {
    const lines = csvText.trim().split('\n');
    if (lines.length < 2) return;
    const headers = lines[0].split(',').map(h => h.trim());
    const items = lines.slice(1).map(line => {
      const vals = line.split(',').map(v => v.trim());
      const item: any = {};
      headers.forEach((h, i) => { if (vals[i]) item[h] = vals[i]; });
      return item;
    });
    try {
      const r = await api.batchImport(projectId, items);
      setImportResult(r);
      setCsvText('');
      if (r.imported > 0) await load();
    } catch (err: any) { alert(err.message || '导入失败'); }
  };

  const downloadTemplate = () => {
    const header = ['title', 'repo_url', 'participant', 'branch', 'service_url', 'base_branch'];
    if (track === '赛道一') header.push('sub_type');
    if (track === 'L2考核') header.push('selected_topic');
    const example = header.map(h =>
      h === 'repo_url' ? 'https://github.com/user/repo' :
      h === 'selected_topic' ? '01' :
      h === 'sub_type' ? '新規' : ''
    ).join(',');
    const blob = new Blob(['\uFEFF' + header.join(',') + '\n' + example], { type: 'text/csv;charset=utf-8' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'entry-import-template.csv';
    a.click();
    URL.revokeObjectURL(a.href);
  };

  const openDetail = async (eid: string) => {
    try { const d = await api.getEntry(projectId, eid); setDetail(d); } catch (err: any) { alert(err.message || '加载详情失败'); }
  };

  const statusBadge = (s: string) => {
    const map: Record<string, string> = {
      pending: 'badge-gray', queued: 'badge-amber', cloning: 'badge-blue', analyzing: 'badge-blue',
      a_done: 'badge-purple', verifying: 'badge-blue',
      review_done: 'badge-green', admin_reviewed: 'badge-green',
      clone_fail: 'badge-red', analysis_fail: 'badge-red', failed: 'badge-red', cancelled: 'badge-gray',
    };
    const label: Record<string, string> = {
      pending: '待评审', queued: '排队中', cloning: '克隆中', analyzing: '分析中',
      a_done: 'A阶段完成', verifying: '系统验证中',
      review_done: '已完成', admin_reviewed: '已修正',
      clone_fail: '克隆失败', analysis_fail: '分析失败', failed: '失败', cancelled: '已取消',
    };
    const cls = map[s] || 'badge-gray';
    return <span className={`badge ${cls}`}>{label[s] || s}</span>;
  };

  const scoreColor = (_s: string, score: number | null | undefined) => {
    if (score === null || score === undefined) return 'score-empty';
    if (score >= 60) return 'score-ok';
    if (score >= 40) return 'score-warn';
    return 'score-low';
  };

  return (
    <div>
      <div className="section-header">
        <h3>评审条目 ({total})</h3>
        <div style={{ display: 'flex', gap: 8 }}>
          <select value={status} onChange={e => { setStatus(e.target.value); setOffset(0); }} className="filter-select">
            <option value="">全部</option>
            <option value="pending">待评审</option>
            <option value="queued">排队中</option>
            <option value="cloning">克隆中</option>
            <option value="analyzing">分析中</option>
            <option value="a_done">A阶段完成</option>
            <option value="verifying">系统验证中</option>
            <option value="review_done">已完成</option>
            <option value="admin_reviewed">已修正</option>
            <option value="clone_fail">克隆失败</option>
            <option value="analysis_fail">分析失败</option>
            <option value="failed">失败</option>
          </select>
          <input value={search} onChange={e => setSearch(e.target.value)} placeholder="搜索标题..." className="search-input" onKeyDown={e => e.key === 'Enter' && doSearch(e.currentTarget.value)} />
          <button onClick={() => setShowAdd(true)} className="btn-primary">+ 添加条目</button>
          <button onClick={batchStart} disabled={selected.size === 0} className="btn-primary">启动选中 ({selected.size})</button>
          <button onClick={() => setShowImport(!showImport)} className="btn-secondary">批量导入</button>
        </div>
      </div>

      {showAdd && (
        <div className="import-panel" style={{ animation: 'slideDown 0.2s ease-out' }}>
          <div className="flex-col gap-10">
            <input value={addForm.title} onChange={e => setAddForm({ ...addForm, title: e.target.value })} placeholder="条目标题 *" className="input-field" autoFocus />
            <input value={addForm.repo_url} onChange={e => setAddForm({ ...addForm, repo_url: e.target.value })} placeholder="Git仓库URL * (https://github.com/...)" className="input-field" />
            <input value={addForm.branch} onChange={e => setAddForm({ ...addForm, branch: e.target.value })} placeholder="分支 (可选，默认 main)" className="input-field" />
            <div className="flex gap-8">
              <input value={addForm.participant} onChange={e => setAddForm({ ...addForm, participant: e.target.value })} placeholder="参赛者" className="input-field flex-1" />
              {track ? (
                <span className="badge badge-blue" style={{ padding: '10px 14px', borderRadius: 8, fontSize: 14, whiteSpace: 'nowrap' }}>{track}</span>
              ) : (
                <span style={{ padding: '10px 14px', borderRadius: 8, fontSize: 13, background: '#f3f4f6', color: '#9ca3af', whiteSpace: 'nowrap' }}>未设置赛道</span>
              )}
            </div>
            {track === '赛道一' && (
              <select value={addForm.sub_type} onChange={e => setAddForm({ ...addForm, sub_type: e.target.value })} className="select-field">
                <option value="">选择子类型</option>
                <option value="新規">新規开发</option>
                <option value="修正">修正/升级</option>
              </select>
            )}
            {track === 'L2考核' && (
              <>
              <select value={addForm.selected_topic} onChange={e => setAddForm({ ...addForm, selected_topic: e.target.value })} className="select-field">
                <option value="self">自选题（需事前登记）</option>
                {l2Topics.map(t => (
                  <option key={t.id} value={t.id}>命题 {t.id}｜{t.title}（{'★'.repeat(t.difficulty)} 满分{t.maxScoreCap}）</option>
                ))}
              </select>
              {addForm.selected_topic === 'self' && (
                <>
                  <input value={addForm.reg_no} onChange={e => setAddForm({ ...addForm, reg_no: e.target.value })} placeholder="登记编号（组委会登记确认后获得）" className="input-field" />
                  <textarea value={addForm.reg_features} onChange={e => setAddForm({ ...addForm, reg_features: e.target.value })} placeholder="登记功能清单（每行一条；低于该范围核心功能的缩减按未实现计）" rows={3} className="input-field" />
                </>
              )}
              </>
            )}
            <div className="form-actions">
              <button onClick={doAdd} disabled={!addForm.title.trim() || !addForm.repo_url.trim()}>添加</button>
              <button onClick={() => setShowAdd(false)}>取消</button>
            </div>
          </div>
        </div>
      )}

      {showImport && (
        <div className="import-panel">
          <textarea value={csvText} onChange={e => setCsvText(e.target.value)} placeholder={'首行为列头（UTF-8 编码，推荐带 BOM）：\ntitle,repo_url,participant,branch,service_url\n张三月结,https://gitea/zhang-01,张三,main,'} rows={5} />
          <div className="form-actions">
            <button onClick={doImport}>导入</button>
            <button onClick={downloadTemplate} className="btn-secondary">下载模板</button>
            <button onClick={() => setShowImport(false)}>取消</button>
          </div>
          {importResult && (
            <div className="import-result">
              成功 {importResult.imported} 条{importResult.errors?.length > 0 ? `，失败 ${importResult.errors.length} 条` : ''}
              {importResult.errors?.slice(0, 5).map((e: any, i: number) => <div key={i} className="error-row">第{e.row}行：{e.reason}</div>)}
            </div>
          )}
        </div>
      )}

      <table className="entry-table">
        <thead>
          <tr>
            <th><input type="checkbox" onChange={e => { if (e.target.checked) setSelected(new Set(entries.map(x => x.id))); else setSelected(new Set()); }} checked={selected.size === entries.length && entries.length > 0} /></th>
            <th>标题</th>
            <th>参赛者</th>
            <th>赛道</th>
            <th>状态</th>
            <th>分数</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          {entries.length === 0 && <tr><td colSpan={7} className="empty-row">暂无条目</td></tr>}
          {entries.map(e => (
            <tr key={e.id} className={e.id === detail?.id ? 'active-row' : ''}>
              <td><input type="checkbox" checked={selected.has(e.id)} onChange={() => toggleSelect(e.id)} /></td>
              <td className="title-cell" onClick={() => openDetail(e.id)}>{e.title}</td>
              <td>{e.participant}</td>
              <td>
                <span className="badge badge-blue">{e.category_tag}</span>
                {e.sub_type && <span className="badge badge-purple ml-8">{e.sub_type}</span>}
                {e.selected_topic && <span className="badge badge-gray ml-8">{e.selected_topic === 'self' ? '自选' : `命题 ${e.selected_topic}`}</span>}
                {e.final_level && (
                  <span className={`badge ml-8 ${e.final_level === 'L3' ? 'badge-purple' : (e.final_level === 'L2' || e.final_level === '合格') ? 'badge-green' : 'badge-red'}`}>
                    {e.final_level}
                  </span>
                )}
              </td>
              <td>{statusBadge(e.status)}</td>
              <td className={`score-cell ${scoreColor(e.status, e.final_score ?? e.raw_score ?? (e.status === 'a_done' ? e.score_a : null))}`}>
                {e.status === 'a_done'
                  ? (e.score_a != null ? `A:${e.score_a}` : 'A:-')
                  : (e.aggregate_count > 0
                    ? `${e.aggregate_score}${e.is_formal ? `（聚合${e.aggregate_count}次）` : `（初评${e.aggregate_count}次）`}`
                    : (e.final_score ?? e.raw_score ?? '-'))}
              </td>
              <td className="action-cell">
                {e.status === 'pending' && <button className="btn-action" onClick={() => doAction('start', e.id)}>启动</button>}
                {e.status === 'pending' && <button className="btn-action" onClick={() => openEdit(e)}>编辑</button>}
                {e.status === 'pending' && <button className="btn-action" style={{ borderColor: '#ef4444', color: '#ef4444' }} onClick={() => doDelete(e.id, e.title)}>删除</button>}
                {['queued', 'cloning', 'analyzing'].includes(e.status) && <button className="btn-action warn" onClick={() => doAction('cancel', e.id)}>取消</button>}
                {e.status === 'a_done' && <button className="btn-action" onClick={() => openEdit(e)}>编辑</button>}
                {e.status === 'a_done' && <button className="btn-action" style={{ borderColor: '#10b981', color: '#10b981' }} onClick={() => doVerify(e.id, 'done')}>构建完成</button>}
                {e.status === 'a_done' && <button className="btn-action" style={{ borderColor: '#ef4444', color: '#ef4444' }} onClick={() => doVerify(e.id, 'failed')}>构建失败</button>}
                {e.status === 'a_done' && <button className="btn-action" onClick={() => openDetail(e.id)}>详情</button>}
                {e.status === 'verifying' && <button className="btn-action warn" onClick={() => doAction('cancel', e.id)}>取消</button>}
                {['clone_fail', 'analysis_fail', 'failed'].includes(e.status) && <button className="btn-action" onClick={() => doAction('retry', e.id)}>重试</button>}
                {['review_done', 'admin_reviewed'].includes(e.status) && <button className="btn-action" onClick={() => openDetail(e.id)}>详情</button>}
                {['review_done', 'admin_reviewed'].includes(e.status) && <button className="btn-action" onClick={() => doAction('start', e.id)}>重新评审</button>}
              </td>
            </tr>
          ))}
        </tbody>
      </table>

      {total > PAGE_LIMIT && (
        <div className="pagination" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 8, marginTop: 16 }}>
          <button disabled={offset === 0} onClick={() => goPage(offset - PAGE_LIMIT)} className="btn-action" style={{ padding: '6px 14px' }}>← 上一页</button>
          <span style={{ fontSize: 13, color: 'var(--text-secondary)' }}>
            {Math.floor(offset / PAGE_LIMIT) + 1} / {Math.ceil(total / PAGE_LIMIT)} 页（共 {total} 条）
          </span>
          <button disabled={offset + PAGE_LIMIT >= total} onClick={() => goPage(offset + PAGE_LIMIT)} className="btn-action" style={{ padding: '6px 14px' }}>下一页 →</button>
        </div>
      )}

      {detail && <DetailPanel entry={detail} projectId={projectId} onClose={() => setDetail(null)} onSave={() => { load(); openDetail(detail.id); }} />}

      {editEntry && (
        <div className="detail-overlay" onClick={() => setEditEntry(null)}>
          <div className="detail-panel" onClick={e => e.stopPropagation()} style={{ width: 480 }}>
            <div className="detail-header">
              <h3>编辑条目</h3>
              <button className="btn-close" onClick={() => setEditEntry(null)}>×</button>
            </div>
            <div className="flex-col gap-12">
              <input value={editEntry.title} onChange={e => setEditEntry({ ...editEntry, title: e.target.value })} placeholder="标题" className="input-field" />
              <input value={editEntry.repo_url} onChange={e => setEditEntry({ ...editEntry, repo_url: e.target.value })} placeholder="仓库URL" className="input-field" />
              <input value={editEntry.branch} onChange={e => setEditEntry({ ...editEntry, branch: e.target.value })} placeholder="分支" className="input-field" />
              <input value={editEntry.service_url || ''} onChange={e => setEditEntry({ ...editEntry, service_url: e.target.value })} placeholder="服务地址（B阶段验证用，如 http://8.8.8.8:8080/app）" className="input-field" />
              <select value={editEntry.build_status || ''} onChange={e => setEditEntry({ ...editEntry, build_status: e.target.value })} className="select-field">
                <option value="">构建确认：未设置（系统自动构建）</option>
                <option value="done">构建确认：成功（跳过自动构建）</option>
                <option value="failed">构建确认：失败（跳过自动构建，注入失败证据）</option>
              </select>
              <div className="flex gap-8">
                <input value={editEntry.participant} onChange={e => setEditEntry({ ...editEntry, participant: e.target.value })} placeholder="参赛者" className="input-field flex-1" />
                {track && <span className="badge badge-blue" style={{ padding: '10px 14px', borderRadius: 8, fontSize: 14, whiteSpace: 'nowrap' }}>{track}</span>}
              </div>
              {track === '赛道一' && (
                <select value={editEntry.sub_type || ''} onChange={e => setEditEntry({ ...editEntry, sub_type: e.target.value })} className="select-field">
                  <option value="">选择子类型</option>
                  <option value="新規">新規开发</option>
                  <option value="修正">修正/升级</option>
                </select>
              )}
              {track === 'L2考核' && (
                <>
                <select value={editEntry.selected_topic || 'self'} onChange={e => setEditEntry({ ...editEntry, selected_topic: e.target.value })} className="select-field">
                  <option value="self">自选题（需事前登记）</option>
                  {l2Topics.map(t => (
                    <option key={t.id} value={t.id}>命题 {t.id}｜{t.title}（{'★'.repeat(t.difficulty)} 满分{t.maxScoreCap}）</option>
                  ))}
                </select>
                {editEntry.selected_topic === 'self' && (
                  <>
                    <input value={editEntry.reg_no || ''} onChange={e => setEditEntry({ ...editEntry, reg_no: e.target.value })} placeholder="登记编号" className="input-field" />
                    <textarea value={editEntry.reg_features || ''} onChange={e => setEditEntry({ ...editEntry, reg_features: e.target.value })} placeholder="登记功能清单（每行一条）" rows={3} className="input-field" />
                  </>
                )}
                </>
              )}
              <div className="form-actions">
                <button onClick={doEditSave}>保存</button>
                <button onClick={() => setEditEntry(null)}>取消</button>
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

function DetailPanel({ entry, projectId, onClose, onSave }: { entry: any; projectId: string; onClose: () => void; onSave: () => void }) {
  const [dims, setDims] = useState<any[]>([]);
  const [dimsAgg, setDimsAgg] = useState<any>(null);
  const [overview, setOverview] = useState('');
  const [overall, setOverall] = useState<any>(null);
  const [saving, setSaving] = useState(false);
  const [editingComment, setEditingComment] = useState<number | null>(null);
  const [deliverables, setDeliverables] = useState<any[]>(() => {
    try { return JSON.parse(entry.deliverables || '[]'); } catch { return []; }
  });

  useEffect(() => {
    if (entry.ai_report) {
      try {
        const r = JSON.parse(entry.ai_report);
        setDims(r.dimensions || []);
        setOverview(r.overview || '');
        setOverall(r.overall || null);
      } catch { setDims(entry.dimensions || []); }
    } else {
      setDims(entry.dimensions || []);
    }
    setDimsAgg((entry as any).dimsAgg || null);
    try { const d = JSON.parse(entry.deliverables || '[]'); if (d.length > 0) setDeliverables(d); } catch {}
  }, [entry]);

  const totalScore = dims.reduce((s: number, d: any) => s + (Number(d.score) || 0), 0);
  const maxTotal = dims.reduce((s: number, d: any) => s + (d.maxScore || 0), 0);
  const pct = maxTotal > 0 ? Math.round((totalScore / maxTotal) * 100) : 0;

  // Old ai_report dimensions lack `group` → derive from standard_snapshot dims by name
  const stdGroupMap: Record<string, string> = {};
  for (const d of (entry.dimensions || [])) stdGroupMap[d.name] = d.group || 'common';
  const dimGroup = (d: any) => d.group || stdGroupMap[d.name] || 'common';
  const l2Dims = dims.filter((d: any) => dimGroup(d) === 'common');
  const l3Dims = dims.filter((d: any) => dimGroup(d) !== 'common');
  const l2Score = l2Dims.reduce((s, d) => s + (Number(d.score) || 0), 0);
  const l2Max = l2Dims.reduce((s, d) => s + (d.maxScore || 0), 0);
  const l3Score = l3Dims.reduce((s, d) => s + (Number(d.score) || 0), 0);
  const l3Max = l3Dims.reduce((s, d) => s + (d.maxScore || 0), 0);

  // 维度级聚合历史（2026-08-19）：dimsAgg.perRun 含各次维度分
  const dimHistory = (name: string): string => {
    if (!dimsAgg?.perRun?.length) return '';
    const scores = dimsAgg.perRun
      .map((r: any) => r.dims?.find((d: any) => d.name === name)?.score)
      .filter((s: any) => s !== undefined && s !== null);
    return scores.length > 1 ? `（历次 ${scores.join(' / ')}）` : '';
  };
  const finalLevel = entry.final_level || '';

  const updateDim = (i: number, field: string, value: any) => {
    const next = [...dims];
    (next[i] as any)[field] = value;
    setDims(next);
  };

  const saveDeliverables = async () => {
    try {
      await api.request('PUT', `/projects/${projectId}/entries/${entry.id}/deliverables`, { deliverables });
    } catch {}
  };

  const toggleDeliverable = (idx: number) => {
    const next = [...deliverables];
    next[idx] = { ...next[idx], submitted: !next[idx].submitted };
    setDeliverables(next);
    saveDeliverables();
  };

  const saveReport = async () => {
    setSaving(true);
    try {
      await api.request('PUT', `/projects/${projectId}/entries/${entry.id}/report`, { dimensions: dims });
      onSave();
    } catch (err: any) { alert(err.message); }
    setSaving(false);
  };

  return (
    <div className="detail-overlay" onClick={onClose}>
      <div className="detail-panel" onClick={e => e.stopPropagation()}>
        <div className="detail-header">
          <h3>{entry.title}</h3>
          <button className="btn-close" onClick={onClose}>✕</button>
        </div>
        <div className="detail-meta">
          <div>仓库：<code>{entry.repo_url}</code></div>
          <div>参赛者：{entry.participant || '-'} | 及格线：{entry.pass_line || '-'}分{entry.selected_topic ? ` | 选题：${entry.selected_topic === 'self' ? '自选题' : `命题 ${entry.selected_topic}`}` : ''}</div>
          <div style={{ display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}>
            {finalLevel && (
              <span className={`badge ${finalLevel === 'L3' ? 'badge-purple' : finalLevel === 'L2' ? 'badge-green' : 'badge-red'}`}>
                {finalLevel === 'L3' ? '🏆 L3合格' : finalLevel === 'L2' ? '✅ L2合格' : '❌ 不合格'}
              </span>
            )}
            <span style={{ fontSize: 15, fontWeight: 600, color: finalLevel === 'L3' ? '#8b5cf6' : pct >= (entry.pass_line || 60) ? '#10b981' : '#ef4444' }}>
              {l3Max > 0 ? `L2: ${l2Score}/${l2Max} | L3: ${l3Score}/${l3Max} | 总分: ${totalScore}/${maxTotal}（${pct}%）` : `得分：${totalScore}/${maxTotal}（${pct}%）`}
            </span>
            {entry.late_days > 0 && <span style={{ color: '#f59e0b' }}>迟交 {entry.late_days} 天</span>}
          </div>
          {entry.score_a != null && (entry.score_b != null || entry.status === 'a_done' || entry.status === 'verifying') && (
            <div style={{ marginTop: 6, fontSize: 13, color: 'var(--text-secondary)' }}>
              A阶段 {entry.score_a} 分
              {entry.score_b != null && ` · B阶段 ${entry.score_b} 分`}
              {entry.stage_b_status && entry.stage_b_status !== 'pending' && entry.stage_b_status !== 'done' && (
                <span style={{ color: entry.stage_b_status === 'failed' ? '#ef4444' : '#f59e0b', marginLeft: 8 }}>
                  · B阶段状态：{entry.stage_b_status === 'failed' ? '验证失败' : entry.stage_b_status === 'skipped' ? '已跳过' : entry.stage_b_status}
                </span>
              )}
            </div>
          )}
          {entry.attempt > 1 && <div className="retake-badge">第 {entry.attempt} 次提交</div>}
        </div>

        {entry.plagiarism_json && (() => {
          let p: any = null;
          try { p = JSON.parse(entry.plagiarism_json); } catch { }
          if (!p || !p.flags || p.flags.length === 0) return null;
          return (
            <div className="overview-section" style={{ paddingLeft: 12, borderLeft: '3px solid #f59e0b', marginBottom: 12 }}>
              <h4 style={{ marginBottom: 8, color: '#b45309' }}>查重初筛告警（需人工复核，不构成违规认定）</h4>
              {p.flags.map((f: any, i: number) => (
                <div key={i} style={{ fontSize: 13, color: 'var(--text-secondary)', marginBottom: 6, marginLeft: 12 }}>
                  <span className={`badge ${f.severity === 'high' ? 'badge-red' : 'badge-amber'} mr-8`}>{f.kind === 'identicalFile' ? '相同文件' : f.kind === 'highSimilarity' ? '高相似源码' : '提交行为'}</span>
                  {f.detail}
                </div>
              ))}
            </div>
          );
        })()}

        {(overview || overall) && (
          <div className="overview-section" style={{ paddingLeft: 12 }}>
            <h4 style={{ marginBottom: 8 }}>整体评价</h4>
            {overview && (
              <div style={{ marginBottom: 8 }}>
                <div style={{ color: 'var(--primary, #6aa1f7)', fontWeight: 600, marginBottom: 4 }}>项目总览</div>
                <div style={{ fontSize: 13, color: 'var(--text-secondary)', marginLeft: 12, marginBottom: 4, lineHeight: 1.7, whiteSpace: 'pre-wrap' }}>{overview}</div>
              </div>
            )}
            {overall && Array.isArray(overall.highlights) && overall.highlights.length > 0 && (
              <div style={{ marginBottom: 8 }}>
                <div style={{ color: 'var(--success, #2e7d32)', fontWeight: 600, marginBottom: 4 }}>核心亮点点评</div>
                {overall.highlights.map((h: any, i: number) => (
                  <div key={i} style={{ fontSize: 13, color: 'var(--text-secondary)', marginLeft: 12, marginBottom: 4 }}>
                    <span style={{ color: 'var(--text-primary)', fontWeight: 500 }}>{h.point}</span>
                    {h.review ? <span style={{ opacity: 0.85 }}> —— {h.review}</span> : null}
                  </div>
                ))}
              </div>
            )}
            {overall && Array.isArray(overall.weaknesses) && overall.weaknesses.length > 0 && (
              <div style={{ marginBottom: 8 }}>
                <div style={{ color: 'var(--danger, #c62828)', fontWeight: 600, marginBottom: 4 }}>主要不足点评</div>
                {overall.weaknesses.map((w: any, i: number) => (
                  <div key={i} style={{ fontSize: 13, color: 'var(--text-secondary)', marginLeft: 12, marginBottom: 4 }}>
                    <span style={{ color: 'var(--text-primary)', fontWeight: 500 }}>{w.point}</span>
                    {w.review ? <span style={{ opacity: 0.85 }}> —— {w.review}</span> : null}
                  </div>
                ))}
              </div>
            )}
            {overall && overall.verdict && <p style={{ lineHeight: 1.7, color: 'var(--text-secondary)', fontSize: 14, whiteSpace: 'pre-wrap' }}>{overall.verdict}</p>}
          </div>
        )}

        {entry.progress_log && (() => {
          try {
            const logs = JSON.parse(entry.progress_log);
            if (!Array.isArray(logs) || logs.length === 0) return null;
            return (
              <details className="history-section" style={{ marginBottom: 16 }}>
                <summary>评审进度（{logs.length} 步）</summary>
                {logs.map((l: any, i: number) => (
                  <div key={i} className="history-item">
                    <div style={{ fontSize: 12, color: '#666' }}>
                      {l.time ? new Date(l.time).toLocaleString() : ''}{l.status ? ` · ${l.status}` : ''}
                    </div>
                    {l.msg && <div style={{ fontSize: 12 }}>{l.msg}</div>}
                  </div>
                ))}
              </details>
            );
          } catch { return null; }
        })()}

        {/* deliverables checklist */}
        <div style={{ marginBottom: 16, padding: 16, background: 'var(--bg-subtle)', borderRadius: 'var(--radius)', border: '1px solid var(--border)' }}>
          <h4 style={{ fontSize: 14, fontWeight: 600, marginBottom: 8 }}>成果物确认</h4>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {(deliverables.length > 0 ? deliverables : DEFAULT_DELIVERABLES).map((d: any, i: number) => (
              <label key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: 13 }}>
                <input type="checkbox" checked={d.submitted} onChange={() => toggleDeliverable(i)} />
                <span style={{ textDecoration: d.submitted ? 'line-through' : 'none', color: d.submitted ? 'var(--success)' : 'var(--text)' }}>
                  {d.name}
                </span>
                {d.required && <span style={{ color: 'var(--danger)', fontSize: 11 }}>（必须）</span>}
                {!d.required && <span style={{ color: 'var(--muted)', fontSize: 11 }}>（可选）</span>}
              </label>
            ))}
          </div>
          <div style={{ marginTop: 8, fontSize: 12, color: 'var(--text-secondary)' }}>
            已提交 {deliverables.filter((d: any) => d.submitted).length}/{deliverables.length || DEFAULT_DELIVERABLES.length}
            {deliverables.filter((d: any) => d.required && !d.submitted).length > 0 && (
              <span style={{ color: 'var(--danger)', marginLeft: 8 }}>
                缺少 {deliverables.filter((d: any) => d.required && !d.submitted).length} 项必须成果物
              </span>
            )}
          </div>
        </div>

        {dims.length > 0 && <RadarChart dims={dims} size={360} />}

        {l2Dims.length > 0 && (
          <div style={{ marginBottom: 16 }}>
            <h4 style={{ fontSize: 14, fontWeight: 600, marginBottom: 8, color: 'var(--text)' }}>L2共通评分（{l2Score}/{l2Max}）</h4>
            <div style={{ overflowX: 'auto' }}>
            <table className="detail-dims">
              <thead><tr><th>评审项</th><th style={{ textAlign: 'center' }}>得分</th><th>评语</th><th>建议</th></tr></thead>
              <tbody>
                {l2Dims.map((d, i) => {
                  const idx = dims.indexOf(d);
                  return (
                    <tr key={i}>
                      <td>
                        <div className="dim-name">{d.name}</div>
                        {dimHistory(d.name) && <div style={{ fontSize: 11, color: 'var(--text-secondary)', opacity: 0.75 }}>{dimHistory(d.name)}</div>}
                        <DimBar score={Number(d.score) || 0} max={d.maxScore} />
                      </td>
                      <td style={{ textAlign: 'center', verticalAlign: 'middle' }}>
                        <input type="number" value={Number(d.score) || 0} min={0} max={d.maxScore} onChange={e => updateDim(idx, 'score', e.target.value === '' ? 0 : Number(e.target.value))} className="score-input" />
                        <span className="score-max">满分 {d.maxScore}</span>
                      </td>
                      <td>
                        {editingComment === idx ? (
                          <textarea value={d.comment || ''} onChange={e => updateDim(idx, 'comment', e.target.value)} onBlur={() => setEditingComment(null)} autoFocus rows={3} className="comment-input" placeholder="评语" />
                        ) : (
                          <span className={`dim-comment-btn ${d.comment ? '' : 'is-empty'}`} onClick={() => setEditingComment(idx)} title="点击编辑评语">{d.comment || '点击填写评语'}</span>
                        )}
                        {d.verifiability?.note && <div style={{ fontSize: 11, color: 'var(--text-secondary)', opacity: 0.8, marginTop: 4 }}>{d.verifiability.note}</div>}
                      </td>
                      <td><div className="suggestion-cell">{d.suggestion || '-'}</div></td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            </div>
          </div>
        )}

        {l3Dims.length > 0 && (
          <div style={{ marginBottom: 16 }}>
            <h4 style={{ fontSize: 14, fontWeight: 600, marginBottom: 8, color: '#8b5cf6' }}>L3追加评分（{l3Score}/{l3Max}）</h4>
            <div style={{ overflowX: 'auto' }}>
            <table className="detail-dims">
              <thead><tr><th>评审项</th><th style={{ textAlign: 'center' }}>得分</th><th>评语</th><th>建议</th></tr></thead>
              <tbody>
                {l3Dims.map((d, i) => {
                  const idx = dims.indexOf(d);
                  return (
                    <tr key={i}>
                      <td>
                        <div className="dim-name" style={{ color: '#8b5cf6' }}>{d.name}</div>
                        {dimHistory(d.name) && <div style={{ fontSize: 11, color: 'var(--text-secondary)', opacity: 0.75 }}>{dimHistory(d.name)}</div>}
                        <DimBar score={Number(d.score) || 0} max={d.maxScore} />
                      </td>
                      <td style={{ textAlign: 'center', verticalAlign: 'middle' }}>
                        <input type="number" value={Number(d.score) || 0} min={0} max={d.maxScore} onChange={e => updateDim(idx, 'score', e.target.value === '' ? 0 : Number(e.target.value))} className="score-input" />
                        <span className="score-max">满分 {d.maxScore}</span>
                      </td>
                      <td>
                        {editingComment === idx ? (
                          <textarea value={d.comment || ''} onChange={e => updateDim(idx, 'comment', e.target.value)} onBlur={() => setEditingComment(null)} autoFocus rows={3} className="comment-input" placeholder="评语" />
                        ) : (
                          <span className={`dim-comment-btn ${d.comment ? '' : 'is-empty'}`} onClick={() => setEditingComment(idx)} title="点击编辑评语">{d.comment || '点击填写评语'}</span>
                        )}
                        {d.verifiability?.note && <div style={{ fontSize: 11, color: 'var(--text-secondary)', opacity: 0.8, marginTop: 4 }}>{d.verifiability.note}</div>}
                      </td>
                      <td><div className="suggestion-cell">{d.suggestion || '-'}</div></td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            </div>
          </div>
        )}

        {entry.snapshots?.length > 0 && (
          <details className="history-section">
            <summary>评审快照（{entry.snapshots.length} 次）</summary>
            {entry.snapshots.map((s: any) => (
              <div key={s.id} className="history-item">
                第 {s.attempt} 次 · {new Date(s.created_at).toLocaleString()}
              </div>
            ))}
          </details>
        )}

        {entry.revisions?.length > 0 && (
          <details className="history-section">
            <summary>修正历史（{entry.revisions.length} 次）</summary>
            {entry.revisions.map((r: any) => {
              let oldScores = '', newScores = '';
              try { oldScores = JSON.parse(r.comments || '[]').map((x: any) => `${x.name}:${x.score}`).join('; '); } catch {}
              try { newScores = JSON.parse(r.scores || '[]').map((x: any) => `${x.name}:${x.score}`).join('; '); } catch {}
              return (
                <div key={r.id} className="history-item">
                  <div style={{ fontSize: 12, color: '#666' }}>{new Date(r.created_at).toLocaleString()}</div>
                  {oldScores && <div style={{ fontSize: 12 }}>修正前：{oldScores}</div>}
                  {newScores && <div style={{ fontSize: 12 }}>修正后：{newScores}</div>}
                </div>
              );
            })}
          </details>
        )}

        <div className="detail-actions">
          <button onClick={() => downloadPdf(`/api/projects/${projectId}/entries/${entry.id}/report/export`).catch(() => {})} className="btn-secondary" title="下载PDF报告">下载报告</button>
          <button onClick={saveReport} disabled={saving} className="btn-primary">{saving ? '保存中...' : '保存修正'}</button>
        </div>
      </div>
    </div>
  );
}

function DimBar({ score, max }: { score: number; max: number }) {
  const pct = max > 0 ? Math.max(0, Math.min(100, (Number(score) / max) * 100)) : 0;
  const color = pct >= 80 ? '#10b981' : pct >= 60 ? '#6366f1' : pct >= 40 ? '#f59e0b' : '#ef4444';
  return (
    <div className="dim-bar-row">
      <div className="dim-bar">
        <div className="dim-bar-fill" style={{ width: `${pct}%`, background: color }} />
      </div>
      <span className="dim-pct">{Math.round(pct)}%</span>
    </div>
  );
}

function RadarChart({ dims, size = 260 }: { dims: any[]; size?: number }) {
  const n = dims.length;
  if (n === 0) return null;
  const cx = size / 2, cy = size / 2, r = size * 0.38;
  const labelR = r + size * 0.07;
  const fontSize = size <= 280 ? 10 : 11;

  const angle = (i: number) => (2 * Math.PI * i) / n - Math.PI / 2;
  const pt = (i: number, radius: number) => {
    const a = angle(i);
    return { x: cx + radius * Math.cos(a), y: cy + radius * Math.sin(a) };
  };

  const scorePts = dims.map((d, i) => {
    const pct = Math.max(0, Math.min(1, d.maxScore > 0 ? Number(d.score) / d.maxScore : 0));
    const p = pt(i, r * pct);
    return `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`;
  }).join(' ') + ' Z';

  return (
    <div style={{ textAlign: 'center', margin: '16px 0' }}>
      <h4 style={{ marginBottom: 8, color: '#666', fontSize: 13 }}>维度评分</h4>
      <svg width={size + 20} height={size + 20} viewBox={`0 0 ${size} ${size}`} style={{ maxWidth: '100%' }}>
        <g>
          {[25, 50, 75, 100].map(pct => {
            const rr = r * pct / 100;
            const pts = Array.from({ length: n }, (_, i) => pt(i, rr)).map(p => `${p.x},${p.y}`).join(' ');
            return <g key={pct}>
              <polygon points={pts} fill="none" stroke="#e5e7eb" strokeWidth="1" strokeDasharray="3,3" />
              <text x={cx} y={cy - rr} fontSize="9" fill="#999" textAnchor="middle" dominantBaseline="middle">{pct}%</text>
            </g>;
          })}
          {Array.from({ length: n }, (_, i) => {
            const p = pt(i, r);
            return <line key={`ax-${i}`} x1={cx} y1={cy} x2={p.x} y2={p.y} stroke="#e5e7eb" strokeWidth="1" />;
          })}
          {dims.map((d, i) => {
            const pct = Math.max(0, Math.min(1, d.maxScore > 0 ? Number(d.score) / d.maxScore : 0));
            const p = pt(i, r * pct);
            return <circle key={`dot-${i}`} cx={p.x} cy={p.y} r="3" fill="#4f46e5" />;
          })}
          {dims.map((d, i) => {
            const p = pt(i, labelR);
            const anchor = p.x > cx + 5 ? 'start' : p.x < cx - 5 ? 'end' : 'middle';
            return <text key={`lb-${i}`} x={p.x} y={p.y} fontSize={fontSize} fill="#333" textAnchor={anchor} dominantBaseline="middle">{d.name}</text>;
          })}
        </g>
        <path d={scorePts} fill="rgba(79,70,229,0.15)" stroke="#4f46e5" strokeWidth="2" />
      </svg>
    </div>
  );
}

function BarChart({ entries, title }: { entries: any[]; title?: string }) {
  if (entries.length === 0) return null;
  const barH = 28, gap = 8, labelW = 160, chartW = 400;
  const h = entries.length * (barH + gap) + 30;
  const maxScore = Math.max(...entries.map(e => e.score || 0));

  const bars = entries.map((e, i) => {
    const y = 30 + i * (barH + gap);
    const w = maxScore > 0 ? ((e.score || 0) / maxScore) * chartW : 0;
    const color = e.passed ? '#10b981' : '#ef4444';
    return (
      <g key={e.id}>
        <text x={labelW - 6} y={y + barH / 2} fontSize="11" fill="#333" textAnchor="end" dominantBaseline="middle">{e.title}</text>
        <rect x={labelW} y={y} width={Math.max(w, 2)} height={barH} rx={4} fill={color} opacity={0.85} />
        <text x={labelW + w + 4} y={y + barH / 2} fontSize="11" fill={color} dominantBaseline="middle">{e.score}分</text>
      </g>
    );
  });

  return (
    <div style={{ textAlign: 'center', margin: '20px 0', overflowX: 'auto' }}>
      {title && <h4 style={{ marginBottom: 8, color: '#666', fontSize: 13 }}>{title}</h4>}
      <svg width={Math.min(labelW + chartW + 60, 800)} height={h} viewBox={`0 0 ${Math.min(labelW + chartW + 60, 800)} ${h}`} style={{ maxWidth: '100%' }}>
        <text x={labelW} y="16" fontSize="12" fill="#999">分数分布</text>
        {bars}
      </svg>
    </div>
  );
}

const DEFAULT_DELIVERABLES = [
  { name: '源代码', required: true, submitted: false },
  { name: 'README', required: true, submitted: false },
  { name: '设计文档', required: true, submitted: false },
  { name: '测试用例与测试结果', required: true, submitted: false },
  { name: 'AGENTS.md', required: true, submitted: false },
  { name: '样本数据', required: true, submitted: false },
  { name: '演示录屏', required: false, submitted: false },
];

function DeliverablesView({ projectId }: { projectId: string }) {
  const [entries, setEntries] = useState<any[]>([]);
  const [deliverableMap, setDeliverableMap] = useState<Record<string, any[]>>({});
  const [loading, setLoading] = useState(false);

  const loadAll = async () => {
    setLoading(true);
    try {
      const r = await api.request<{ items: any[] }>('GET', `/projects/${projectId}/entries?limit=250`);
      const items = r.items || [];
      setEntries(items);
      const map: Record<string, any[]> = {};
      for (const e of items) {
        let d: any[] = [];
        try { d = JSON.parse(e.deliverables || '[]'); } catch {}
        if (d.length === 0) {
          d = DEFAULT_DELIVERABLES.map(x => ({ ...x, submitted: false }));
        }
        map[e.id] = d;
      }
      setDeliverableMap(map);
    } catch {}
    setLoading(false);
  };

  useEffect(() => { loadAll(); }, [projectId]);

  // Initialize all entries with default deliverables
  const initAll = async () => {
    try {
      const r = await fetch(`/api/projects/${projectId}/entries/deliverables/init`, {
        method: 'PUT',
        credentials: 'include'
      });
      const result = await r.json();
      console.log('Initialized:', result.initialized);
    } catch {}
    await loadAll();
  };

  // Toggle a single deliverable
  const toggle = async (entryId: string, idx: number) => {
    const d = [...(deliverableMap[entryId] || [])];
    d[idx] = { ...d[idx], submitted: !d[idx].submitted };
    setDeliverableMap({ ...deliverableMap, [entryId]: d });
    try {
      await fetch(`/api/projects/${projectId}/entries/${entryId}/deliverables`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'include',
        body: JSON.stringify({ deliverables: d })
      });
    } catch {}
  };

  // Calculate summary
  const colNames = DEFAULT_DELIVERABLES.map(d => d.name);
  const summary = DEFAULT_DELIVERABLES.map(d => {
    let submitted = 0, total = 0;
    for (const eid of Object.keys(deliverableMap)) {
      const items = deliverableMap[eid] || [];
      const found = items.find((x: any) => x.name === d.name);
      if (found) { total++; if (found.submitted) submitted++; }
    }
    return { ...d, submitted, total };
  });

  const totalRequired = summary.filter(s => s.required).reduce((s, x) => s + x.total, 0);
  const totalSubmitted = summary.filter(s => s.required).reduce((s, x) => s + x.submitted, 0);
  const rate = totalRequired > 0 ? Math.round((totalSubmitted / totalRequired) * 100) : 0;

  if (loading && entries.length === 0) return <div className="loading">加载中...</div>;

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
        <div>
          <h3 style={{ margin: 0 }}>成果物确认</h3>
          <div style={{ fontSize: 13, color: 'var(--text-secondary)', marginTop: 4 }}>
            {entries.length}个条目 · 提交率 {rate}%（{totalSubmitted}/{totalRequired}）
          </div>
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button onClick={initAll} className="btn-secondary">初始化一覧</button>
          <button onClick={() => downloadPdf(`/api/projects/${projectId}/entries/deliverables/export`).catch(() => {})} className="btn-secondary">下载CSV</button>
        </div>
      </div>

      {/* Summary cards */}
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 16 }}>
        {summary.map((s: any) => (
          <div key={s.name} style={{ padding: '8px 14px', background: 'var(--card)', borderRadius: 'var(--radius)', border: '1px solid var(--border)', fontSize: 13 }}>
            <div style={{ fontWeight: 600, fontSize: 12 }}>{s.name}</div>
            <div style={{ color: s.submitted === s.total ? 'var(--success)' : 'var(--danger)', fontSize: 15, fontWeight: 700 }}>
              {s.submitted}/{s.total}
            </div>
            <div style={{ fontSize: 11, color: 'var(--muted)' }}>{s.required ? '必须' : '可选'}</div>
          </div>
        ))}
      </div>

      {/* Checklist table */}
      <div className="deliverables-scroll" style={{ overflowX: 'auto', overflowY: 'auto', maxHeight: 'calc(100vh - 430px)' }}>
        <table className="entry-table" style={{ minWidth: 1000 }}>
          <thead>
            <tr>
              <th style={{ position: 'sticky', left: 0, top: 0, background: 'var(--bg-subtle)', zIndex: 2, width: 130 }}>参赛者</th>
              <th style={{ position: 'sticky', left: 130, top: 0, background: 'var(--bg-subtle)', zIndex: 2, width: 200 }}>标题</th>
              {colNames.map((name, ci) => (
                <th key={ci} style={{ position: 'sticky', top: 0, fontSize: 11, textAlign: 'center', minWidth: 80 }}>
                  {name.replace('与测试结果', '')}
                  <div style={{ fontSize: 10, color: 'var(--muted)', fontWeight: 400 }}>
                    {summary[ci]?.submitted}/{summary[ci]?.total}
                  </div>
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {entries.map((e) => {
              const d = deliverableMap[e.id] || [];
              const hasDetected = d.some((x: any) => x.submitted);
              return (
                <tr key={e.id} style={{ opacity: d.length === 0 ? 0.5 : 1 }}>
                  <td style={{ position: 'sticky', left: 0, background: 'var(--card)', zIndex: 1, fontWeight: 500, width: 130 }}>
                    {e.participant || e.title.substring(0, 8)}
                  </td>
                  <td style={{ position: 'sticky', left: 130, background: 'var(--card)', zIndex: 1, fontSize: 12, width: 200, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {e.title}
                    {hasDetected && (
                      <span style={{ marginLeft: 6, fontSize: 10, color: 'var(--success)', border: '1px solid var(--success)', borderRadius: 4, padding: '0 4px', flexShrink: 0 }}>
                        已检测
                      </span>
                    )}
                  </td>
                  {colNames.map((name, ci) => {
                    const item = d.find((x: any) => x.name === name);
                    const checked = item?.submitted || false;
                    const isReq = DEFAULT_DELIVERABLES[ci]?.required;
                    return (
                      <td key={ci} style={{ textAlign: 'center' }}>
                        <input
                          type="checkbox"
                          checked={checked}
                          onChange={() => {
                            const idx = d.findIndex((x: any) => x.name === name);
                            if (idx >= 0) toggle(e.id, idx);
                          }}
                          style={{ cursor: 'pointer', width: 16, height: 16, accentColor: isReq ? 'var(--success)' : undefined }}
                        />
                      </td>
                    );
                  })}
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
      {entries.length === 0 && (
        <div className="empty" style={{ padding: '60px 40px' }}>
          <div style={{ fontSize: 32, marginBottom: 16 }}>📦</div>
          <div style={{ fontSize: 16, fontWeight: 600, marginBottom: 8 }}>暂无条目</div>
          <div style={{ fontSize: 14, color: 'var(--text-secondary)', maxWidth: 400, margin: '0 auto', lineHeight: 1.7 }}>
            请先在「条目」标签页中添加参赛条目。<br />
            添加后返回此页，点击「初始化一覧」按钮为所有条目设置成果物清单，然后逐一确认提交状态。
          </div>
        </div>
      )}
      {entries.length > 0 && totalRequired === 0 && (
        <div className="empty" style={{ padding: '40px 40px', marginTop: 16 }}>
          <div style={{ fontSize: 14, color: 'var(--text-secondary)', marginBottom: 12 }}>
            条目已存在，但尚未初始化成果物清单。
          </div>
          <button onClick={initAll} className="btn-primary" style={{ padding: '8px 24px' }}>初始化一覧</button>
        </div>
      )}
    </div>
  );
}

function SummaryView({ projectId }: { projectId: string }) {
  const [summary, setSummary] = useState<any>(null);
  useEffect(() => { api.request('GET', `/projects/${projectId}/summary`).then(setSummary).catch(() => {}); }, [projectId]);

  // L2考核 按 final_level（合格/不合格）展示认定结果，其余赛道按是否达线展示
  const isL2Cat = (c: string) => c === 'L2考核';

  if (!summary) return <div className="loading">加载中...</div>;

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
        <h3 style={{ margin: 0 }}>汇总排名</h3>
        <button onClick={() => downloadPdf(`/api/projects/${projectId}/summary/export`).catch(() => {})} className="btn-secondary">下载汇总PDF</button>
      </div>

      {/* Category groups */}
      {summary.categories?.map((cat: any) => {
        const showLevel = isL2Cat(cat.category);
        return (
          <div key={cat.category} className="summary-section">
            <h4>{cat.category} · {cat.entries.length}个条目</h4>
            <table className="entry-table">
              <thead><tr><th>排名</th><th>标题</th><th>参赛者</th><th>得分</th><th>及格线</th><th>{showLevel ? '认定' : '结果'}</th></tr></thead>
              <tbody>
                {cat.entries.map((e: any) => (
                  <tr key={e.id}>
                    <td className="rank">#{e.rank}</td>
                    <td>{e.title}</td>
                    <td>{e.participant}</td>
                    <td style={{ fontWeight: 600 }}>{e.score}{e.aggregate_count > 0 ? (e.is_formal ? `（聚合${e.aggregate_count}次）` : `（初评${e.aggregate_count}次）`) : ''}</td>
                    <td>{e.pass_line}</td>
                    <td>
                      {showLevel && e.final_level ? (
                        <span className={`badge ${e.final_level === '合格' || e.final_level === 'L2' ? 'badge-green' : e.final_level === 'L3' ? 'badge-purple' : 'badge-red'}`}>
                          {e.final_level}
                        </span>
                      ) : (
                        e.passed ? <span className="pass">✅ 通过</span> : <span className="fail">❌ 未达线</span>
                      )}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
            {cat.entries.length > 1 && <BarChart entries={cat.entries} />}
          </div>
        );
      })}

      {/* Participant summary */}
      {summary.participants?.length > 0 && (
        <>
          <h4 style={{ marginTop: 24 }}>参赛者合格判定</h4>
          <table className="entry-table">
            <thead><tr><th>参赛者</th><th>题目</th><th>得分</th><th>及格线</th><th>总评</th></tr></thead>
            <tbody>
              {summary.participants.map((p: any) => (
                <tr key={p.participant}>
                  <td><strong>{p.participant}</strong></td>
                  <td>{p.entries.map((e: any) => e.title).join(', ')}</td>
                  <td>{p.entries.map((e: any) => e.score).join(' / ')}</td>
                  <td>{p.entries.map((e: any) => e.pass_line).join(' / ')}</td>
                  <td>{p.passed ? <span className="pass">✅ 通过</span> : <span className="fail">❌ 未通过</span>}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </>
      )}
    </div>
  );
}
