/**
 * Dashboard — 评估历史列表与按角色的待办工作台。
 *
 * 列表与待办均走**服务端 SQL 分页**(page/pageSize/status/q),统计卡走 summary 聚合接口,
 * 不再把全量数据载入前端,支持大数据量。
 */

import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, Card, Table } from '../design-system/index.js';
import type { TableColumn } from '../design-system/index.js';
import { Icon } from '../design-system/index.js';
import {
  colorVar,
  FONT_FAMILY,
  RADIUS,
  space,
  typographyStyle,
} from '../design-system/components/styles.js';
import { fetchAssessmentsPage, fetchSummary, archiveAssessment, applyCalibration, fetchCalibration, listDrafts, deleteDraftApi, fetchAssignments, API_BASE } from '../api/client.js';

// 校准状态类型（目标净利率基准）。
type CalibrationState = { currentBase: number; suggestedBase: number; uncalibratedBase: number; calibrated: boolean; bias: string | null; deviationPct: number | null };
import type { AssessmentListItem, WorkflowStatus, DraftItem, AssignmentRecord } from '../api/client.js';
import { useAuthStore } from '../stores/authStore.js';
import { GuideBanner } from '../app/Guidance.js';
import { RiskBadge } from '../charts/index.js';

const STATUS_LABEL: Record<WorkflowStatus, string> = {
  draft: '草稿待申报',
  pending_risk_review: '待风控审核',
  risk_reviewed: '风控已审核',
  pending_management_approval: '待管理层审批',
  approved: '已通过',
  rejected: '已驳回',
  abandoned: '已放弃',
};

const STATUS_STYLE: Record<WorkflowStatus, { bg: string; fg: string }> = {
  draft: { bg: 'rgba(100, 116, 139, 0.12)', fg: '#64748B' },
  pending_risk_review: { bg: 'rgba(180, 83, 9, 0.12)', fg: '#B45309' },
  risk_reviewed: { bg: 'rgba(37, 99, 235, 0.12)', fg: '#2563EB' },
  pending_management_approval: { bg: 'rgba(124, 58, 237, 0.12)', fg: '#7C3AED' },
  approved: { bg: 'rgba(16, 128, 61, 0.12)', fg: '#15803D' },
  rejected: { bg: 'rgba(190, 18, 60, 0.12)', fg: '#BE123C' },
  abandoned: { bg: 'rgba(100, 116, 139, 0.14)', fg: '#475569' },
};

type StatusFilter = 'all' | WorkflowStatus;

const STATUS_FILTER_OPTIONS: ReadonlyArray<{ value: StatusFilter; label: string }> = [
  { value: 'all', label: '全部状态' },
  { value: 'draft', label: STATUS_LABEL.draft },
  { value: 'pending_risk_review', label: STATUS_LABEL.pending_risk_review },
  { value: 'risk_reviewed', label: STATUS_LABEL.risk_reviewed },
  { value: 'pending_management_approval', label: STATUS_LABEL.pending_management_approval },
  { value: 'approved', label: STATUS_LABEL.approved },
  { value: 'rejected', label: STATUS_LABEL.rejected },
  { value: 'abandoned', label: STATUS_LABEL.abandoned },
];

/** 角色 → 待办状态。 */
const TODO_STATUS: Record<string, WorkflowStatus> = {
  '商务/销售': 'rejected',
  '风控': 'pending_risk_review',
  '管理层': 'risk_reviewed',
};

/** 承接建议等级配色。 */
const REC_BG: Record<string, string> = {
  accept: 'rgba(16,128,61,0.12)',
  conditional: 'rgba(194,65,12,0.12)',
  caution: 'rgba(194,65,12,0.12)',
  reject: 'rgba(190,18,60,0.12)',
};
const REC_FG: Record<string, string> = {
  accept: '#15803D',
  conditional: '#C2410C',
  caution: '#C2410C',
  reject: '#BE123C',
};

function formatDateTime(value: string): string {
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return value;
  return date.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
}

