// review-database.jsx - review/source database page.
// Receives derived rows and save callbacks from app.jsx; owns no repositories, persistence, or science logic.

function reviewDbText(value, fallback = '/') {
  const text = value == null ? '' : String(value).trim();
  return text || fallback;
}

function reviewDbDate(value) {
  const text = reviewDbText(value, '');
  if (!text) return '/';
  return text.length > 16 ? text.slice(0, 16).replace('T', ' ') : text;
}

function reviewDbStatusLabel(status) {
  if (status === 'confirmed' || status === 'reviewed') return 'Confirmed / 已确认';
  if (status === 'draft') return 'Draft / 草稿';
  if (status === 'stale') return 'Stale / 来源已变化';
  if (status === 'missing') return 'Missing / 来源缺失';
  if (status === 'legacy') return 'Legacy / 待重新确认';
  return 'Pending / 待审核';
}

const REVIEW_DB_CMJ_TRAINING_FOCUS = [
  ['monitor-only-retest', 'Monitor / retest · 观察与复测'],
  ['max-strength-emphasis', 'Max strength · 最大力量'],
  ['force-capacity-emphasis', 'Force capacity · 力量能力'],
  ['velocity-ballistic-emphasis', 'Velocity / ballistic · 速度与爆发'],
  ['braking-reactive-emphasis', 'Braking / reactive · 制动与反应'],
  ['asymmetry-monitoring', 'Asymmetry monitoring · 不对称监控'],
  ['reactive-fast-intent-emphasis', 'Reactive fast intent · 快速反应意图'],
];
const REVIEW_DB_PAGE_SIZE = 100;

function reviewDbModeLabel(row) {
  if (!row) return '/';
  if (row.canEditReview) return 'Inline Review / 页内审核';
  if (row.canOpenReport || row.canOpenReview) return 'Source Report / 来源报告';
  if (row.canOpenSource) return 'Source Re-entry / 回到来源';
  return 'Read Only / 只读';
}

function reviewDbSourceLabel(type) {
  if (type === 'cmj') return 'CMJ';
  if (type === 'sj') return 'SJ';
  if (type === 'imtp') return 'IMTP';
  if (type === 'field') return 'Field Test';
  if (type === 'manual') return 'Manual Entry';
  return reviewDbText(type, 'Source');
}

// Consumption read-back: count how many training sessions reference this review row.
// Scans every session.linkedEvidenceRefs across all programs. Two ref shapes match:
//   1. REV-R3 links store `refId === row.id` (e.g. "cmj:<sessionId>") — exact match.
//   2. Legacy CMJ links store `refId` as the reviewed-evidence seed id
//      ("<review.id>" or "cmj_review_<sessionId>"), which embeds the CMJ sessionId.
//      For cmj rows we also count refs whose refId contains row.sessionId.
// Non-cmj rows only use the exact match, so the legacy bridge never over-counts.
function reviewDbCountTrainingLinks(programs, row) {
  if (!row) return 0;
  const list = Array.isArray(programs) ? programs : [];
  const rowId = row.id;
  const sessionId = row.sourceType === 'cmj' ? String(row.sessionId || '').trim() : '';
  let count = 0;
  list.forEach(program => {
    const sessions = Array.isArray(program?.sessions) ? program.sessions : [];
    sessions.forEach(session => {
      const refs = Array.isArray(session?.linkedEvidenceRefs) ? session.linkedEvidenceRefs : [];
      refs.forEach(ref => {
        const refId = ref == null ? '' : String(ref.refId || '').trim();
        if (!refId) return;
        if (refId === rowId) { count += 1; return; }
        if (sessionId && refId.indexOf(sessionId) !== -1) count += 1;
      });
    });
  });
  return count;
}

function reviewDbFinite(value) {
  return typeof value === 'number' && Number.isFinite(value);
}

function reviewDbRepresentativeTrialIndex(session) {
  return session?.representativeTrialIndex
    ?? session?.representative?.index
    ?? session?.bestTrialIndex
    ?? session?.trials?.[0]?.index
    ?? null;
}

function reviewDbCompactChartModel(session, trial) {
  const curve = trial?.curve;
  if (!curve?.t?.length || !curve?.f?.length) return null;
  const bodyMass = session?.protocol?.bodyMass;
  const bw = reviewDbFinite(bodyMass) && bodyMass > 0 ? bodyMass * 9.81 : null;
  return {
    compact: true,
    time: curve.t,
    total: bw ? curve.f.map(value => value * bw) : curve.f,
    left: null,
    right: null,
    forceUnit: bw ? 'N' : 'BW',
    phases: null,
    velAt: index => curve.v?.[index] ?? null,
    dispAt: index => curve.d?.[index] ?? null,
    bw_n: bw,
  };
}

function reviewDbChartSeries(model, chartType) {
  if (!model?.time?.length || !model?.total?.length) return [];
  const onset = model.phases?.onset ?? 0;
  const takeoff = model.phases?.takeoff ?? model.time.length - 1;
  const start = chartType === 'normalized' ? onset : 0;
  const end = chartType === 'normalized' ? takeoff : model.time.length - 1;
  const xValue = (index) => {
    if (chartType === 'normalized') return end > start ? ((index - start) / (end - start)) * 100 : 0;
    if (chartType === 'fd') return model.dispAt(index);
    if (chartType === 'fv') return model.velAt(index);
    return model.time[index];
  };
  const make = (key, label, color) => {
    const values = model[key];
    if (!values?.length) return null;
    const points = [];
    for (let index = start; index <= end; index += 1) {
      const x = xValue(index);
      const y = values[index];
      if (reviewDbFinite(x) && reviewDbFinite(y)) points.push({ x, y });
    }
    return points.length > 1 ? { key, label, color, points } : null;
  };
  if (model.left?.length && model.right?.length) {
    return [
      make('left', 'Left / 左', '#5b6fb5'),
      make('right', 'Right / 右', '#55a37b'),
      make('total', 'Total / 总力', '#202938'),
    ].filter(Boolean);
  }
  return [make('total', 'Total / 总力', '#202938')].filter(Boolean);
}

function ReviewDbEvidenceChart({ model, chartType }) {
  const series = React.useMemo(() => reviewDbChartSeries(model, chartType), [model, chartType]);
  if (!series.length) {
    return <div className="review-db-evidence-empty">No chartable source data / 当前来源没有可绘制数据</div>;
  }
  const all = series.flatMap(item => item.points);
  const xs = all.map(point => point.x);
  const ys = all.map(point => point.y);
  let minX = Math.min(...xs), maxX = Math.max(...xs);
  let minY = Math.min(...ys), maxY = Math.max(...ys);
  if (minX === maxX) maxX = minX + 1;
  if (minY === maxY) maxY = minY + 1;
  const padY = Math.max((maxY - minY) * 0.08, 1);
  minY -= padY;
  maxY += padY;
  const px = value => 58 + ((value - minX) / (maxX - minX)) * 782;
  const py = value => 300 - ((value - minY) / (maxY - minY)) * 250;
  const path = points => points.map((point, index) => `${index ? 'L' : 'M'}${px(point.x).toFixed(2)},${py(point.y).toFixed(2)}`).join(' ');
  const xLabel = chartType === 'normalized' ? 'Movement cycle / 动作周期 (%)'
    : chartType === 'fd' ? 'Displacement / 位移 (m)'
    : chartType === 'fv' ? 'Velocity / 速度 (m/s)'
    : 'Time / 时间 (s)';
  return (
    <div className="review-db-chart-frame">
      <svg viewBox="0 0 880 350" role="img" aria-label="Same-page review evidence chart">
        {[0, 1, 2, 3, 4].map(index => {
          const y = 50 + index * 62.5;
          return <line key={index} x1="58" y1={y} x2="840" y2={y} className="review-db-chart-grid" />;
        })}
        <line x1="58" y1="300" x2="840" y2="300" className="review-db-chart-axis" />
        <line x1="58" y1="50" x2="58" y2="300" className="review-db-chart-axis" />
        {series.map(item => <path key={item.key} d={path(item.points)} fill="none" stroke={item.color} strokeWidth={item.key === 'total' ? 2.4 : 1.8} vectorEffect="non-scaling-stroke" />)}
        <text x="449" y="338" textAnchor="middle" className="review-db-chart-label">{xLabel}</text>
        <text x="17" y="175" textAnchor="middle" transform="rotate(-90 17 175)" className="review-db-chart-label">Force / 力 ({model.forceUnit || 'N'})</text>
      </svg>
      <div className="review-db-chart-legend">
        {series.map(item => <span key={item.key}><i style={{ background: item.color }} />{item.label}</span>)}
      </div>
    </div>
  );
}