export function Dashboard(): JSX.Element {
  const navigate = useNavigate();
  const { user } = useAuthStore();
  const role = user?.role ?? '商务/销售';

  const [error, setError] = useState<string | null>(null);
  const [notice, setNotice] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  // 历史列表(服务端分页)
  const [items, setItems] = useState<AssessmentListItem[]>([]);
  const [total, setTotal] = useState(0);
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(10);
  const [statusFilter, setStatusFilter] = useState<StatusFilter>('all');
  const [archivedView, setArchivedView] = useState<'active' | 'archived'>('active');
  const [searchInput, setSearchInput] = useState('');
  const [search, setSearch] = useState('');

  // 待办列表
  const [todoItems, setTodoItems] = useState<AssessmentListItem[]>([]);
  // 审批人指派（assessmentId → 记录）；待办软过滤用。
  const [assignments, setAssignments] = useState<Record<string, AssignmentRecord>>({});
  const [onlyMine, setOnlyMine] = useState(true);
  // 草稿箱（销售：未运行/未提交的向导进度，服务端持久化）
  const [drafts, setDrafts] = useState<DraftItem[]>([]);
  // 统计
  const [summary, setSummary] = useState<{ total: number; byStatus: Record<string, number>; archived?: number }>({ total: 0, byStatus: {} });
  // 告警
  const [expiring, setExpiring] = useState<Array<{ id: string; project: string }>>([]);
  const [overdue, setOverdue] = useState<Array<{ id: string; project: string; overdueHours: number }>>([]);
  const [rejectStats, setRejectStats] = useState<Array<{ reasonType: string; count: number }>>([]);
  const [accuracy, setAccuracy] = useState<{ count: number; avgPredictedPct: number | null; avgActualPct: number | null; avgDeviationPct: number | null; bias: string | null }>({ count: 0, avgPredictedPct: null, avgActualPct: null, avgDeviationPct: null, bias: null });
  // 校准状态（目标净利率基准）。
  const [calibration, setCalibration] = useState<CalibrationState | null>(null);

  // 搜索防抖
  useEffect(() => {
    const t = setTimeout(() => {
      setSearch(searchInput);
      setPage(1);
    }, 350);
    return () => clearTimeout(t);
  }, [searchInput]);

  // 销售仅可见本人发起的评估（行级数据隔离）；风控/管理层可见全部。
  const scopeAssessorId = role === '商务/销售' ? user?.id : undefined;

  const loadList = useCallback(() => {
    setLoading(true);
    fetchAssessmentsPage({ page, pageSize, status: statusFilter, q: search, archived: archivedView, ...(scopeAssessorId !== undefined ? { assessorId: scopeAssessorId } : {}) })
      .then((res) => {
        setItems(res.items);
        setTotal(res.total);
        setError(null);
      })
      .catch((err: unknown) => setError(err instanceof Error ? err.message : '加载失败'))
      .finally(() => setLoading(false));
  }, [page, pageSize, statusFilter, search, archivedView, scopeAssessorId]);

  useEffect(() => {
    loadList();
  }, [loadList]);

  const loadAux = useCallback(() => {
    fetchSummary(scopeAssessorId).then(setSummary).catch(() => undefined);
    const todoStatus = TODO_STATUS[role];
    if (todoStatus !== undefined) {
      fetchAssessmentsPage({ page: 1, pageSize: 50, status: todoStatus, ...(scopeAssessorId !== undefined ? { assessorId: scopeAssessorId } : {}) })
        .then((res) => setTodoItems(res.items))
        .catch(() => setTodoItems([]));
    } else {
      setTodoItems([]);
    }
    // 组合分析（准确度/驳回Top/到期/超时）面向风控与管理层；销售视图不展示（避免跨人数据）。
    if (role === '商务/销售') {
      setExpiring([]); setOverdue([]); setRejectStats([]); setAccuracy({ count: 0, avgPredictedPct: null, avgActualPct: null, avgDeviationPct: null, bias: null }); setCalibration(null);
    } else {
      fetch(`${API_BASE}/api/assessments/expiring`).then((r) => r.json()).then(setExpiring).catch(() => setExpiring([]));
      fetch(`${API_BASE}/api/assessments/overdue`).then((r) => r.json()).then(setOverdue).catch(() => setOverdue([]));
      fetch(`${API_BASE}/api/reject-reason-stats`).then((r) => r.json()).then(setRejectStats).catch(() => setRejectStats([]));
      fetch(`${API_BASE}/api/accuracy`).then((r) => r.json()).then(setAccuracy).catch(() => undefined);
      fetchCalibration().then(setCalibration).catch(() => setCalibration(null));
    }
    // 草稿箱（仅销售展示）：列出当前用户的向导草稿。
    if (role === '商务/销售') {
      listDrafts(user?.id ?? undefined).then(setDrafts).catch(() => setDrafts([]));
    } else {
      setDrafts([]);
    }
    // 审批人指派：风控/管理层用于待办过滤；销售用于查看自己项目的审批进度。
    if (role === '风控' || role === '管理层' || role === '商务/销售') {
      fetchAssignments().then(setAssignments).catch(() => setAssignments({}));
    } else {
      setAssignments({});
    }
  }, [role, user?.username, user?.id, scopeAssessorId]);

  /** 删除一条草稿并刷新草稿箱。 */
  const removeDraft = useCallback(async (id: string): Promise<void> => {
    try { await deleteDraftApi(id); } catch { /* ignore */ }
    setDrafts((ds) => ds.filter((d) => d.id !== id));
  }, []);

  useEffect(() => {
    loadAux();
  }, [loadAux]);

  const handleArchive = useCallback(
    (id: string, archived: boolean) => {
      archiveAssessment(id, archived, user?.username)
        .then(() => {
          loadList();
          loadAux();
        })
        .catch((err: unknown) => setError(err instanceof Error ? err.message : '操作失败'));
    },
    [loadList, loadAux, user],
  );

  const columns: ReadonlyArray<TableColumn<AssessmentListItem>> = [
    {
      key: 'project',
      header: '项目描述',
      render: (r) => {
        // 提取【项目】后的名称,去掉【客户】部分
        const nameMatch = r.projectDescription.match(/【项目】([^｜|\n]+)/);
        const name = nameMatch !== null && nameMatch[1] !== undefined ? nameMatch[1].trim() : r.projectDescription.slice(0, 20);
        const display = name.length > 18 ? `${name.slice(0, 18)}…` : name;
        return (
          <div style={{ display: 'flex', flexDirection: 'column', gap: '2px', maxWidth: 200 }}>
            <span style={{ fontWeight: 700, color: colorVar('color.text.primary'), whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
              {display}
            </span>
            <span style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary'), whiteSpace: 'nowrap' }}>发起人：{r.assessorName ?? r.assessorId}</span>
          </div>
        );
      },
    },
    { key: 'type', header: '业务类型', render: (r) => <span style={{ display: 'inline-block', minWidth: 56, wordBreak: 'keep-all', whiteSpace: 'normal' }}>{r.businessType}</span> },
    { key: 'industry', header: '行业', render: (r) => <span style={{ display: 'inline-block', minWidth: 48, whiteSpace: 'nowrap' }}>{r.industry}</span> },
    { key: 'score', header: '风险分', align: 'right', render: (r) => (r.riskScore !== undefined ? r.riskScore.toFixed(1) : '-') },
    {
      key: 'grade',
      header: '风险分级',
      render: (r) => (r.riskGrade !== undefined ? <RiskBadge grade={r.riskGrade as '低' | '中' | '高' | '极高'} /> : '-'),
    },
    {
      key: 'status',
      header: '状态',
      render: (r) => (
        <span
          style={{
            display: 'inline-block',
            padding: '2px 10px',
            borderRadius: '999px',
            backgroundColor: STATUS_STYLE[r.status].bg,
            color: STATUS_STYLE[r.status].fg,
            ...typographyStyle('caption'),
            fontWeight: 600,
            whiteSpace: 'nowrap',
          }}
        >
          {STATUS_LABEL[r.status]}
        </span>
      ),
    },
    { key: 'acceptability', header: '可接受性', render: (r) => r.acceptability ?? '-' },
    {
      key: 'recommendation',
      header: '承接建议',
      render: (r) =>
        r.recommendation ? (
          <span
            style={{
              display: 'inline-block',
              padding: '2px 10px',
              borderRadius: '999px',
              ...typographyStyle('caption'),
              fontWeight: 600,
              whiteSpace: 'nowrap',
              backgroundColor: REC_BG[r.recommendation.level] ?? 'transparent',
              color: REC_FG[r.recommendation.level] ?? colorVar('color.text.secondary'),
            }}
          >
            {r.recommendation.title}
          </span>
        ) : (
          <span style={{ color: colorVar('color.text.secondary') }}>-</span>
        ),
    },
    {
      key: 'latest',
      header: '最近处理',
      render: (r) => {
        const latest = r.auditLog.at(-1);
        return latest === undefined ? (
          <span style={{ color: colorVar('color.text.secondary'), whiteSpace: 'nowrap' }}>待处理</span>
        ) : (
          <div style={{ whiteSpace: 'nowrap', maxWidth: 140, overflow: 'hidden', textOverflow: 'ellipsis' }}>
            <span style={{ ...typographyStyle('caption') }}>{latest.action.length > 10 ? latest.action.slice(0, 10) + '…' : latest.action}</span>
            <div style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary') }}>
              {latest.role} · {formatDateTime(latest.timestamp)}
            </div>
          </div>
        );
      },
    },
    { key: 'created', header: '创建时间', render: (r) => formatDateTime(r.createdAt) },
    {
      key: 'action',
      header: '操作',
      render: (r) => {
        const canArchive = r.status === 'approved' || r.status === 'abandoned';
        return (
          <div style={{ display: 'flex', gap: `${space(1)}px`, whiteSpace: 'nowrap' }}>
            <Button size="sm" variant="ghost" onClick={() => navigate(`/assessments/${r.id}`)}>
              查看
            </Button>
            {archivedView === 'archived' ? (
              <Button size="sm" variant="ghost" onClick={() => handleArchive(r.id, false)}>
                取消归档
              </Button>
            ) : (
              <span title={canArchive ? '归档' : '仅「最终通过」或「已放弃」可归档'} style={{ display: 'inline-flex' }}>
                <Button
                  size="sm"
                  variant="ghost"
                  disabled={!canArchive}
                  onClick={() => handleArchive(r.id, true)}
                >
                  归档
                </Button>
              </span>
            )}
          </div>
        );
      },
    },
  ];

  const todoCount = summary.byStatus[TODO_STATUS[role] ?? ''] ?? 0;
  const isSales = role === '商务/销售';

  // 销售首页：为自己的项目显示审批进度（阶段 + 当前审批人）。
  const approvalProgressCol: TableColumn<AssessmentListItem> = {
    key: 'progress',
    header: '审批进度',
    render: (r) => {
      const a = assignments[r.id];
      const stage = (() => {
        switch (r.status) {
          case 'draft': return { text: '待申报（草稿）', sub: '尚未报送，点「查看」进入后申报', color: '#64748B' };
          case 'pending_risk_review': return { text: '风控审核中', sub: a?.riskReviewerName ? `审批人：${a.riskReviewerName}` : '待分配风控', color: '#B45309' };
          case 'risk_reviewed':
          case 'pending_management_approval': return { text: '管理层审批中', sub: a?.managerName ? `审批人：${a.managerName}` : '待分配管理层', color: '#4F46E5' };
          case 'approved': return { text: '已通过 ✓', sub: '审批完成', color: '#15803D' };
          case 'rejected': return { text: '已驳回', sub: '请修改后重新提交', color: '#BE123C' };
          case 'abandoned': return { text: '已放弃', sub: '流程终止', color: '#475569' };
          default: return { text: r.status, sub: '', color: colorVar('color.text.secondary') };
        }
      })();
      return (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 2, minWidth: 120 }}>
          <span style={{ ...typographyStyle('caption'), fontWeight: 700, color: stage.color }}>{stage.text}</span>
          {stage.sub && <span style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary'), fontSize: '11px' }}>{stage.sub}</span>}
        </div>
      );
    },
  };
  // 销售视图：去掉与「审批进度」重复的列（承接建议/最近处理），插入审批进度，保留操作列。
  // 风控/管理层视图：去掉冗余的「可接受性」（与承接建议重复），精简列宽避免溢出。
  const historyColumns = isSales
    ? (() => {
        const base = columns.filter((col) => col.key !== 'recommendation' && col.key !== 'latest');
        return [...base.slice(0, -1), approvalProgressCol, base[base.length - 1]!];
      })()
    : columns.filter((col) => col.key !== 'acceptability');
  const summaryItems = [
    { label: isSales ? '我的评估' : '全部评估', value: summary.total, tone: colorVar('color.brand.primary') },
    { label: isSales ? '被驳回(待处理)' : '我的待办', value: todoCount, tone: colorVar('color.risk.high') },
    { label: isSales ? '我的待风控' : '待风控', value: summary.byStatus.pending_risk_review ?? 0, tone: colorVar('color.risk.medium') },
    { label: isSales ? '我的已通过' : '已通过', value: summary.byStatus.approved ?? 0, tone: colorVar('color.risk.low') },
  ];

  const pageTitle = role === '风控' ? '待办审核' : role === '管理层' ? '待办审批' : '评估历史';
  const roleDescription =
    role === '风控'
      ? '聚焦待复核项目，补充风险判断并留下处理痕迹。'
      : role === '管理层'
        ? '查看全部历史与高风险事项，完成最终审批决策。'
        : '发起新评估，跟踪退回事项，并查看全量历史记录。';

  const totalPages = Math.max(1, Math.ceil(total / pageSize));

  return (
    <div style={{ fontFamily: FONT_FAMILY }}>
      {role === '商务/销售' && (
        <GuideBanner id="role-sales" tone="brand">
          <strong>你是 商务/销售。</strong>主线三步：① 点右上角 <strong>+ 新建评估</strong> 录入项目 → ② 在详情页点 <strong>申报</strong> 报送风控 → ③ 被驳回的项目可<strong>编辑后重报</strong>。下方为你的评估历史与被退回事项。
        </GuideBanner>
      )}
      {role === '风控' && (
        <GuideBanner id="role-risk" tone="brand">
          <strong>你是 风控。</strong>下方 <strong>待处理</strong> 是需要你审核的项目；打开后审阅风险与红线，给出<strong>审核通过 / 驳回</strong>结论。红线"需人工核实"项可在详情页人工裁定。
        </GuideBanner>
      )}
      {role === '管理层' && (
        <GuideBanner id="role-mgmt" tone="brand">
          <strong>你是 管理层。</strong>下方 <strong>待处理</strong> 待你终审（通过/驳回/放弃）；顶部导航可维护 <strong>费率 / 红线 / 客户档案</strong>。看板含到期、超时与预测准确度提醒。
        </GuideBanner>
      )}
      <div
        style={{
          display: 'flex',
          flexWrap: 'wrap',
          justifyContent: 'space-between',
          alignItems: 'center',
          gap: `${space(3)}px`,
          marginBottom: `${space(5)}px`,
          padding: `${space(5)}px`,
          background: `linear-gradient(135deg, ${colorVar('color.bg.elevated')} 0%, ${colorVar('color.bg.surface')} 100%)`,
          border: `1px solid ${colorVar('color.border.default')}`,
          borderRadius: `${RADIUS.lg}px`,
        }}
      >
        <div style={{ display: 'flex', flexDirection: 'column', gap: `${space(2)}px` }}>
          <span style={{ ...typographyStyle('caption'), color: colorVar('color.brand.primary'), fontWeight: 700 }}>{role} 工作台</span>
          <h1 style={{ ...typographyStyle('heading'), margin: 0, color: colorVar('color.text.primary') }}>{pageTitle}</h1>
          <p style={{ margin: 0, color: colorVar('color.text.secondary'), maxWidth: 620 }}>{roleDescription}</p>
        </div>
        {role === '商务/销售' && (
          <Button onClick={() => navigate('/new')} size="lg"><span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}><Icon name="plus" size={16} color="currentColor" /> 新建评估</span></Button>
        )}
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: `${space(3)}px`, marginBottom: `${space(4)}px` }}>
        {summaryItems.map((item) => (
          <div
            key={item.label}
            style={{
              padding: `${space(4)}px`,
              backgroundColor: colorVar('color.bg.elevated'),
              border: `1px solid ${colorVar('color.border.default')}`,
              borderRadius: `${RADIUS.lg}px`,
              display: 'flex',
              flexDirection: 'column',
              gap: `${space(2)}px`,
            }}
          >
            <span style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary') }}>{item.label}</span>
            <span style={{ ...typographyStyle('heading'), color: item.tone, fontWeight: 800 }}>{item.value}</span>
          </div>
        ))}
      </div>

      {/* 告警卡片 */}
      {(expiring.length > 0 || overdue.length > 0 || rejectStats.length > 0 || accuracy.count > 0) && (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: `${space(3)}px`, marginBottom: `${space(4)}px` }}>
          {accuracy.count > 0 && accuracy.bias !== null && (() => {
            const consistent = accuracy.bias === '基本一致';
            const accent = consistent ? '#15803D' : '#B45309';
            const dev = accuracy.avgDeviationPct ?? 0;
            return (
              <div style={{ padding: `${space(4)}px`, backgroundColor: colorVar('color.bg.elevated'), border: `1px solid ${colorVar('color.border.default')}`, borderRadius: `${RADIUS.lg}px`, boxShadow: '0 1px 2px rgba(16,24,40,0.04)' }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: `${space(2)}px`, marginBottom: `${space(3)}px` }}>
                  <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: 8, backgroundColor: `${accent}1A`, color: accent }}>
                    <Icon name="trending-up" size={18} />
                  </span>
                  <div style={{ display: 'flex', flexDirection: 'column' }}>
                    <span style={{ ...typographyStyle('body'), fontWeight: 700, color: colorVar('color.text.primary') }}>预测准确度</span>
                    <span style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary') }}>基于 {accuracy.count} 个已回填项目</span>
                  </div>
                </div>
                <div style={{ display: 'flex', gap: `${space(3)}px`, marginBottom: `${space(3)}px` }}>
                  <div style={{ flex: 1, padding: `${space(2)}px ${space(3)}px`, backgroundColor: colorVar('color.bg.surface'), borderRadius: `${RADIUS.md}px` }}>
                    <div style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary') }}>预测净利率</div>
                    <div style={{ ...typographyStyle('body'), fontWeight: 800, color: colorVar('color.text.primary') }}>{accuracy.avgPredictedPct}%</div>
                  </div>
                  <div style={{ flex: 1, padding: `${space(2)}px ${space(3)}px`, backgroundColor: colorVar('color.bg.surface'), borderRadius: `${RADIUS.md}px` }}>
                    <div style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary') }}>实际净利率</div>
                    <div style={{ ...typographyStyle('body'), fontWeight: 800, color: colorVar('color.text.primary') }}>{accuracy.avgActualPct}%</div>
                  </div>
                </div>
                <div style={{ display: 'flex', alignItems: 'center', gap: `${space(2)}px` }}>
                  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: `2px ${space(2)}px`, borderRadius: '999px', backgroundColor: `${accent}1A`, color: accent, ...typographyStyle('caption'), fontWeight: 700 }}>
                    <Icon name={consistent ? 'check-circle' : 'alert'} size={13} />
                    系统性偏差 {dev > 0 ? '+' : ''}{accuracy.avgDeviationPct} pp · {accuracy.bias}
                  </span>
                </div>
                {!consistent && (
                  <div style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary'), marginTop: `${space(2)}px`, lineHeight: 1.6 }}>
                    {accuracy.bias === '预测偏乐观' ? '建议上调目标净利率基准以补偿系统性乐观偏差。' : '预测偏保守，可适当下调目标净利率基准。'}
                    {calibration !== null && (
                      <span> 当前基准 <strong>{(calibration.currentBase * 100).toFixed(1)}%</strong>
                        {!calibration.calibrated && <>，建议调整为 <strong style={{ color: accent }}>{(calibration.suggestedBase * 100).toFixed(1)}%</strong></>}。</span>
                    )}
                  </div>
                )}
                {role === '管理层' && !consistent && calibration !== null && (
                  calibration.calibrated ? (
                    <div style={{ marginTop: `${space(3)}px`, display: 'inline-flex', alignItems: 'center', gap: 6, padding: `${space(1)}px ${space(3)}px`, borderRadius: `${RADIUS.md}px`, backgroundColor: 'rgba(16,128,61,0.10)', color: '#15803D', ...typographyStyle('caption'), fontWeight: 600 }}>
                      <Icon name="check-circle" size={14} /> 已按当前偏差校准：目标净利率基准 {(calibration.currentBase * 100).toFixed(1)}%
                    </div>
                  ) : (
                    <button
                      type="button"
                      onClick={() => {
                        applyCalibration()
                          .then((r) => { setError(null); setNotice(`已应用校准：目标净利率基准 ${(r.previousBase * 100).toFixed(1)}% → ${(r.appliedBase * 100).toFixed(1)}%（影响后续承接建议阈值）`); loadAux(); })
                          .catch((e: unknown) => setError(e instanceof Error ? e.message : '校准失败'));
                      }}
                      style={{ marginTop: `${space(3)}px`, display: 'inline-flex', alignItems: 'center', gap: 6, padding: `${space(1)}px ${space(3)}px`, borderRadius: `${RADIUS.md}px`, border: `1px solid ${accent}`, background: 'transparent', color: accent, cursor: 'pointer', fontFamily: FONT_FAMILY, ...typographyStyle('caption'), fontWeight: 600 }}
                    >
                      <Icon name="settings" size={14} /> 应用校准建议（{(calibration.currentBase * 100).toFixed(1)}% → {(calibration.suggestedBase * 100).toFixed(1)}%）
                    </button>
                  )
                )}
                {role === '管理层' && !consistent && (
                  <div style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary'), marginTop: `${space(2)}px`, fontSize: '11px', lineHeight: 1.6 }}>
                    注：上方偏差来自已回填项目的历史预测与实际对比，属既成事实，不会因校准而改变；校准仅调整后续评估的目标净利率基准（影响承接建议阈值）。
                  </div>
                )}
              </div>
            );
          })()}
          {expiring.length > 0 && (
            <div style={{ padding: `${space(4)}px`, backgroundColor: colorVar('color.bg.elevated'), border: `1px solid ${colorVar('color.border.default')}`, borderRadius: `${RADIUS.lg}px`, boxShadow: '0 1px 2px rgba(16,24,40,0.04)' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: `${space(2)}px`, marginBottom: `${space(2)}px` }}>
                <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: 8, backgroundColor: 'rgba(180,83,9,0.1)', color: '#B45309' }}>
                  <Icon name="clock" size={18} />
                </span>
                <span style={{ ...typographyStyle('body'), fontWeight: 700, color: colorVar('color.text.primary') }}>即将到期（{expiring.length}）</span>
              </div>
              {expiring.slice(0, 3).map((e) => (
                <div key={e.id} style={{ ...typographyStyle('caption'), color: colorVar('color.text.primary'), padding: '3px 0' }}>{e.project.slice(0, 25)}</div>
              ))}
            </div>
          )}
          {overdue.length > 0 && (
            <div style={{ padding: `${space(4)}px`, backgroundColor: colorVar('color.bg.elevated'), border: `1px solid ${colorVar('color.border.default')}`, borderRadius: `${RADIUS.lg}px`, boxShadow: '0 1px 2px rgba(16,24,40,0.04)' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: `${space(2)}px`, marginBottom: `${space(2)}px` }}>
                <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: 8, backgroundColor: 'rgba(190,18,60,0.1)', color: '#BE123C' }}>
                  <Icon name="alert" size={18} />
                </span>
                <span style={{ ...typographyStyle('body'), fontWeight: 700, color: colorVar('color.text.primary') }}>审批超时（{overdue.length}）</span>
              </div>
              {overdue.slice(0, 3).map((e) => (
                <div key={e.id} style={{ ...typographyStyle('caption'), color: colorVar('color.text.primary'), padding: '3px 0' }}>{e.project.slice(0, 20)} · 超时 {e.overdueHours}h</div>
              ))}
            </div>
          )}
          {rejectStats.length > 0 && (() => {
            const maxCount = Math.max(...rejectStats.map((s) => s.count), 1);
            return (
              <div style={{ padding: `${space(4)}px`, backgroundColor: colorVar('color.bg.elevated'), border: `1px solid ${colorVar('color.border.default')}`, borderRadius: `${RADIUS.lg}px`, boxShadow: '0 1px 2px rgba(16,24,40,0.04)' }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: `${space(2)}px`, marginBottom: `${space(3)}px` }}>
                  <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 32, height: 32, borderRadius: 8, backgroundColor: 'rgba(190,18,60,0.1)', color: '#BE123C' }}>
                    <Icon name="ban" size={18} />
                  </span>
                  <span style={{ ...typographyStyle('body'), fontWeight: 700, color: colorVar('color.text.primary') }}>驳回 Top 原因</span>
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: `${space(2)}px` }}>
                  {rejectStats.slice(0, 4).map((s) => (
                    <div key={s.reasonType} style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                      <div style={{ display: 'flex', justifyContent: 'space-between', ...typographyStyle('caption'), color: colorVar('color.text.primary') }}>
                        <span>{s.reasonType}</span>
                        <span style={{ fontWeight: 700, color: '#BE123C' }}>{s.count} 次</span>
                      </div>
                      <div style={{ height: 6, borderRadius: 999, backgroundColor: colorVar('color.bg.surface'), overflow: 'hidden' }}>
                        <div style={{ width: `${(s.count / maxCount) * 100}%`, height: '100%', borderRadius: 999, backgroundColor: '#BE123C', opacity: 0.85 }} />
                      </div>
                    </div>
                  ))}
                </div>
              </div>
            );
          })()}
        </div>
      )}

      {error !== null && (
        <div style={{ padding: `${space(3)}px`, backgroundColor: 'rgba(190,18,60,0.08)', color: colorVar('color.risk.critical'), borderRadius: `${RADIUS.md}px`, marginBottom: `${space(3)}px` }}>
          {error}
        </div>
      )}

      {notice !== null && (
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: `${space(3)}px`, backgroundColor: 'rgba(16,128,61,0.08)', color: '#15803D', borderRadius: `${RADIUS.md}px`, marginBottom: `${space(3)}px`, fontWeight: 600 }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}><Icon name="check-circle" size={16} /> {notice}</span>
          <button type="button" onClick={() => setNotice(null)} style={{ display: 'inline-flex', border: 'none', background: 'transparent', color: '#15803D', cursor: 'pointer' }} aria-label="关闭"><Icon name="close" size={16} /></button>
        </div>
      )}

      {todoItems.length > 0 && (() => {
        const myId = user?.id;
        const assignedToMe = (id: string): boolean => {
          const a = assignments[id];
          if (a === undefined) return false;
          return role === '风控' ? a.riskReviewerId === myId : role === '管理层' ? a.managerId === myId : false;
        };
        const isAssigned = (id: string): boolean => {
          const a = assignments[id];
          if (a === undefined) return false;
          return role === '风控' ? a.riskReviewerId !== null : role === '管理层' ? a.managerId !== null : false;
        };
        // 软约束：默认只看分给我的；未指派的也展示（避免遗漏）。
        const shown = onlyMine ? todoItems.filter((t) => assignedToMe(t.id) || !isAssigned(t.id)) : todoItems;
        const assignCol: TableColumn<AssessmentListItem> = {
          key: 'assignee',
          header: '指派审批人',
          render: (r) => {
            const a = assignments[r.id];
            const name = a !== undefined ? (role === '管理层' ? a.managerName : a.riskReviewerName) : null;
            const aid = a !== undefined ? (role === '管理层' ? a.managerId : a.riskReviewerId) : null;
            if (name === null || name === undefined) return <span style={{ color: colorVar('color.text.secondary') }}>未指派</span>;
            const mine = aid === myId;
            return <span style={{ ...typographyStyle('caption'), fontWeight: 600, color: mine ? '#15803D' : colorVar('color.text.primary') }}>{name}{mine ? '（我）' : ''}</span>;
          },
        };
        // 待处理列表精简列（参考销售列表）：去掉「可接受性」（与承接建议重复）与「最近处理」
        //（待审项最近处理即提交动作，对裁决无意义），插入「指派审批人」，保留操作列，避免溢出。
        const todoBase = columns.filter((col) => col.key !== 'acceptability' && col.key !== 'latest');
        const todoColumns = [...todoBase.slice(0, -1), assignCol, todoBase[todoBase.length - 1]!];
        return (
          <div style={{ marginBottom: `${space(4)}px` }}>
            <Card title={
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: `${space(2)}px` }}>
                <span>待处理 ({shown.length})</span>
                <label style={{ display: 'inline-flex', alignItems: 'center', gap: 6, ...typographyStyle('caption'), color: colorVar('color.text.secondary'), fontWeight: 400 }}>
                  <input type="checkbox" checked={onlyMine} onChange={(e) => setOnlyMine(e.target.checked)} /> 只看分给我的（含未指派）
                </label>
              </div>
            }>
              <Table columns={todoColumns} data={shown} getRowKey={(row) => row.id} caption={`${role} 待处理列表`} emptyMessage={onlyMine ? '没有分给你的待处理项（可取消勾选查看全部）' : '当前没有需要你处理的评估'} />
            </Card>
          </div>
        );
      })()}

      {role === '商务/销售' && drafts.length > 0 && (
        <div style={{ marginBottom: `${space(4)}px` }}>
          <Card title={`草稿箱 (${drafts.length})`}>
            <p style={{ margin: `0 0 ${space(2)}px`, ...typographyStyle('caption'), color: colorVar('color.text.secondary') }}>
              未运行/未提交的填写进度（服务端保存，跨设备）。「继续」回到向导接着填，运行评估后自动清除。
            </p>
            <div style={{ display: 'flex', flexDirection: 'column', gap: `${space(1)}px` }}>
              {drafts.map((d) => (
                <div key={d.id} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: `${space(2)}px`, padding: `${space(2)}px ${space(3)}px`, border: `1px solid ${colorVar('color.border.default')}`, borderRadius: `${RADIUS.md}px` }}>
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 2, minWidth: 0 }}>
                    <span style={{ ...typographyStyle('body'), color: colorVar('color.text.primary'), fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display: 'inline-flex', alignItems: 'center', gap: 6 }}>
                      <Icon name={d.sourceAssessmentId !== null ? 'edit' : 'file'} size={15} color={colorVar('color.brand.primary')} />
                      {d.sourceAssessmentId !== null ? '编辑草稿' : '新建草稿'}：{d.projectName ?? '未命名项目'}
                    </span>
                    <span style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary') }}>
                      更新于 {formatDateTime(d.updatedAt)}
                    </span>
                  </div>
                  <div style={{ display: 'flex', gap: `${space(1)}px`, whiteSpace: 'nowrap' }}>
                    <Button size="sm" onClick={() => navigate(`/new?draft=${encodeURIComponent(d.id)}`)}>继续</Button>
                    <Button size="sm" variant="ghost" onClick={() => { void removeDraft(d.id); }}>删除</Button>
                  </div>
                </div>
              ))}
            </div>
          </Card>
        </div>
      )}

      <Card
        title={
          <div style={{ display: 'flex', flexDirection: 'column', gap: `${space(1)}px` }}>
            <span>{isSales ? '我的申报 · 审批进度' : '评估历史'}</span>
            <span style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary'), fontWeight: 400 }}>
              服务端分页 · 共 {total} 条
            </span>
          </div>
        }
      >
        <div style={{ display: 'flex', gap: `${space(2)}px`, marginBottom: `${space(3)}px`, borderBottom: `1px solid ${colorVar('color.border.default')}`, paddingBottom: `${space(2)}px` }}>
          {(['active', 'archived'] as const).map((v) => {
            const active = archivedView === v;
            return (
              <button
                key={v}
                type="button"
                onClick={() => {
                  setArchivedView(v);
                  setPage(1);
                }}
                style={{
                  padding: `${space(1)}px ${space(3)}px`,
                  borderRadius: '999px',
                  border: `1px solid ${active ? colorVar('color.brand.primary') : colorVar('color.border.default')}`,
                  backgroundColor: active ? colorVar('color.brand.primary') : 'transparent',
                  color: active ? '#fff' : colorVar('color.text.secondary'),
                  cursor: 'pointer',
                  fontFamily: FONT_FAMILY,
                  ...typographyStyle('caption'),
                  fontWeight: 600,
                }}
              >
                {v === 'active' ? '进行中' : `已归档${summary.archived ? ` (${summary.archived})` : ''}`}
              </button>
            );
          })}
        </div>

        <div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: `${space(3)}px`, marginBottom: `${space(3)}px` }}>
          <input
            value={searchInput}
            onChange={(e) => setSearchInput(e.target.value)}
            placeholder="搜索项目、业务类型、行业或发起人"
            style={{
              minWidth: 280,
              flex: '1 1 320px',
              padding: `${space(2)}px ${space(3)}px`,
              border: `1px solid ${colorVar('color.border.default')}`,
              borderRadius: `${RADIUS.md}px`,
              backgroundColor: colorVar('color.bg.canvas'),
              color: colorVar('color.text.primary'),
              fontFamily: FONT_FAMILY,
              ...typographyStyle('body'),
            }}
          />
          <select
            value={statusFilter}
            onChange={(e) => {
              setStatusFilter(e.target.value as StatusFilter);
              setPage(1);
            }}
            style={{
              minWidth: 160,
              padding: `${space(2)}px ${space(3)}px`,
              border: `1px solid ${colorVar('color.border.default')}`,
              borderRadius: `${RADIUS.md}px`,
              backgroundColor: colorVar('color.bg.canvas'),
              color: colorVar('color.text.primary'),
              fontFamily: FONT_FAMILY,
              ...typographyStyle('body'),
            }}
          >
            {STATUS_FILTER_OPTIONS.map((o) => (
              <option key={o.value} value={o.value}>{o.label}</option>
            ))}
          </select>
        </div>

        {/* 状态图例 */}
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: `${space(2)}px`, marginBottom: `${space(3)}px` }}>
          {(['draft', 'pending_risk_review', 'risk_reviewed', 'approved', 'rejected', 'abandoned'] as WorkflowStatus[]).map((s) => (
            <span key={s} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, ...typographyStyle('caption'), color: colorVar('color.text.secondary') }}>
              <span style={{ width: 10, height: 10, borderRadius: '50%', backgroundColor: STATUS_STYLE[s].fg, display: 'inline-block' }} />
              {STATUS_LABEL[s]}
            </span>
          ))}
        </div>

        {loading ? (
          <div style={{ padding: `${space(4)}px`, color: colorVar('color.text.secondary') }}>加载中…</div>
        ) : (
          <>
            <Table
              columns={historyColumns}
              data={items}
              getRowKey={(row) => row.id}
              caption="全部评估记录列表"
              emptyMessage={total === 0 ? (archivedView === 'archived' ? '暂无已归档的评估' : (role === '商务/销售' ? '还没有评估。点击右上角「+ 新建评估」开始第一单。' : '暂无评估记录')) : '没有匹配筛选条件的评估'}
            />
            <Pager
              page={page}
              totalPages={totalPages}
              total={total}
              pageSize={pageSize}
              onPage={setPage}
              onPageSize={(s) => {
                setPageSize(s);
                setPage(1);
              }}
            />
          </>
        )}
      </Card>
    </div>
  );
}

/** 服务端分页器。 */
function Pager({
  page,
  totalPages,
  total,
  pageSize,
  onPage,
  onPageSize,
}: {
  readonly page: number;
  readonly totalPages: number;
  readonly total: number;
  readonly pageSize: number;
  readonly onPage: (p: number) => void;
  readonly onPageSize: (s: number) => void;
}): JSX.Element {
  const btn = (label: string, to: number, disabled: boolean): JSX.Element => (
    <button
      type="button"
      disabled={disabled}
      onClick={() => onPage(to)}
      style={{
        padding: `${space(1)}px ${space(2)}px`,
        borderRadius: `${RADIUS.sm}px`,
        border: `1px solid ${colorVar('color.border.default')}`,
        background: 'transparent',
        color: disabled ? colorVar('color.text.secondary') : colorVar('color.text.primary'),
        cursor: disabled ? 'not-allowed' : 'pointer',
        opacity: disabled ? 0.5 : 1,
        fontFamily: FONT_FAMILY,
        ...typographyStyle('caption'),
      }}
    >
      {label}
    </button>
  );
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between', gap: `${space(2)}px`, marginTop: `${space(3)}px` }}>
      <span style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary') }}>
        共 {total} 条，第 {page} / {totalPages} 页
      </span>
      <div style={{ display: 'flex', alignItems: 'center', gap: `${space(2)}px` }}>
        <span style={{ ...typographyStyle('caption'), color: colorVar('color.text.secondary') }}>每页</span>
        <select
          value={pageSize}
          onChange={(e) => onPageSize(Number(e.target.value))}
          style={{
            padding: `${space(1)}px ${space(2)}px`,
            borderRadius: `${RADIUS.sm}px`,
            border: `1px solid ${colorVar('color.border.default')}`,
            backgroundColor: colorVar('color.bg.canvas'),
            color: colorVar('color.text.primary'),
            fontFamily: FONT_FAMILY,
            ...typographyStyle('caption'),
          }}
        >
          {[10, 20, 50].map((s) => (
            <option key={s} value={s}>{s}</option>
          ))}
        </select>
        {btn('首页', 1, page <= 1)}
        {btn('上一页', page - 1, page <= 1)}
        {btn('下一页', page + 1, page >= totalPages)}
        {btn('末页', totalPages, page >= totalPages)}
      </div>
    </div>
  );
}