function ReviewDbEvidencePanel({ row, forceTraceRead, forceTraceReadModel, reviewKnowledgeReadModel, trainingLinkCount }) {
  const session = row?.sourceData || null;
  const trials = Array.isArray(session?.trials) ? session.trials : [];
  const [trialIndex, setTrialIndex] = React.useState(() => reviewDbRepresentativeTrialIndex(session));
  const [chartType, setChartType] = React.useState('raw');
  const [traceState, setTraceState] = React.useState('idle');
  const [traceBundle, setTraceBundle] = React.useState(new Map());
  const loadGeneration = React.useRef(0);

  React.useEffect(() => {
    setTrialIndex(reviewDbRepresentativeTrialIndex(session));
    setChartType('raw');
  }, [row?.id]);

  React.useEffect(() => {
    const generation = ++loadGeneration.current;
    setTraceBundle(new Map());
    if (row?.sourceType !== 'cmj' || !session?.id || !forceTraceRead?.listBySession || !forceTraceReadModel?.buildTraceBundle) {
      setTraceState('not-applicable');
      return () => { loadGeneration.current += 1; };
    }
    setTraceState('loading');
    Promise.resolve(forceTraceRead.listBySession(session.id)).then(records => {
      if (generation !== loadGeneration.current) return;
      setTraceBundle(forceTraceReadModel.buildTraceBundle(records, session));
      setTraceState('ready');
    }).catch(() => {
      if (generation !== loadGeneration.current) return;
      setTraceState('storage-error');
    });
    return () => { loadGeneration.current += 1; };
  }, [row?.id, session?.id, forceTraceRead, forceTraceReadModel]);

  const selectedTrial = trials.find(trial => trial?.index === trialIndex) || trials[0] || null;
  const fullEntry = traceBundle.get(selectedTrial?.index);
  const chartModel = fullEntry?.state === 'ready'
    ? { ...fullEntry.chartModel, forceUnit: 'N' }
    : reviewDbCompactChartModel(session, selectedTrial);
  const metrics = selectedTrial?.metrics || session?.representative?.metrics || session?.best || {};
  const metricEntries = Object.entries(metrics)
    .filter(([, value]) => reviewDbFinite(value))
    .slice(0, 12);
  const persistentAvailability = row?.sourceType === 'cmj' && forceTraceReadModel?.deriveAvailability
    ? forceTraceReadModel.deriveAvailability(session)
    : 'not-applicable';
  // Keep the live page light: expose the index contract during rendering; a future
  // A future assistant calls buildDetailDocument only for the selected source so raw arrays
  // are serialized on demand rather than copied on every React render.
  const knowledgeDocument = reviewKnowledgeReadModel?.buildIndexDocument
    ? reviewKnowledgeReadModel.buildIndexDocument(row, {
        trainingLinkCount,
        traceAvailability: persistentAvailability,
      })
    : null;
  const chartTabs = [
    ['raw', 'Raw F-t / 原始 F-t'],
    ['normalized', 'Normalized / 归一化'],
    ['fd', 'F-D'],
    ['fv', 'F-V'],
    ['phase', 'Phase & asymmetry / 阶段与不对称'],
  ];

  return (
    <section className="review-db-evidence" aria-label="Same-page chart evidence review">
      <div className="review-db-evidence-head">
        <div>
          <span className="review-db-kicker">EVIDENCE / 同屏证据</span>
          <h2>{reviewDbText(row?.athleteName, 'Unknown athlete')}</h2>
          <p>{reviewDbText(row?.sourceLabel, 'Source')} · {reviewDbDate(row?.sourceDate)}</p>
        </div>
        <div className="review-db-truth-state">
          <strong>{chartModel?.compact ? 'Degraded curve / 压缩曲线' : chartModel ? 'Full trace / 完整原始曲线' : 'No chart / 无图形'}</strong>
          <span>{traceState === 'storage-error' ? 'Trace storage unavailable / 原始曲线存储不可用' : `${persistentAvailability} · ${traceState}`}</span>
        </div>
      </div>

      {!!trials.length && (
        <div className="review-db-trials" aria-label="Trial selection">
          {trials.map(trial => (
            <button
              key={trial.index}
              type="button"
              className={trial.index === selectedTrial?.index ? 'active' : ''}
              aria-pressed={trial.index === selectedTrial?.index}
              onClick={() => setTrialIndex(trial.index)}
            >
              Trial {trial.index}
              {trial.index === reviewDbRepresentativeTrialIndex(session) && <small>Representative / 代表</small>}
            </button>
          ))}
        </div>
      )}

      <div className="review-db-chart-tabs" aria-label="Evidence chart type">
        {chartTabs.map(([id, label]) => (
          <button key={id} type="button" className={chartType === id ? 'active' : ''} aria-pressed={chartType === id} onClick={() => setChartType(id)}>
            {label}
          </button>
        ))}
      </div>

      <ReviewDbEvidenceChart model={chartModel} chartType={chartType} />

      <div className="review-db-evidence-meta">
        <span><b>Source / 来源</b>{session?.provenance?.fileName || row?.sourceLabel || '/'}</span>
        <span><b>Sampling / 采样</b>{session?.algorithmRef?.sampling?.effectiveHz || session?.protocol?.sampleRate || '/'} Hz</span>
        <span><b>Algorithm / 算法</b>{session?.algorithmRef?.version || '/'}</span>
        <span><b>Data read contract</b>{knowledgeDocument ? `v${knowledgeDocument.schemaVersion} · on-demand detail ready` : 'unavailable'}</span>
      </div>

      <div className="review-db-evidence-metrics">
        {metricEntries.length ? metricEntries.map(([key, value]) => (
          <div key={key}><span>{key}</span><strong>{Number(value).toLocaleString(undefined, { maximumFractionDigits: 3 })}</strong></div>
        )) : <div className="review-db-evidence-empty">No structured metrics / 当前来源无结构化指标</div>}
      </div>
      {chartType === 'phase' && (
        <div className="review-db-phase-note">
          Phase indices and asymmetry metrics are shown from the saved source only. 阶段索引与不对称指标仅展示已保存事实，不在审核页重算。
        </div>
      )}
    </section>
  );
}

function ReviewDatabasePage({
  rows = [],
  programs = [],
  forceTraceRead = null,
  forceTraceReadModel = null,
  reviewKnowledgeReadModel = null,
  onOpenReview,
  onOpenSource,
  onOpenReport,
  onSaveReview,
  onLinkToTraining,
  reviewerName = '',
  onReviewerNameChange,
}) {
  const safeRows = Array.isArray(rows) ? rows : [];
  const safePrograms = Array.isArray(programs) ? programs : [];
  const [filter, setFilter] = React.useState('all');
  const [query, setQuery] = React.useState('');
  const [selectedId, setSelectedId] = React.useState(null);
  const [workspaceMode, setWorkspaceMode] = React.useState('queue');
  const [pendingFirst, setPendingFirst] = React.useState(true);
  const [pageIndex, setPageIndex] = React.useState(0);
  const tableBodyRef = React.useRef(null);
  const [draft, setDraft] = React.useState({ status: 'draft', conclusion: '', trainingFocus: '', note: '' });
  const [saveState, setSaveState] = React.useState('');
  const [lastSavedAt, setLastSavedAt] = React.useState('');
  const filteredRows = React.useMemo(() => {
    const normalizedQuery = query.trim().toLowerCase();
    const matched = safeRows.filter(row => {
      if (filter === 'all') return true;
      if (filter === 'needs-review') return row.lifecycleState !== 'confirmed';
      if (filter === 'reviewed') return row.lifecycleState === 'confirmed';
      if (filter === 'missing-source') return !row.sourceAvailable;
      return row.sourceType === filter;
    }).filter(row => {
      if (!normalizedQuery) return true;
      return [
        row.athleteName, row.sourceLabel, row.sourceTypeLabel, row.sessionId,
        row.assessmentId, row.metricId, row.conclusion, row.trainingFocus,
        row.noteExcerpt, row.reviewedBy?.displayName, row.sourceDate,
      ].some(value => String(value || '').toLowerCase().includes(normalizedQuery));
    });
    if (!pendingFirst) return matched;
    // Pending-first: unreviewed rows float to the top, existing order preserved within groups.
    return matched
      .map((row, index) => ({ row, index }))
      .sort((a, b) => {
        const aPending = a.row.lifecycleState !== 'confirmed' ? 0 : 1;
        const bPending = b.row.lifecycleState !== 'confirmed' ? 0 : 1;
        if (aPending !== bPending) return aPending - bPending;
        return a.index - b.index;
      })
      .map(item => item.row);
  }, [safeRows, filter, pendingFirst, query]);
  const pageCount = Math.max(1, Math.ceil(filteredRows.length / REVIEW_DB_PAGE_SIZE));
  const safePageIndex = Math.min(pageIndex, pageCount - 1);
  const pageStart = safePageIndex * REVIEW_DB_PAGE_SIZE;
  const pagedRows = filteredRows.slice(pageStart, pageStart + REVIEW_DB_PAGE_SIZE);
  const selectedRow = filteredRows.find(row => row.id === selectedId) || filteredRows[0] || null;
  React.useEffect(() => {
    setPageIndex(0);
    tableBodyRef.current?.scrollTo({ top: 0 });
  }, [filter, query, pendingFirst]);

  const goToPage = (nextPage) => {
    const bounded = Math.max(0, Math.min(nextPage, pageCount - 1));
    setPageIndex(bounded);
    setSelectedId(filteredRows[bounded * REVIEW_DB_PAGE_SIZE]?.id || null);
    tableBodyRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
  };

  const scrollReviewToTop = () => {
    let node = tableBodyRef.current;
    while (node) {
      if (typeof node.scrollTo === 'function') node.scrollTo({ top: 0, behavior: 'smooth' });
      else node.scrollTop = 0;
      node = node.parentElement;
    }
    window.scrollTo?.({ top: 0, behavior: 'smooth' });
  };

  const sourceTypes = React.useMemo(() => {
    const order = ['cmj', 'sj', 'imtp', 'field', 'manual'];
    return order
      .map(type => {
        const rowsOfType = safeRows.filter(row => row.sourceType === type);
        return {
          id: type,
          label: reviewDbSourceLabel(type),
          count: rowsOfType.length,
          editable: rowsOfType.some(row => row.canEditReview),
        };
      })
      .filter(item => item.count > 0);
  }, [safeRows]);

  const filters = [
    { id: 'all', label: 'All Sources / 全部来源', count: safeRows.length },
    { id: 'needs-review', label: 'Needs Review / 待审核', count: safeRows.filter(row => row.lifecycleState !== 'confirmed').length },
    { id: 'reviewed', label: 'Confirmed / 已确认', count: safeRows.filter(row => row.lifecycleState === 'confirmed').length },
    { id: 'missing-source', label: 'Missing Source / 来源缺失', count: safeRows.filter(row => !row.sourceAvailable).length },
    ...sourceTypes,
  ];

  const summary = {
    reviewed: safeRows.filter(row => row.lifecycleState === 'confirmed').length,
    pending: safeRows.filter(row => row.lifecycleState !== 'confirmed').length,
    sources: safeRows.filter(row => row.sourceAvailable).length,
    editable: safeRows.filter(row => row.canEditReview).length,
  };
  React.useEffect(() => {
    setDraft({
      status: selectedRow?.lifecycleState === 'confirmed' ? 'reviewed' : 'draft',
      conclusion: selectedRow?.conclusion || '',
      trainingFocus: selectedRow?.editorTrainingFocus || selectedRow?.trainingFocus || '',
      note: selectedRow?.noteExcerpt || '',
    });
    setSaveState('');
    setLastSavedAt('');
  }, [selectedRow?.id]);

  // Keyboard-flow queue: j/k or ArrowUp/Down move the selection, Enter focuses the
  // inline conclusion field, Esc returns focus to the list. Listener is bound only
  // while this page is mounted (cleanup on unmount) so it never leaks to global scope.
  React.useEffect(() => {
    const isTextTarget = (el) => {
      if (!el) return false;
      const tag = (el.tagName || '').toLowerCase();
      return tag === 'input' || tag === 'textarea' || tag === 'select' || el.isContentEditable;
    };
    const onKeyDown = (event) => {
      const target = event.target;
      if (event.key === 'Escape') {
        if (isTextTarget(target)) {
          target.blur();
          event.preventDefault();
        }
        return;
      }
      if (isTextTarget(target)) return;
      const key = event.key;
      const list = filteredRows;
      if (!list.length) return;
      const current = list.findIndex(row => row.id === selectedRow?.id);
      if (key === 'j' || key === 'ArrowDown') {
        event.preventDefault();
        const nextIndex = Math.min(current + 1, list.length - 1);
        const next = list[nextIndex] || list[0];
        if (next) {
          setSelectedId(next.id);
          setPageIndex(Math.floor(nextIndex / REVIEW_DB_PAGE_SIZE));
        }
      } else if (key === 'k' || key === 'ArrowUp') {
        event.preventDefault();
        const prevIndex = Math.max(current - 1, 0);
        const prev = list[prevIndex] || list[0];
        if (prev) {
          setSelectedId(prev.id);
          setPageIndex(Math.floor(prevIndex / REVIEW_DB_PAGE_SIZE));
        }
      } else if (key === 'Enter') {
        if (selectedRow?.canEditReview) {
          event.preventDefault();
          document.getElementById('review-db-inline-conclusion')?.focus();
        }
      }
    };
    window.addEventListener('keydown', onKeyDown);
    return () => window.removeEventListener('keydown', onKeyDown);
  }, [filteredRows, selectedRow?.id, selectedRow?.canEditReview]);

  const sourceChip = (row) => (
    <span className={`review-db-source source-${row.sourceType || 'generic'}`}>{reviewDbText(row.sourceTypeLabel, 'Source')}</span>
  );
  const trainingLinkCount = (row) => reviewDbCountTrainingLinks(safePrograms, row);
  const consumptionLabel = (row) => {
    if (!row || row.lifecycleState !== 'confirmed') return null;
    const n = trainingLinkCount(row);
    return n > 0
      ? `Linked to training ×${n} / 已被引用于 训练×${n}`
      : 'Not yet referenced / 尚未被引用';
  };
  const updateDraft = (key, value) => {
    setDraft(current => ({ ...current, [key]: value }));
    setSaveState('Unsaved changes / 未保存修改');
    setLastSavedAt('');
  };
  const saveInlineReview = (status) => {
    if (!selectedRow?.canEditReview) return;
    if (!reviewerName.trim()) {
      setSaveState('Reviewer name required / 请先填写审核人姓名');
      return;
    }
    const saved = onSaveReview?.(selectedRow, { ...draft, status });
    if (saved) {
      setDraft(current => ({ ...current, status }));
      setSaveState(status === 'reviewed' ? 'Saved as reviewed / 已标记已审核' : 'Draft saved / 草稿已保存');
      setLastSavedAt(reviewDbDate(saved.reviewedAt || saved.updatedAt || new Date().toISOString()));
    } else {
      setSaveState('Unable to save / 无法保存');
      setLastSavedAt('');
    }
  };

  return (
    <main className={`review-db-page mode-${workspaceMode}`}>
      <section className="review-db-header">
        <div>
          <div className="review-db-kicker">WORKSPACE / REVIEW</div>
          <h1>Review Database / 审核数据库</h1>
          <p>Uploaded sessions and test records in one review queue. 上传数据与测试记录的统一审核入口。</p>
        </div>
        <label className="review-db-reviewer" style={{ display: 'grid', gap: '6px', maxWidth: '320px', marginTop: '12px' }}>
          <span style={{ fontSize: '11px', color: 'var(--muted)', letterSpacing: '.04em' }}>Reviewer / 审核人</span>
          <input
            value={reviewerName}
            onChange={event => onReviewerNameChange?.(event.target.value)}
            placeholder="Required for draft and confirmation / 保存与确认必填"
          />
        </label>
        <div className="review-db-metrics" aria-label="Review database summary">
          <div><strong>{safeRows.length}</strong><span>Total / 全部</span></div>
          <div><strong>{summary.pending}</strong><span>Pending / 待审核</span></div>
          <div><strong>{summary.reviewed}</strong><span>Reviewed / 已审核</span></div>
          <div><strong>{summary.sources}</strong><span>Linked / 已接通</span></div>
          <div><strong>{summary.editable}</strong><span>Editable / 可审核</span></div>
        </div>
        <div className="review-db-queue-badge" aria-label="Review queue counts" style={{ display: 'flex', alignItems: 'center', gap: '10px', marginTop: '10px', fontSize: '11px', color: 'var(--muted)' }}>
          <span>Queue / 队列: <strong style={{ color: 'var(--text-2)', fontVariantNumeric: 'tabular-nums' }}>{summary.pending}</strong> pending / 待审 · <strong style={{ color: 'var(--text-2)', fontVariantNumeric: 'tabular-nums' }}>{summary.reviewed}</strong> reviewed / 已审</span>
          <button
            type="button"
            className={`review-db-filter${pendingFirst ? ' active' : ''}`}
            style={{ width: 'auto', margin: 0, padding: '5px 10px' }}
            aria-pressed={pendingFirst}
            onClick={() => setPendingFirst(v => !v)}
          >
            <span>Pending first / 待审优先</span>
            <strong>{pendingFirst ? 'On / 开' : 'Off / 关'}</strong>
          </button>
          <span style={{ color: 'var(--muted-2)' }}>Keys / 键盘: j·k move / 移动 · Enter review / 审核 · Esc list / 回列表</span>
        </div>
        <div className="review-db-source-map" aria-label="Review database source coverage">
          {sourceTypes.length ? sourceTypes.map(item => (
            <div key={item.id}>
              <strong>{item.label}</strong>
              <span> · {item.count} sources / 来源 · {item.editable ? 'Inline review / 页内审核' : 'Source re-entry / 回到来源'}</span>
            </div>
          )) : <div><strong>No sources</strong><span>No reviewable sources loaded / 暂无可审核来源</span></div>}
        </div>
        <nav className="review-db-workspace-tabs" aria-label="Review workspace mode">
          <button type="button" className={workspaceMode === 'queue' ? 'active' : ''} aria-pressed={workspaceMode === 'queue'} onClick={() => setWorkspaceMode('queue')}>
            Review queue / 审核队列
          </button>
          <button type="button" className={workspaceMode === 'evidence' ? 'active' : ''} aria-pressed={workspaceMode === 'evidence'} disabled={!selectedRow} onClick={() => setWorkspaceMode('evidence')}>
            Evidence review / 证据审核
          </button>
          <button type="button" className={workspaceMode === 'lifecycle' ? 'active' : ''} aria-pressed={workspaceMode === 'lifecycle'} disabled={!selectedRow} onClick={() => setWorkspaceMode('lifecycle')}>
            Lifecycle / 生命周期
          </button>
        </nav>
      </section>

      <section className={`review-db-shell mode-${workspaceMode}`}>
        <aside className="review-db-filters">
          <div className="review-db-panel-title">Views / 视图</div>
          {filters.map(item => (
            <button
              key={item.id}
              type="button"
              className={`review-db-filter${filter === item.id ? ' active' : ''}`}
              onClick={() => setFilter(item.id)}
            >
              <span>{item.label}</span>
              <strong>{item.count}</strong>
            </button>
          ))}
        </aside>

        {workspaceMode === 'queue' ? (
        <section className="review-db-table" aria-label="Review database rows">
          <div className="review-db-table-tools">
            <label className="review-db-search">
              <span>Search / 搜索</span>
              <input
                type="search"
                value={query}
                onChange={event => setQuery(event.target.value)}
                placeholder="Athlete, source, conclusion… / 运动员、来源、结论…"
                aria-label="Search review database"
              />
              {query && <button type="button" onClick={() => setQuery('')} aria-label="Clear review search">清除</button>}
            </label>
            <span className="review-db-result-count">
              {filteredRows.length ? `${pageStart + 1}–${Math.min(pageStart + REVIEW_DB_PAGE_SIZE, filteredRows.length)}` : '0'} / {filteredRows.length}
            </span>
            <nav className="review-db-pagination" aria-label="Review queue pagination">
              <button type="button" disabled={safePageIndex === 0} onClick={() => goToPage(safePageIndex - 1)} aria-label="Previous review page">‹</button>
              <span><strong>{safePageIndex + 1}</strong> / {pageCount}</span>
              <button type="button" disabled={safePageIndex >= pageCount - 1} onClick={() => goToPage(safePageIndex + 1)} aria-label="Next review page">›</button>
            </nav>
          </div>
          <div className="review-db-table-head">
            <span>Athlete / Source</span>
            <span>Status / 状态</span>
            <span>Review Mode / 审核方式</span>
            <span>Conclusion / 结论</span>
            <span>Focus / 关注点</span>
            <span>Date / 日期</span>
          </div>
          <div
            className="review-db-table-body"
            ref={tableBodyRef}
          >
            {pagedRows.length ? pagedRows.map(row => (
              <button
                key={row.id}
                type="button"
                className={`review-db-row${selectedRow?.id === row.id ? ' active' : ''}`}
                onClick={() => setSelectedId(row.id)}
                onDoubleClick={() => { setSelectedId(row.id); setWorkspaceMode('evidence'); }}
              >
                <span className="review-db-athlete">
                  <strong>{reviewDbText(row.athleteName, 'Unknown athlete')}</strong>
                  <small>{sourceChip(row)} {reviewDbText(row.sourceLabel, row.sessionId || row.assessmentId || '/')}</small>
                  {consumptionLabel(row) && (
                    <small
                      className="review-db-consumed"
                      style={{ marginTop: '3px', color: trainingLinkCount(row) > 0 ? 'var(--accent)' : 'var(--muted-2)', fontVariantNumeric: 'tabular-nums' }}
                    >{consumptionLabel(row)}</small>
                  )}
                </span>
                <span><i className={`review-db-status status-${row.lifecycleState || 'pending'}`}>{reviewDbStatusLabel(row.lifecycleState)}</i></span>
                <span className="review-db-mode">{reviewDbModeLabel(row)}</span>
                <span>{reviewDbText(row.conclusion)}</span>
                <span>{reviewDbText(row.trainingFocus)}</span>
                <span>{reviewDbDate(row.sourceDate || row.lastEdited)}</span>
              </button>
            )) : (
              <div className="review-db-empty">No sources match this view or search. 当前筛选或搜索无匹配来源。</div>
            )}
          </div>
        </section>
        ) : workspaceMode === 'evidence' ? (
          selectedRow ? (
            <ReviewDbEvidencePanel
              row={selectedRow}
              forceTraceRead={forceTraceRead}
              forceTraceReadModel={forceTraceReadModel}
              reviewKnowledgeReadModel={reviewKnowledgeReadModel}
              trainingLinkCount={trainingLinkCount(selectedRow)}
            />
          ) : <div className="review-db-empty">Select a source first / 请先选择来源</div>
        ) : (
          <section className="review-db-lifecycle" aria-label="Review lifecycle">
            <div className="review-db-panel-title">Lifecycle / 审核生命周期</div>
            <div className="review-db-lifecycle-rail">
              <div className="done"><strong>1</strong><span>Source captured / 来源已采集</span></div>
              <div className={selectedRow?.sourceAvailable ? 'done' : ''}><strong>2</strong><span>Evidence available / 证据可读取</span></div>
              <div className={selectedRow?.lifecycleState === 'confirmed' ? 'done' : 'active'}><strong>3</strong><span>Human review / 人工审核</span></div>
              <div className={trainingLinkCount(selectedRow) > 0 ? 'done' : ''}><strong>4</strong><span>Training reference / 训练引用</span></div>
              <div className={selectedRow?.canOpenReport ? 'done' : ''}><strong>5</strong><span>Report use / 报告使用</span></div>
            </div>
            <div className="review-db-note">
              Lifecycle is a read-back of persisted state. It does not infer completion or create recommendations.
              生命周期仅回读已持久化状态，不推断完成度、不生成建议。
            </div>
          </section>
        )}

        <aside className="review-db-detail" aria-label="Selected review source detail">
          {selectedRow ? (
            <React.Fragment>
              <div className="review-db-panel-title">Selected Source / 当前来源</div>
              <div className="review-db-detail-card">
                <div className="review-db-detail-top">
                  {sourceChip(selectedRow)}
                  <i className={`review-db-status status-${selectedRow.lifecycleState || 'pending'}`}>{reviewDbStatusLabel(selectedRow.lifecycleState)}</i>
                </div>
                <h2>{reviewDbText(selectedRow.athleteName, 'Unknown athlete')}</h2>
                <p>{reviewDbText(selectedRow.sourceLabel, 'Source')}</p>
              </div>
              <dl className="review-db-facts">
                <div><dt>Source date / 来源日期</dt><dd>{reviewDbDate(selectedRow.sourceDate)}</dd></div>
                <div><dt>Last edited / 最近编辑</dt><dd>{reviewDbDate(selectedRow.lastEdited)}</dd></div>
                <div><dt>Review mode / 审核方式</dt><dd>{reviewDbModeLabel(selectedRow)}</dd></div>
                <div><dt>Conclusion / 结论</dt><dd>{reviewDbText(selectedRow.conclusion)}</dd></div>
                <div><dt>Reviewer training focus / 训练关注点</dt><dd>{reviewDbText(selectedRow.trainingFocus)}</dd></div>
                <div><dt>Notes / 备注</dt><dd>{reviewDbText(selectedRow.noteExcerpt)}</dd></div>
                {selectedRow.lifecycleState === 'confirmed' && (
                  <div>
                    <dt>Downstream use / 去向</dt>
                    <dd style={{ color: trainingLinkCount(selectedRow) > 0 ? 'var(--accent)' : 'var(--muted-2)', fontVariantNumeric: 'tabular-nums' }}>{consumptionLabel(selectedRow)}</dd>
                  </div>
                )}
                <div><dt>Reviewer / 审核人</dt><dd>{reviewDbText(selectedRow.reviewedBy?.displayName)}</dd></div>
                <div><dt>Review revision / 审核修订</dt><dd>{selectedRow.reviewRevision || '/'}</dd></div>
                <div><dt>Reviewed at / 确认时间</dt><dd>{reviewDbDate(selectedRow.reviewedAt)}</dd></div>
              </dl>
              {selectedRow.canEditReview && (
                <div className="review-db-editor" aria-label="Inline review form">
                  <div className="review-db-panel-title">Review Form / 审核表单</div>
                  <label>
                    <span>Sports Scientist Conclusion / 运动科学结论</span>
                    <textarea id="review-db-inline-conclusion" value={draft.conclusion} onChange={e => updateDraft('conclusion', e.target.value)} placeholder="Write the human-reviewed conclusion..." />
                  </label>
                  {selectedRow.editorKind === 'cmj' ? (
                    <label>
                      <span>Training Implication / 训练意义</span>
                      <select value={draft.trainingFocus || 'monitor-only-retest'} onChange={e => updateDraft('trainingFocus', e.target.value)}>
                        {REVIEW_DB_CMJ_TRAINING_FOCUS.map(([value, label]) => <option key={value} value={value}>{label}</option>)}
                      </select>
                    </label>
                  ) : (
                    <label>
                      <span>Reviewer Training Focus / 训练关注点</span>
                      <input value={draft.trainingFocus} onChange={e => updateDraft('trainingFocus', e.target.value)} placeholder="Optional human-selected focus" />
                    </label>
                  )}
                  <label>
                    <span>Notes / 备注</span>
                    <textarea value={draft.note} onChange={e => updateDraft('note', e.target.value)} placeholder="Context, caveats, or communication notes..." />
                  </label>
                  <div className="review-db-editor-note">Human review only / 仅记录人工审核：does not evaluate metrics, create recommendations, or change source data. 不评估指标、不生成建议、不改变来源数据。</div>
                  {!selectedRow.reviewEligible && selectedRow.sourceType === 'field' && (
                    <div className="review-db-editor-note">{selectedRow.reviewEligibilityReason}</div>
                  )}
                  <div className="review-db-save-row">
                    <button type="button" className="btn ghost" onClick={() => saveInlineReview('draft')}>Save Draft / 保存草稿</button>
                    <button type="button" className="btn" onClick={() => saveInlineReview('reviewed')}>Mark Reviewed / 标记已审核</button>
                  </div>
                  <div className="review-db-save-status" aria-live="polite">
                    <span>{saveState || 'No unsaved changes / 无未保存修改'}</span>
                    {lastSavedAt && <span>Last saved / 最近保存: {lastSavedAt}</span>}
                  </div>
                </div>
              )}
              {selectedRow.reviewHistory?.length > 0 && (
                <div className="review-db-history" aria-label="Review revision history">
                  <div className="review-db-panel-title">Revision History / 修订历史</div>
                  {selectedRow.reviewHistory.slice().reverse().map(item => (
                    <div key={item.revision} className="review-db-note">
                      <strong>#{item.revision} · {item.action === 'confirmed' ? 'Confirmed / 已确认' : 'Draft / 草稿'}</strong>
                      <div>{reviewDbText(item.actor?.displayName)} · {reviewDbDate(item.savedAt)}</div>
                    </div>
                  ))}
                </div>
              )}
              <div className="review-db-actions">
                <button type="button" className="btn" disabled={!selectedRow.sourceAvailable} onClick={() => setWorkspaceMode('evidence')}>Review evidence / 同屏审核证据</button>
                {selectedRow.canEditReview ? (
                  <button type="button" className="btn" onClick={() => document.getElementById('review-db-inline-conclusion')?.focus()}>Inline Review / 页内审核</button>
                ) : (
                  <button type="button" className="btn" disabled={!selectedRow.canOpenReview} onClick={() => onOpenReview?.(selectedRow)}>Open Review / 打开审核</button>
                )}
                <button type="button" className="btn ghost" disabled={!selectedRow.canOpenReport} onClick={() => onOpenReport?.(selectedRow)}>Individual Report / 个人报告</button>
                <button type="button" className="btn ghost" disabled={!selectedRow.canOpenSource || !selectedRow.sourceAvailable} onClick={() => onOpenSource?.(selectedRow)}>Open Source / 打开来源</button>
                {selectedRow.lifecycleState === 'confirmed' && (
                  <button type="button" className="btn ghost" disabled={!onLinkToTraining} onClick={() => onLinkToTraining?.(selectedRow)}>Link to training / 链接到训练</button>
                )}
              </div>
              {!selectedRow.canOpenReview && !selectedRow.canEditReview && (
                <div className="review-db-note">This source is connected for browsing and source re-entry. Editable review form is not available for this source. 此来源可浏览并回到来源页；当前来源暂无可编辑审核表单。</div>
              )}
            </React.Fragment>
          ) : (
            <div className="review-db-empty">Select a source to inspect. 请选择一个来源。</div>
          )}
        </aside>
      </section>

      <button type="button" className="review-db-back-top" onClick={scrollReviewToTop}>↑ 回到顶部</button>
    </main>
  );
}
