'use client';

import { useEffect, useMemo, useState, type FormEvent } from 'react';
import { useParams, useRouter } from 'next/navigation';
import { AlertTriangle, Calendar, Check, ChevronLeft, Clock, FileText, Link2, Pencil, Search, Settings, Sparkles, UserPlus, X } from 'lucide-react';
import { useProductStore } from '@/stores/useProductStore';
import { useRequirementStore } from '@/stores/useRequirementStore';
import { useOvertimeStore } from '@/stores/useOvertimeStore';
import { getVersionDetail, type Priority, type VersionWithContext } from '@/lib/derive';
import type { Role } from '@/lib/stage';
import { CapsuleStages } from '@/components/version/CapsuleStages';
import { FilterSelect } from '@/components/FilterSelect';
import { CHANGE_REASON_LABEL } from '@/lib/requirement';
import { OVERTIME_REASON_LABEL } from '@/lib/overtime';
import { VersionRequirementsTab } from '@/components/version/VersionRequirementsTab';
import { PlanTab } from '@/components/version/PlanTab';
import { DevTaskTab } from '@/components/dev-task/DevTaskTab';
import { TestCaseTab } from '@/components/test-case/TestCaseTab';
import { BugTab } from '@/components/bug/BugTab';
import { useVersionPlanStore } from '@/stores/useVersionPlanStore';
import { useDevTaskStore } from '@/stores/useDevTaskStore';
import { useTestCaseStore } from '@/stores/useTestCaseStore';
import { useBugStore } from '@/stores/useBugStore';
import { useAuthStore } from '@/stores/useAuthStore';
import { useMemberStore } from '@/stores/useMemberStore';
import { useTaskCategoryStore } from '@/stores/useTaskCategoryStore';
import { calcGroupProgress as calcDevTaskProgress } from '@/lib/dev-task';
import { hasPermission } from '@/lib/permissions';
import { calcActualElapsedHours, formatActualDuration } from '@/lib/work-hours';
import { formatDateTime, formatLocalDate } from '@/lib/format';
import { getProjectAdoptedRequirementCandidates } from '@/lib/requirement-selector';
import { getRequirementVersionSelectionPatch, getRequirementVersionUnlinkPatch } from '@/lib/requirement-version-link';
import { calcBugSeverityRanking, calcPersonalEffortRanking, calcStageEffortMetrics, calcVersionOverviewEffortTotals } from '@/lib/version-overview';
import { addVersionMembers, DEFAULT_VERSION_MEMBER_ROLE, filterVersionMemberCandidates } from '@/lib/version-members';
import { addRecommendedVersionMembers, getDefaultRecommendedMemberNames, recommendVersionMembers, type MemberRecommendationGroup, type RecommendableRole } from '@/lib/member-recommendation';
import { getRequirementCoverageSummary } from '@/lib/version-plan';
import { buildVersionDataScope, selectVersionDataScope, type VersionDataScope } from '@/lib/version-data-scope';
import { calcScopedVersionProgress } from '@/lib/version-progress';
import { canSubmitReleaseForm, getReleaseProgressWarning } from '@/lib/version-release';
import { isVersionReadonly } from '@/lib/version-status';
import { loadV22VersionDetailData } from '@/lib/v22-api';
import {
  VERSION_DEVELOPMENT_TYPE_OPTIONS,
  buildVersionName,
  canSubmitVersionEditForm,
  getEditableVersionNumber,
  getRecommendedExpectedReleaseDate,
  getRecommendedWorkDays,
  getVersionDevelopmentTypeOption,
  type VersionDevelopmentType,
} from '@/lib/version-form';

function formatOverviewDateTime(value?: string | null): string {
  if (!value) return '-';
  return value.includes('T') ? formatDateTime(value) : value;
}

const BUG_SEVERITY_SEGMENTS = [
  { key: 'critical', label: '致命', color: 'bg-red-500' },
  { key: 'major', label: '严重', color: 'bg-orange-500' },
  { key: 'minor', label: '一般', color: 'bg-amber-400' },
  { key: 'trivial', label: '轻微', color: 'bg-zinc-400' },
] as const;

const RECOMMENDATION_ROLE_LABEL: Record<RecommendableRole, string> = {
  frontend: '前端',
  backend: '后端',
  testing: '测试',
};

const RECOMMENDATION_CONFIDENCE_LABEL = {
  high: '高置信',
  medium: '中置信',
  low: '低置信',
} as const;

const TABS = [
  { key: 'overview', label: '概览', permission: null as string | null },
  { key: 'requirements', label: '关联需求', permission: 'version.req:view' },
  { key: 'research', label: '调研', permission: 'version.research:view' },
  { key: 'product', label: '产品方案', permission: 'version.product_plan:view' },
  { key: 'ui', label: 'UI设计', permission: 'version.ui_plan:view' },
  { key: 'tasks', label: '开发任务', permission: 'version.devtask:view' },
  { key: 'testcases', label: '测试用例', permission: 'version.testcase:view' },
  { key: 'bugs', label: 'BUG', permission: 'version.bug:view' },
];

export default function VersionDetailPage() {
  const params = useParams();
  const router = useRouter();
  const versionId = params.id as string;
  const { overview, fetchOverview, updateVersion, deleteVersion } = useProductStore();
  const {
    requirements,
    loaded: requirementsLoaded,
    fetchRequirements,
    updateRequirement,
    createRequirement,
  } = useRequirementStore();
  const { records, fetchRecords } = useOvertimeStore();
  const {
    plans,
    loaded: plansLoaded,
    fetchPlans,
    createPlan,
    updatePlan,
    completePlan,
    deletePlan,
  } = useVersionPlanStore();
  const {
    tasks: devTasks,
    loaded: devTasksLoaded,
    fetchTasks: fetchDevTasks,
    deleteTask: deleteDevTask,
  } = useDevTaskStore();
  const {
    testCases,
    loaded: testCasesLoaded,
    fetchTestCases,
    deleteTestCase,
  } = useTestCaseStore();
  const {
    bugs,
    loaded: bugsLoaded,
    fetchBugs,
    deleteBug,
  } = useBugStore();
  const user = useAuthStore((s) => s.user);
  const { departments, members: allMembers, roles, fetchMembers } = useMemberStore();
  const { categories: taskCategories, fetchCategories } = useTaskCategoryStore();
  const currentRole = useMemo(() => roles.find((r) => r.id === user?.roleId), [roles, user?.roleId]);
  const memberCandidates = useMemo(
    () => allMembers.map((member) => ({
      id: member.id,
      name: member.name,
      departmentName: departments.find((department) => department.id === member.departmentId)?.name,
    })),
    [allMembers, departments],
  );
  const visibleTabs = useMemo(
    () => TABS.filter((t) => t.permission === null || hasPermission(currentRole, t.permission)),
    [currentRole],
  );
  const [activeTab, setActiveTab] = useState('overview');
  useEffect(() => {
    if (visibleTabs.length > 0 && !visibleTabs.find((t) => t.key === activeTab)) {
      setActiveTab(visibleTabs[0].key);
    }
  }, [visibleTabs, activeTab]);
  const [showMemberModal, setShowMemberModal] = useState(false);
  const [showRecommendModal, setShowRecommendModal] = useState(false);
  const [showEditModal, setShowEditModal] = useState(false);
  const [showReleaseModal, setShowReleaseModal] = useState(false);
  const [recommendationDataReady, setRecommendationDataReady] = useState(false);
  const [v22Scope, setV22Scope] = useState<VersionDataScope | null>(null);

  useEffect(() => { fetchOverview(); }, [fetchOverview]);
  useEffect(() => {
    let active = true;
    setV22Scope(null);
    loadV22VersionDetailData(versionId)
      .then((data) => {
        if (active) setV22Scope(data.scope);
      })
      .catch(() => {
        if (active) setV22Scope(null);
      });
    return () => { active = false; };
  }, [versionId]);
  useEffect(() => {
    if (activeTab === 'overview') {
      fetchRecords();
      return;
    }
    if (activeTab === 'requirements') {
      fetchRequirements();
      fetchDevTasks();
      return;
    }
    if (activeTab === 'research' || activeTab === 'product' || activeTab === 'ui') {
      fetchRequirements();
      fetchPlans();
      return;
    }
    if (activeTab === 'tasks') {
      fetchRequirements();
      fetchDevTasks();
      return;
    }
    if (activeTab === 'testcases') {
      fetchRequirements();
      fetchDevTasks();
      fetchTestCases();
      fetchBugs();
      return;
    }
    if (activeTab === 'bugs') {
      fetchRequirements();
      fetchTestCases();
      fetchBugs();
    }
  }, [activeTab, fetchBugs, fetchDevTasks, fetchPlans, fetchRecords, fetchRequirements, fetchTestCases]);
  useEffect(() => {
    if (!showRecommendModal) return;
    fetchRecords();
    fetchRequirements();
    fetchPlans();
    fetchDevTasks();
    fetchTestCases();
    fetchBugs();
  }, [fetchBugs, fetchDevTasks, fetchPlans, fetchRecords, fetchRequirements, fetchTestCases, showRecommendModal]);
  useEffect(() => {
    let active = true;
    setRecommendationDataReady(false);
    Promise.all([Promise.resolve(fetchMembers()), Promise.resolve(fetchCategories())]).finally(() => {
      if (active) setRecommendationDataReady(true);
    });
    return () => { active = false; };
  }, [fetchMembers, fetchCategories]);

  const version = useMemo(() => getVersionDetail(overview, versionId), [overview, versionId]);
  const appDataVersionScope = useMemo(
    () => version
      ? buildVersionDataScope({
        versionId: version.id,
        requirements,
        plans,
        devTasks,
        testCases,
        bugs,
        overtimeRecords: records,
      })
      : null,
    [version, requirements, plans, devTasks, testCases, bugs, records],
  );
  const versionWriteStoresReady = requirementsLoaded && plansLoaded && devTasksLoaded && testCasesLoaded && bugsLoaded;
  const requirementWriteReady = requirementsLoaded && devTasksLoaded;
  const planWriteReady = requirementsLoaded && plansLoaded;
  const devTaskWriteReady = requirementsLoaded && devTasksLoaded;
  const testCaseWriteReady = requirementsLoaded && devTasksLoaded && testCasesLoaded && bugsLoaded;
  const bugWriteReady = requirementsLoaded && testCasesLoaded && bugsLoaded;
  const loadAllVersionStores = () => {
    fetchRecords();
    fetchRequirements();
    fetchPlans();
    fetchDevTasks();
    fetchTestCases();
    fetchBugs();
  };
  const versionScope = useMemo(
    () => selectVersionDataScope({
      appDataScope: appDataVersionScope,
      v22Scope: versionWriteStoresReady ? null : v22Scope,
    }),
    [appDataVersionScope, v22Scope, versionWriteStoresReady],
  );
  const releaseProgress = versionScope
    ? calcScopedVersionProgress(versionScope.plans, versionScope.devTasks, versionScope.testCases)
    : 0;
  const versionReadonly = version ? isVersionReadonly(version.status) : false;

  // 自动同步版本状态：有计划开始时间<=今天，版本应进入对应阶段
  useEffect(() => {
    if (!version || !versionScope || !versionScope.plans.length) return;
    if (version.status !== 'planned' && version.status !== 'developing') return;
    const today = new Date().toISOString().slice(0, 10);
    const stageMap = { research: 'requirement', product: 'product_design', ui: 'ui_design' } as const;
    const stageOrder: string[] = ['requirement', 'product_design', 'ui_design'];
    const versionPlans = versionScope.plans.filter((p) => p.startTime <= today);
    if (versionPlans.length === 0) return;
    let targetStage = '';
    for (const p of versionPlans) {
      const s = stageMap[p.type];
      if (!targetStage || stageOrder.indexOf(s) > stageOrder.indexOf(targetStage)) {
        targetStage = s;
      }
    }
    if (version.status === 'planned' || (version.currentStage && stageOrder.indexOf(targetStage) > stageOrder.indexOf(version.currentStage))) {
      updateVersion(version.productId, version.id, { status: 'developing', currentStage: targetStage as any });
    }
  }, [versionScope, version, updateVersion]);

  if (!version) {
    return (
      <div className="flex h-full flex-col items-center justify-center gap-3">
        <p className="text-sm text-[var(--ink-muted)]">版本不存在</p>
        <button onClick={() => router.push('/versions')} className="text-xs text-[var(--accent)] hover:underline">返回版本列表</button>
      </div>
    );
  }

  // 权限校验：只有参与人员可以访问（超管不受限）
  const currentUserName = user?.name || '';
  const isSuperAdmin = !!currentRole && currentRole.permissions.includes('*');
  const isMember = isSuperAdmin || (version.members ?? []).length === 0 || (version.members ?? []).some((m) => m.name === currentUserName);
  if (!isMember) {
    return (
      <div className="flex h-full flex-col items-center justify-center gap-3">
        <p className="text-sm text-[var(--ink-muted)]">您不是该版本的参与人员，无权访问</p>
        <button onClick={() => router.push('/versions')} className="text-xs text-[var(--accent)] hover:underline">返回版本列表</button>
      </div>
    );
  }

  const scopedVersionData = versionScope!;
  const appDataScopedVersionData = appDataVersionScope ?? scopedVersionData;
  const displayRequirements = requirementsLoaded ? requirements : scopedVersionData.requirements;
  const displayPlans = plansLoaded ? appDataScopedVersionData.plans : scopedVersionData.plans;
  const displayDevTasks = devTasksLoaded ? appDataScopedVersionData.devTasks : scopedVersionData.devTasks;
  const displayTestCases = testCasesLoaded ? appDataScopedVersionData.testCases : scopedVersionData.testCases;
  const displayBugs = bugsLoaded ? appDataScopedVersionData.bugs : scopedVersionData.bugs;
  const displayRequirementIds = requirementsLoaded
    ? appDataScopedVersionData.requirementIds
    : scopedVersionData.requirementIds;
  const memberRecommendationGroups = showRecommendModal && recommendationDataReady ? (() => {
    const currentSystemParticipation = new Map<string, number>();
    overview.forEach((product) => {
      product.versions.forEach((item) => {
        const uniqueNames = new Set((item.members ?? []).map((member) => member.name));
        uniqueNames.forEach((name) => currentSystemParticipation.set(name, (currentSystemParticipation.get(name) ?? 0) + 1));
      });
    });

    return recommendVersionMembers({
      candidates: memberCandidates,
      currentMembers: version.members ?? [],
      devTasks,
      testCases,
      bugs,
      overtimeRecords: records,
      versionDeadline: version.expectedReleaseDate,
      scopeRequirements: scopedVersionData.requirements,
      scopeDevTasks: scopedVersionData.devTasks,
      scopeTestCases: scopedVersionData.testCases,
      taskCategories,
      historicalStats: memberCandidates.flatMap((member) => {
        const projectParticipationCount = currentSystemParticipation.get(member.name);
        return typeof projectParticipationCount === 'number'
          ? [{ name: member.name, projectParticipationCount }]
          : [];
      }),
    });
  })() : [];

  const renderActions = () => {
    const buttons: { label: string; action: () => void; danger?: boolean; tone?: 'release'; icon?: JSX.Element }[] = [];
    if (versionReadonly) return null;
    if (version.status !== 'released' && version.status !== 'closed') {
      buttons.push({ label: '发版', action: () => setShowReleaseModal(true), tone: 'release' });
    }
    buttons.push({ label: '编辑', action: () => setShowEditModal(true), icon: <Pencil className="h-3.5 w-3.5" /> });
    if (version.status === 'planned') {
      buttons.push({ label: '删除', action: () => {
        if (confirm('确认删除该版本？关联的需求会回到需求池，版本下的计划、开发任务、测试用例、Bug 将被清除。')) {
          // 释放关联需求
          if (!versionWriteStoresReady) {
            loadAllVersionStores();
            alert('版本关联数据正在加载，请稍后再删除。');
            return;
          }
          scopedVersionData.requirements.forEach((r) => updateRequirement(r.id, getRequirementVersionUnlinkPatch(r)));
          // 清理计划任务
          scopedVersionData.plans.forEach((p) => deletePlan(p.id));
          // 清理开发任务
          scopedVersionData.devTasks.forEach((t) => deleteDevTask(t.id));
          // 清理测试用例和Bug
          scopedVersionData.testCases.forEach((c) => deleteTestCase(c.id));
          scopedVersionData.bugs.forEach((b) => deleteBug(b.id));
          deleteVersion(version.productId, version.id);
          router.push('/versions');
        }
      }, danger: true });
      buttons.push({ label: '关闭', action: () => updateVersion(version.productId, version.id, { status: 'closed' }), danger: true });
    } else if (version.status === 'developing') {
      buttons.push({ label: '暂停', action: () => updateVersion(version.productId, version.id, { status: 'paused' }) });
      buttons.push({ label: '关闭', action: () => updateVersion(version.productId, version.id, { status: 'closed' }), danger: true });
    } else if (version.status === 'paused') {
      buttons.push({ label: '恢复', action: () => updateVersion(version.productId, version.id, { status: 'developing' }) });
      buttons.push({ label: '关闭', action: () => updateVersion(version.productId, version.id, { status: 'closed' }), danger: true });
    }
    return buttons.map((btn) => (
      <button
        key={btn.label}
        onClick={btn.action}
        className={`inline-flex h-7 items-center gap-1.5 rounded-md border px-3 text-[12px] font-medium transition-colors ${btn.danger ? 'border-red-200 text-red-600 hover:bg-red-50' : btn.tone === 'release' ? 'border-emerald-200 text-emerald-700 hover:bg-emerald-50' : 'border-[var(--line)] text-[var(--ink-soft)] hover:bg-[var(--bg-subtle)]'}`}
      >
        {btn.icon}
        {btn.label}
      </button>
    ));
  };

  return (
    <div className="flex h-full flex-col">
      {/* Header */}
      <header className="flex h-14 shrink-0 items-center justify-between border-b border-[var(--line)] bg-[var(--bg-card)] px-5">
        <div className="flex items-center">
          <button onClick={() => router.push('/versions')} className="flex items-center gap-1 rounded-md px-1.5 py-1 text-[12px] text-[var(--ink-muted)] hover:bg-[var(--bg-subtle)] hover:text-[var(--ink)]">
            <ChevronLeft className="h-3.5 w-3.5" strokeWidth={2} />版本
          </button>
          <span className="ml-2 text-[var(--ink-muted)]">/</span>
          <button onClick={() => router.push('/products')} className="ml-2 text-[12px] text-[var(--ink-muted)] hover:text-[var(--accent)] hover:underline">{version.productName}</button>
          {version.projectName && version.projectName !== '未关联' && (
            <>
              <span className="ml-1.5 text-[var(--ink-muted)]">/</span>
              <span className="ml-1.5 text-[12px] text-[var(--ink-muted)]">{version.projectName}</span>
            </>
          )}
          <span className="ml-1.5 text-[var(--ink-muted)]">/</span>
          <span className="ml-1.5 text-[15px] font-semibold text-[var(--ink)]">{version.name}</span>
        </div>
        <div className="flex items-center gap-2">{renderActions()}</div>
      </header>

      {/* Tab bar */}
      <div className="flex items-center gap-0 border-b border-[var(--line)] bg-[var(--bg-card)] px-5">
        {visibleTabs.map((tab) => (
          <button
            key={tab.key}
            onClick={() => setActiveTab(tab.key)}
            className={`px-4 py-2.5 text-[13px] font-medium border-b-2 transition-colors ${activeTab === tab.key ? 'border-[var(--accent)] text-[var(--ink)]' : 'border-transparent text-[var(--ink-muted)] hover:text-[var(--ink-soft)]'}`}
          >
            {tab.label}
          </button>
        ))}
      </div>

      {/* Content */}
      <div className="flex-1 overflow-y-auto p-5 bg-[var(--bg)]">
        {activeTab === 'overview' ? (
          (() => {
            const now = new Date();
            const versionReqs = scopedVersionData.requirements;
            const versionPlans = scopedVersionData.plans;
            const versionDevTasks = scopedVersionData.devTasks;
            const versionTCs = scopedVersionData.testCases;
            const versionBugs = scopedVersionData.bugs;
            const versionOT = scopedVersionData.overtimeRecords;
            const stageEffortMetrics = calcStageEffortMetrics({
              plans: versionPlans,
              devTasks: versionDevTasks,
              testCases: versionTCs,
              bugs: versionBugs,
              now,
            });
            const effortTotals = calcVersionOverviewEffortTotals({
              plans: versionPlans,
              devTasks: versionDevTasks,
              testCases: versionTCs,
              bugs: versionBugs,
              overtimeRecords: versionOT,
              now,
            });
            const personalRanking = calcPersonalEffortRanking({
              plans: versionPlans,
              devTasks: versionDevTasks,
              testCases: versionTCs,
              bugs: versionBugs,
              overtimeRecords: versionOT,
              now,
            });
            const bugRanking = calcBugSeverityRanking(versionBugs);

            // 人员加班排名
            const personOT: Record<string, number> = {};
            versionOT.forEach((r) => { personOT[r.person] = (personOT[r.person] || 0) + r.duration; });
            const otRanking = Object.entries(personOT).sort((a, b) => b[1] - a[1]).map(([name, hours]) => ({ name, hours: Math.round(hours * 10) / 10 }));

            // 加班原因占比
            const reasonMap: Record<string, number> = {};
            versionOT.forEach((r) => { reasonMap[r.reasonId] = (reasonMap[r.reasonId] || 0) + r.duration; });
            const reasonRanking = Object.entries(reasonMap).sort((a, b) => b[1] - a[1]);
            const reasonTotal = reasonRanking.reduce((s, [, v]) => s + v, 0) || 1;

            return (
              <div className="space-y-4">
                {/* 阶段概览 */}
                {(() => {
                  const researchPlans = versionPlans.filter((p) => p.type === 'research');
                  const productPlans = versionPlans.filter((p) => p.type === 'product');
                  const uiPlans = versionPlans.filter((p) => p.type === 'ui');

                  const calcGroupProgress = (group: typeof versionPlans, type: 'research' | 'product' | 'ui') => {
                    if (group.length === 0) return 0;
                    // 分子=已完成子任务数，分母=所有plan的子任务总数
                    // completed 的 plan：其子任务全算已完成
                    let totalItems = 0;
                    let doneItems = 0;
                    for (const p of group) {
                      if (type === 'research') {
                        const tasks = p.tasks || [];
                        const count = Math.max(tasks.length, 1);
                        totalItems += count;
                        if (p.status === 'completed') {
                          doneItems += count;
                        } else {
                          doneItems += tasks.filter((t) => t.status === 'completed').length;
                        }
                      } else {
                        const summary = getRequirementCoverageSummary(p);
                        const count = Math.max(summary.total, 1);
                        totalItems += count;
                        if (p.status === 'completed') {
                          doneItems += count;
                        } else {
                          doneItems += summary.completed;
                        }
                      }
                    }
                    return totalItems > 0 ? Math.round((doneItems / totalItems) * 100) : 0;
                  };

                  const getPlanStatus = (group: typeof versionPlans): 'idle' | 'active' | 'done' => {
                    if (group.length === 0) return 'idle';
                    if (group.every((p) => p.status === 'completed')) return 'done';
                    if (group.some((p) => p.status === 'in_progress')) return 'active';
                    return 'idle';
                  };

                  type StageProgressItem = { percent: number; status: 'idle' | 'active' | 'done'; actualHours?: number; aiEstimateHours?: number };
                  const stageProgress: Partial<Record<string, StageProgressItem>> = {};

                  stageProgress['requirement'] = {
                    percent: calcGroupProgress(researchPlans, 'research'),
                    status: getPlanStatus(researchPlans),
                    ...stageEffortMetrics.requirement,
                  };
                  stageProgress['product_design'] = {
                    percent: calcGroupProgress(productPlans, 'product'),
                    status: getPlanStatus(productPlans),
                    ...stageEffortMetrics.product_design,
                  };
                  stageProgress['ui_design'] = {
                    percent: calcGroupProgress(uiPlans, 'ui'),
                    status: getPlanStatus(uiPlans),
                    ...stageEffortMetrics.ui_design,
                  };

                  // 开发阶段
                  const allSubmitted = versionDevTasks.length > 0 && versionDevTasks.every((t) => t.status === 'submitted');
                  const hasActiveDevTask = versionDevTasks.some((t) => t.status === 'in_progress' || t.status === 'testing');
                  stageProgress['dev'] = {
                    percent: versionDevTasks.length > 0 ? calcDevTaskProgress(versionDevTasks) : 0,
                    status: versionDevTasks.length === 0 ? 'idle' : allSubmitted ? 'done' : hasActiveDevTask ? 'active' : 'idle',
                    ...stageEffortMetrics.dev,
                  };

                  // 测试阶段
                  const executed = versionTCs.filter((c) => c.status === 'passed' || c.status === 'failed' || c.status === 'blocked').length;
                  const testPercent = versionTCs.length > 0 ? Math.round((executed / versionTCs.length) * 100) : 0;
                  const allPassed = versionTCs.length > 0 && versionTCs.every((c) => c.status === 'passed');
                  const hasRunning = versionTCs.some((c) => c.status === 'running');
                  stageProgress['testing'] = {
                    percent: testPercent,
                    status: versionTCs.length === 0 ? 'idle' : allPassed ? 'done' : (hasRunning || executed > 0) ? 'active' : 'idle',
                    ...stageEffortMetrics.testing,
                  };

                  // BUG 阶段：已关闭Bug / 总Bug
                  const closedBugs = versionBugs.filter((b) => b.status === 'closed' || b.status === 'rejected').length;
                  const bugPercent = versionBugs.length > 0 ? Math.round((closedBugs / versionBugs.length) * 100) : 0;
                  const allClosed = versionBugs.length > 0 && versionBugs.every((b) => b.status === 'closed' || b.status === 'rejected');
                  stageProgress['bug'] = {
                    percent: bugPercent,
                    status: versionBugs.length === 0 ? 'idle' : allClosed ? 'done' : 'active',
                    ...stageEffortMetrics.bug,
                  };

                  return <CapsuleStages stageProgress={stageProgress as any} />;
                })()}

                {/* 3. 时间与投入概览 + 参与人员 + 相关链接 */}
                <div className="space-y-4">
                  {(() => {
                      const startDates: string[] = [];
                      versionPlans.forEach((p) => {
                        if (p.actualStartAt) startDates.push(p.actualStartAt);
                        else if (p.status === 'pending' && p.startTime && new Date(p.startTime) <= now) startDates.push(p.startTime);
                      });
                      versionDevTasks.forEach((t) => { if (t.actualStartAt) startDates.push(t.actualStartAt); });
                      versionTCs.forEach((c) => { if (c.startedAt) startDates.push(c.startedAt); });
                      const actualStartIso = startDates.length > 0 ? startDates.sort()[0] : (version.startDate ?? null);

                      const endDates: string[] = [];
                      versionPlans.forEach((p) => { if (p.completedAt) endDates.push(p.completedAt); });
                      versionDevTasks.forEach((t) => { if (t.actualEndAt) endDates.push(t.actualEndAt); });
                      versionTCs.forEach((c) => { if (c.completedAt) endDates.push(c.completedAt); });
                      versionBugs.forEach((b) => {
                        if (b.closedAt) endDates.push(b.closedAt);
                        else if (b.resolvedAt) endDates.push(b.resolvedAt);
                        else if ((b.status === 'closed' || b.status === 'rejected') && b.updatedAt) endDates.push(b.updatedAt);
                      });
                      const actualEndIso = endDates.length > 0 ? endDates.sort().reverse()[0] : null;

                      const isTerminalVersion = version.status === 'released' || version.status === 'closed';
                      const deadline = version.expectedReleaseDate;
                      const actualReleaseDate = version.releaseDate;
                      const versionActualHours = calcActualElapsedHours(actualStartIso, isTerminalVersion ? actualEndIso : now.toISOString());
                      let overdueDays = 0;
                      if (deadline && actualReleaseDate) {
                        const endDate = new Date(actualReleaseDate);
                        const deadlineDate = new Date(deadline);
                        endDate.setHours(0, 0, 0, 0);
                        deadlineDate.setHours(0, 0, 0, 0);
                        overdueDays = Math.floor((endDate.getTime() - deadlineDate.getTime()) / (1000 * 60 * 60 * 24));
                      }

                      const metrics = [
                        { label: '开始', value: actualStartIso ? formatOverviewDateTime(actualStartIso) : '未开始', icon: <Calendar className="h-3.5 w-3.5" /> },
                        { label: '期望发版', value: deadline ? formatOverviewDateTime(deadline) : '未设置' },
                        { label: '实际发版日期', value: actualReleaseDate ? formatOverviewDateTime(actualReleaseDate) : '未发布' },
                        { label: '实际截止', value: isTerminalVersion ? (actualEndIso ? formatOverviewDateTime(actualEndIso) : '未记录') : '未完成' },
                        { label: '实际耗时', value: formatActualDuration(versionActualHours), icon: <Clock className="h-3.5 w-3.5" /> },
                        { label: '人力总投入', value: formatActualDuration(effortTotals.actualHours), tone: 'accent' as const },
                        { label: '加班时长', value: formatActualDuration(effortTotals.overtimeHours), sub: '来自加班记录', tone: 'orange' as const },
                      ];

                      return (
                        <div className="overflow-hidden rounded-xl border border-[var(--line)] bg-[var(--bg-card)]">
                          <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-7">
                            {metrics.map((item) => (
                              <OverviewMetric
                                key={item.label}
                                label={item.label}
                                value={item.value}
                                icon={item.icon}
                                sub={item.sub}
                                tone={item.tone}
                              />
                            ))}
                          </div>
                          {overdueDays > 0 && (
                            <div className="border-t border-red-100 bg-red-50 px-4 py-2 text-[12px] font-medium text-red-600">
                              实际发版晚于期望发版 {overdueDays} 天
                            </div>
                          )}
                        </div>
                      );
                    })()}
                  <div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
                    <div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-4">
                      <div className="flex items-center justify-between gap-3 mb-2">
                        <div className="text-[11px] text-[var(--ink-muted)] font-medium">参与人员</div>
                        {!versionReadonly && (
                          <div className="flex items-center gap-2">
                            <button
                              onClick={() => setShowRecommendModal(true)}
                              disabled={!recommendationDataReady}
                              className="flex items-center gap-1 text-[11px] font-medium text-violet-600 hover:underline disabled:cursor-not-allowed disabled:text-[var(--ink-muted)] disabled:no-underline"
                            >
                              <Sparkles className="h-3 w-3" />{recommendationDataReady ? 'AI推荐' : '加载中'}
                            </button>
                            <button onClick={() => setShowMemberModal(true)} className="flex items-center gap-1 text-[11px] text-[var(--accent)] hover:underline">
                              <Settings className="h-3 w-3" />设置
                            </button>
                          </div>
                        )}
                      </div>
                      {(() => {
                        const membersList = version.members ?? [];
                        const roleLabel: Record<string, string> = { research: '调研', product: '产品', ui: 'UI', frontend: '前端', backend: '后端', testing: '测试' };
                        if (membersList.length === 0) return <span className="text-[12px] text-[var(--ink-muted)]">暂无成员，请点击设置添加</span>;
                        return (
                          <div className="flex flex-wrap gap-1.5">
                            {membersList.map((m, i) => (
                              <span key={`${m.name}-${i}`} className="inline-flex items-center gap-1 rounded-full bg-[var(--bg-subtle)] px-2.5 py-1 text-[11px] text-[var(--ink-soft)]">
                                <span className="text-[var(--ink-muted)]">{roleLabel[m.role] ?? m.role}</span>
                                <span className="font-medium text-[var(--ink)]">{m.name}</span>
                              </span>
                            ))}
                          </div>
                        );
                      })()}
                    </div>
                    <div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-4">
                      <div className="text-[11px] text-[var(--ink-muted)] mb-3 font-medium">相关链接</div>
                      {(() => {
                        const vPlansCompleted = versionPlans.filter((p) => p.status === 'completed' && p.resultUrl);
                        const typeLabel: Record<string, string> = { research: '调研', product: '产品方案', ui: 'UI设计' };
                        const grouped = ['research', 'product', 'ui'].map((type) => ({
                          type,
                          label: typeLabel[type],
                          items: vPlansCompleted.filter((p) => p.type === type),
                        })).filter((g) => g.items.length > 0);

                        if (grouped.length === 0) {
                          return <span className="text-[12px] text-[var(--ink-muted)]">暂无成果链接</span>;
                        }
                        return (
                          <div className="space-y-3">
                            {grouped.map((g) => (
                              <div key={g.type}>
                                <div className="text-[10px] text-[var(--ink-muted)] mb-1">{g.label}</div>
                                <div className="space-y-1.5">
                                  {g.items.map((p) => (
                                    <a key={p.id} href={p.resultUrl} target="_blank" rel="noopener noreferrer" className="flex items-center gap-1.5 text-[12px] text-[var(--accent)] hover:underline">
                                      {p.resultType === 'file' ? <FileText className="h-3 w-3" /> : <Link2 className="h-3 w-3" />}
                                      <span className="truncate">{p.resultTitle || p.resultFileName || p.title}</span>
                                    </a>
                                  ))}
                                </div>
                              </div>
                            ))}
                          </div>
                        );
                      })()}
                    </div>
                  </div>
                </div>

                {/* 5. 加班时长排名 + 加班原因占比 */}
                <div className="grid grid-cols-2 gap-4">
                  {/* 参与人员加班排名 */}
                  <div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-4">
                    <div className="text-[11px] text-[var(--ink-muted)] mb-3 font-medium">加班时长排名</div>
                    {otRanking.length === 0 ? (
                      <span className="text-[12px] text-[var(--ink-muted)]">暂无数据</span>
                    ) : (
                      <div className="space-y-2">
                        {otRanking.slice(0, 8).map((item, i) => (
                          <div key={item.name} className="flex items-center gap-2">
                            <span className={`flex h-5 w-5 items-center justify-center rounded-full text-[10px] font-semibold ${i < 3 ? 'bg-[var(--accent-soft)] text-[var(--accent)]' : 'bg-[var(--bg-subtle)] text-[var(--ink-muted)]'}`}>{i + 1}</span>
                            <span className="flex-1 text-[12px] text-[var(--ink)]">{item.name}</span>
                            <span className="text-[12px] font-medium tabular-nums text-[var(--ink-soft)]">{item.hours}h</span>
                          </div>
                        ))}
                      </div>
                    )}
                  </div>

                  {/* 加班原因占比 - 饼图 */}
                  <div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-4">
                    <div className="text-[11px] text-[var(--ink-muted)] mb-3 font-medium">加班原因占比</div>
                    {reasonRanking.length === 0 ? (
                      <span className="text-[12px] text-[var(--ink-muted)]">暂无数据</span>
                    ) : (
                      <div className="flex items-center gap-4">
                        {/* SVG 环形饼图 */}
                        <svg viewBox="0 0 100 100" className="h-28 w-28 shrink-0">
                          {(() => {
                            const colors = ['#3b82f6', '#f97316', '#8b5cf6', '#10b981', '#ef4444', '#f59e0b', '#6366f1', '#ec4899'];
                            let cumPercent = 0;
                            return reasonRanking.map(([reasonId, hours], i) => {
                              const percent = hours / reasonTotal;
                              const startAngle = cumPercent * 360;
                              cumPercent += percent;
                              const endAngle = cumPercent * 360;
                              const largeArc = percent > 0.5 ? 1 : 0;
                              const startRad = ((startAngle - 90) * Math.PI) / 180;
                              const endRad = ((endAngle - 90) * Math.PI) / 180;
                              const x1 = 50 + 40 * Math.cos(startRad);
                              const y1 = 50 + 40 * Math.sin(startRad);
                              const x2 = 50 + 40 * Math.cos(endRad);
                              const y2 = 50 + 40 * Math.sin(endRad);
                              if (percent >= 1) {
                                return <circle key={reasonId} cx="50" cy="50" r="40" fill={colors[i % colors.length]} />;
                              }
                              return (
                                <path
                                  key={reasonId}
                                  d={`M 50 50 L ${x1} ${y1} A 40 40 0 ${largeArc} 1 ${x2} ${y2} Z`}
                                  fill={colors[i % colors.length]}
                                />
                              );
                            });
                          })()}
                          <circle cx="50" cy="50" r="22" fill="var(--bg-card)" />
                        </svg>
                        {/* 图例 */}
                        <div className="space-y-1.5 flex-1">
                          {reasonRanking.map(([reasonId, hours], i) => {
                            const colors = ['#3b82f6', '#f97316', '#8b5cf6', '#10b981', '#ef4444', '#f59e0b', '#6366f1', '#ec4899'];
                            const percent = Math.round((hours / reasonTotal) * 100);
                            const reasonName = OVERTIME_REASON_LABEL[reasonId] || reasonId;
                            return (
                              <div key={reasonId} className="flex items-center gap-2 text-[11px]">
                                <span className="h-2.5 w-2.5 rounded-sm shrink-0" style={{ backgroundColor: colors[i % colors.length] }} />
                                <span className="flex-1 text-[var(--ink-soft)] truncate">{reasonName}</span>
                                <span className="tabular-nums text-[var(--ink-muted)]">{percent}%</span>
                              </div>
                            );
                          })}
                        </div>
                      </div>
                    )}
                  </div>
                </div>

                {/* 6. 需求变更统计 */}
                {(() => {
                  const changeReqs = versionReqs.filter((r) => r.reqType === 'change');
                  if (changeReqs.length === 0) return null;

                  // 变更人员排名
                  const personChange: Record<string, number> = {};
                  changeReqs.forEach((r) => { const by = r.changeBy || r.creator; personChange[by] = (personChange[by] || 0) + 1; });
                  const changePersonRanking = Object.entries(personChange).sort((a, b) => b[1] - a[1]);

                  // 变更原因占比
                  const reasonCount: Record<string, number> = {};
                  changeReqs.forEach((r) => { if (r.changeReason) reasonCount[r.changeReason] = (reasonCount[r.changeReason] || 0) + 1; });
                  const changeReasonRanking = Object.entries(reasonCount).sort((a, b) => b[1] - a[1]);
                  const reasonTotal = changeReqs.length;

                  const colors = ['#f97316', '#3b82f6', '#8b5cf6', '#10b981'];

                  return (
                    <div className="grid grid-cols-2 gap-4">
                      <div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-4">
                        <div className="text-[11px] text-[var(--ink-muted)] mb-3 font-medium">变更人员排名</div>
                        {changePersonRanking.length === 0 ? (
                          <span className="text-[12px] text-[var(--ink-muted)]">暂无数据</span>
                        ) : (
                          <div className="space-y-2">
                            {changePersonRanking.map(([name, count], i) => (
                              <div key={name} className="flex items-center justify-between">
                                <div className="flex items-center gap-2">
                                  <span className="text-[11px] font-medium text-[var(--ink-muted)] w-4">{i + 1}</span>
                                  <span className="text-[12px] text-[var(--ink)]">{name}</span>
                                </div>
                                <span className="text-[12px] font-medium tabular-nums text-orange-600">{count} 次</span>
                              </div>
                            ))}
                          </div>
                        )}
                      </div>
                      <div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-4">
                        <div className="text-[11px] text-[var(--ink-muted)] mb-3 font-medium">变更原因占比</div>
                        <div className="flex items-center gap-4">
                          <svg viewBox="0 0 100 100" className="h-24 w-24 shrink-0">
                            {(() => {
                              let cumPercent = 0;
                              return changeReasonRanking.map(([reasonId, count], i) => {
                                const percent = count / reasonTotal;
                                const startAngle = cumPercent * 360;
                                cumPercent += percent;
                                const endAngle = cumPercent * 360;
                                const largeArc = percent > 0.5 ? 1 : 0;
                                const startRad = ((startAngle - 90) * Math.PI) / 180;
                                const endRad = ((endAngle - 90) * Math.PI) / 180;
                                const x1 = 50 + 40 * Math.cos(startRad);
                                const y1 = 50 + 40 * Math.sin(startRad);
                                const x2 = 50 + 40 * Math.cos(endRad);
                                const y2 = 50 + 40 * Math.sin(endRad);
                                if (percent >= 1) return <circle key={reasonId} cx="50" cy="50" r="40" fill={colors[i % colors.length]} />;
                                return <path key={reasonId} d={`M 50 50 L ${x1} ${y1} A 40 40 0 ${largeArc} 1 ${x2} ${y2} Z`} fill={colors[i % colors.length]} />;
                              });
                            })()}
                            <circle cx="50" cy="50" r="20" fill="var(--bg-card)" />
                          </svg>
                          <div className="space-y-1.5 flex-1">
                            {changeReasonRanking.map(([reasonId, count], i) => (
                              <div key={reasonId} className="flex items-center gap-2 text-[11px]">
                                <span className="h-2.5 w-2.5 rounded-sm shrink-0" style={{ backgroundColor: colors[i % colors.length] }} />
                                <span className="flex-1 text-[var(--ink-soft)] truncate">{CHANGE_REASON_LABEL[reasonId as keyof typeof CHANGE_REASON_LABEL] || reasonId}</span>
                                <span className="tabular-nums text-[var(--ink-muted)]">{Math.round((count / reasonTotal) * 100)}%</span>
                              </div>
                            ))}
                          </div>
                        </div>
                      </div>
                    </div>
                  );
                })()}

                <div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
                  {/* 个人耗时排名 */}
                  {(() => {
                    const maxHours = personalRanking[0]?.total || 1;

                    return (
                      <div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-4">
                        <div className="mb-3 flex flex-wrap items-center justify-between gap-2">
                          <div className="text-[11px] font-medium text-[var(--ink-muted)]">个人耗时排名</div>
                          <div className="flex items-center gap-3 text-[10px] text-[var(--ink-muted)]">
                            <span className="inline-flex items-center gap-1"><span className="h-2 w-2 rounded-sm bg-[var(--accent)]" />实际</span>
                            <span className="inline-flex items-center gap-1"><span className="h-2 w-2 rounded-sm bg-orange-500" />加班</span>
                          </div>
                        </div>
                        {personalRanking.length === 0 ? (
                          <span className="text-[12px] text-[var(--ink-muted)]">暂无数据</span>
                        ) : (
                          <div className="space-y-2.5">
                            {personalRanking.slice(0, 8).map((item, i) => {
                              const totalWidth = (item.total / maxHours) * 100;
                              const actualWidth = item.total > 0 ? (item.actualHours / item.total) * 100 : 0;
                              const overtimeWidth = item.total > 0 ? (item.overtimeHours / item.total) * 100 : 0;

                              return (
                                <div key={item.name}>
                                  <div className="mb-1 flex items-center gap-2">
                                    <span className={`flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[10px] font-semibold ${i < 3 ? 'bg-[var(--accent-soft)] text-[var(--accent)]' : 'bg-[var(--bg-subtle)] text-[var(--ink-muted)]'}`}>{i + 1}</span>
                                    <span className="min-w-0 flex-1 truncate text-[12px] text-[var(--ink)]">{item.name}</span>
                                    <span className="shrink-0 text-[12px] font-medium tabular-nums text-[var(--ink-soft)]">{formatActualDuration(item.total)}</span>
                                  </div>
                                  <div className="ml-7 h-2 overflow-hidden rounded-full bg-[var(--bg-subtle)]">
                                    <div className="flex h-full overflow-hidden rounded-full" style={{ width: `${totalWidth}%` }}>
                                      {item.actualHours > 0 && <div className="h-full bg-[var(--accent)]" style={{ width: `${actualWidth}%` }} />}
                                      {item.overtimeHours > 0 && <div className="h-full bg-orange-500" style={{ width: `${overtimeWidth}%` }} />}
                                    </div>
                                  </div>
                                  <div className="ml-7 mt-1 flex flex-wrap gap-x-2 gap-y-0.5 text-[10px] text-[var(--ink-muted)]">
                                    <span>实际 {formatActualDuration(item.actualHours)}</span>
                                    <span>加班 {formatActualDuration(item.overtimeHours)}</span>
                                  </div>
                                </div>
                              );
                            })}
                          </div>
                        )}
                      </div>
                    );
                  })()}

                  {/* Bug 数排名 */}
                  {(() => {
                    const maxBugCount = bugRanking[0]?.total || 1;

                    return (
                      <div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-4">
                        <div className="mb-3 flex flex-wrap items-center justify-between gap-2">
                          <div className="text-[11px] font-medium text-[var(--ink-muted)]">Bug 数排名</div>
                          <div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-[10px] text-[var(--ink-muted)]">
                            {BUG_SEVERITY_SEGMENTS.map((segment) => (
                              <span key={segment.key} className="inline-flex items-center gap-1">
                                <span className={`h-2 w-2 rounded-sm ${segment.color}`} />
                                {segment.label}
                              </span>
                            ))}
                          </div>
                        </div>
                        {bugRanking.length === 0 ? (
                          <span className="text-[12px] text-[var(--ink-muted)]">暂无数据</span>
                        ) : (
                          <div className="space-y-2.5">
                            {bugRanking.slice(0, 8).map((item, i) => {
                              const totalWidth = (item.total / maxBugCount) * 100;

                              return (
                                <div key={item.assigneeId}>
                                  <div className="mb-1 flex items-center gap-2">
                                    <span className={`flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[10px] font-semibold ${i < 3 ? 'bg-red-50 text-red-600' : 'bg-[var(--bg-subtle)] text-[var(--ink-muted)]'}`}>{i + 1}</span>
                                    <span className="min-w-0 flex-1 truncate text-[12px] text-[var(--ink)]">{item.assigneeId}</span>
                                    <span className="shrink-0 text-[12px] font-medium tabular-nums text-[var(--ink-soft)]">{item.total} 个</span>
                                  </div>
                                  <div className="ml-7 h-2 overflow-hidden rounded-full bg-[var(--bg-subtle)]">
                                    <div className="flex h-full overflow-hidden rounded-full" style={{ width: `${totalWidth}%` }}>
                                      {BUG_SEVERITY_SEGMENTS.map((segment) => {
                                        const count = item[segment.key];
                                        if (count <= 0) return null;
                                        return (
                                          <div
                                            key={segment.key}
                                            className={`h-full ${segment.color}`}
                                            style={{ width: `${(count / item.total) * 100}%` }}
                                            title={`${segment.label} ${count} 个`}
                                          />
                                        );
                                      })}
                                    </div>
                                  </div>
                                  <div className="ml-7 mt-1 flex flex-wrap gap-x-2 gap-y-0.5 text-[10px] text-[var(--ink-muted)]">
                                    {BUG_SEVERITY_SEGMENTS.map((segment) => (
                                      <span key={segment.key}>{segment.label} {item[segment.key]}</span>
                                    ))}
                                  </div>
                                </div>
                              );
                            })}
                          </div>
                        )}
                      </div>
                    );
                  })()}
                </div>
              </div>
            );
          })()
        ) : activeTab === 'requirements' ? (
          <VersionRequirementsTab
            versionId={version.id}
            projectId={version.projectId}
            requirements={displayRequirements}
            devTasks={displayDevTasks}
            versionMembers={version.members ?? []}
            currentUserName={user?.name ?? ''}
            readOnly={versionReadonly || !requirementWriteReady}
            onLink={(ids, addedBy) => {
              if (versionReadonly || !requirementWriteReady) return;
              ids.forEach((id) => {
                const requirement = requirements.find((item) => item.id === id);
                if (!requirement) return;
                updateRequirement(id, getRequirementVersionSelectionPatch(requirement, version.id, addedBy));
              });
            }}
            onUnlink={(id) => {
              if (versionReadonly || !requirementWriteReady) return;
              const requirement = requirements.find((item) => item.id === id);
              if (!requirement) return;
              updateRequirement(id, getRequirementVersionUnlinkPatch(requirement));
            }}
            onCreateChange={(data) => {
              if (versionReadonly || !requirementWriteReady) return;
              createRequirement({
                ...data,
                productId: version.productId,
                projectId: version.projectId,
                versionId: version.id,
                sourceType: 'internal',
                sourceTarget: '',
                platforms: [],
                typeId: '',
                status: 'adopted',
                priority: 'P2',
                effort: 'M',
                creator: user?.name ?? '',
                addedToVersionBy: user?.name ?? '',
                reqType: 'change',
              } as any);
            }}
          />
        ) : (activeTab === 'research' || activeTab === 'product' || activeTab === 'ui') ? (
          (() => {
            const pt = activeTab as 'research' | 'product' | 'ui';
            const projectAdoptedReqs = getProjectAdoptedRequirementCandidates(displayRequirements, version.projectId);
            return (
              <PlanTab
                plans={displayPlans}
                versionId={version.id}
                version={version}
                versionDeadline={version.expectedReleaseDate ?? undefined}
                currentUserName={user?.name ?? ''}
                planType={pt}
                versionMembers={version.members ?? []}
                linkedRequirements={projectAdoptedReqs}
                allRequirements={displayRequirements}
                readOnly={versionReadonly || !planWriteReady}
                onCreate={(data) => {
                  if (versionReadonly || !planWriteReady) return;
                  createPlan(data);
                  if ((pt === 'product') && data.linkedRequirementIds?.length) {
                    data.linkedRequirementIds.forEach((rid) => updateRequirement(rid, { productOwner: data.owner }));
                  }
                  // 同步版本状态：计划开始时间<=今天，版本进入对应阶段
                  const today = new Date().toISOString().slice(0, 10);
                  if (data.startTime <= today && (version.status === 'planned' || version.status === 'developing')) {
                    const stageMap = { research: 'requirement', product: 'product_design', ui: 'ui_design' } as const;
                    updateVersion(version.productId, version.id, { status: 'developing', currentStage: stageMap[pt] });
                  }
                }}
                onUpdate={(id, data) => {
                  if (versionReadonly || !planWriteReady) return;
                  updatePlan(id, data);
                  if ((pt === 'product') && data.linkedRequirementIds && data.owner) {
                    data.linkedRequirementIds.forEach((rid) => updateRequirement(rid, { productOwner: data.owner }));
                  }
                }}
                onComplete={(id, result) => {
                  if (versionReadonly || !planWriteReady) return;
                  return completePlan(id, result);
                }}
                onDelete={(id) => {
                  if (versionReadonly || !planWriteReady) return;
                  deletePlan(id);
                }}
              />
            );
          })()
        ) : activeTab === 'tasks' ? (
          <DevTaskTab
            versionId={version.id}
            requirementIds={displayRequirementIds}
            versionDeadline={version.expectedReleaseDate ?? undefined}
            readOnly={versionReadonly || !devTaskWriteReady}
            versionTasks={displayDevTasks}
            versionRequirements={displayRequirements}
          />
        ) : activeTab === 'testcases' ? (
          <TestCaseTab
            versionId={version.id}
            requirementIds={displayRequirementIds}
            readOnly={versionReadonly || !testCaseWriteReady}
            versionCases={displayTestCases}
            versionBugs={displayBugs}
            versionDevTasks={displayDevTasks}
            versionRequirements={displayRequirements}
          />
        ) : activeTab === 'bugs' ? (
          <BugTab
            versionId={version.id}
            requirementIds={displayRequirementIds}
            readOnly={versionReadonly || !bugWriteReady}
            versionBugs={displayBugs}
            versionTestCases={displayTestCases}
          />
        ) : (
          <div className="rounded-xl border border-[var(--line)] bg-[var(--bg-card)] p-12 flex items-center justify-center">
            <span className="text-[13px] text-[var(--ink-muted)]">功能开发中，敬请期待</span>
          </div>
        )}
      </div>

      {showEditModal && !versionReadonly && (
        <VersionEditModal
          version={version}
          onSubmit={(data) => {
            updateVersion(version.productId, version.id, data);
            setShowEditModal(false);
          }}
          onClose={() => setShowEditModal(false)}
        />
      )}

      {showRecommendModal && !versionReadonly && (
        <MemberRecommendationModal
          groups={memberRecommendationGroups}
          members={version.members ?? []}
          onAdd={(selectedNames) => {
            const recommendations = memberRecommendationGroups.flatMap((group) => group.items);
            const newMembers = addRecommendedVersionMembers(version.members ?? [], selectedNames, recommendations);
            updateVersion(version.productId, version.id, { members: newMembers });
            setShowRecommendModal(false);
          }}
          onClose={() => setShowRecommendModal(false)}
        />
      )}

      {showReleaseModal && !versionReadonly && (
        <ReleaseVersionModal
          progress={releaseProgress}
          initialDate={formatLocalDate()}
          onSubmit={(releaseDate) => {
            updateVersion(version.productId, version.id, { status: 'released', releaseDate });
            setShowReleaseModal(false);
          }}
          onClose={() => setShowReleaseModal(false)}
        />
      )}

      {/* 参与人员设置弹窗 */}
      {showMemberModal && !versionReadonly && (
        <MemberSettingModal
          members={version.members ?? []}
          allMembers={memberCandidates}
          onSave={(newMembers) => {
            updateVersion(version.productId, version.id, { members: newMembers });
            setShowMemberModal(false);
          }}
          onClose={() => setShowMemberModal(false)}
        />
      )}
    </div>
  );
}

const PRIORITY_OPTIONS: Priority[] = ['P0', 'P1', 'P2', 'P3', 'P4'];

function toDateInputValue(value?: string | null): string {
  if (!value) return '';
  return value.includes('T') ? value.slice(0, 10) : value;
}

function VersionEditModal({ version, onSubmit, onClose }: {
  version: VersionWithContext;
  onSubmit: (data: { name: string; priority: Priority; expectedReleaseDate: string }) => void;
  onClose: () => void;
}) {
  const [versionNumber, setVersionNumber] = useState(() => getEditableVersionNumber(version.projectName, version.name));
  const [priority, setPriority] = useState<Priority>((version.priority ?? 'P2') as Priority);
  const [developmentType, setDevelopmentType] = useState<VersionDevelopmentType | ''>('');
  const [productDesignCompleted, setProductDesignCompleted] = useState(false);
  const [expectedReleaseDate, setExpectedReleaseDate] = useState(() => toDateInputValue(version.expectedReleaseDate));
  const [submitting, setSubmitting] = useState(false);
  const selectedDevelopmentType = developmentType ? getVersionDevelopmentTypeOption(developmentType) : undefined;
  const recommendedWorkDays = selectedDevelopmentType
    ? getRecommendedWorkDays(selectedDevelopmentType, { productDesignCompleted })
    : 0;
  const canSubmit = canSubmitVersionEditForm({ versionNumber, expectedReleaseDate, submitting });

  const handleVersionInput = (value: string) => {
    setVersionNumber(value.replace(/[^\d.]/g, ''));
  };

  const applyRecommendedReleaseDate = (type: VersionDevelopmentType, productDesigned: boolean) => {
    setExpectedReleaseDate(getRecommendedExpectedReleaseDate(type, new Date(), { productDesignCompleted: productDesigned }));
  };

  const handleDevelopmentTypeChange = (type: VersionDevelopmentType) => {
    setDevelopmentType(type);
    applyRecommendedReleaseDate(type, productDesignCompleted);
  };

  const handleProductDesignCompletedChange = (completed: boolean) => {
    setProductDesignCompleted(completed);
    if (developmentType) applyRecommendedReleaseDate(developmentType, completed);
  };

  const handleSubmit = (event: FormEvent) => {
    event.preventDefault();
    if (!canSubmit) return;
    setSubmitting(true);
    onSubmit({
      name: buildVersionName(version.projectName, versionNumber),
      priority,
      expectedReleaseDate,
    });
  };

  return (
    <div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/40 px-4" onClick={onClose}>
      <form
        onSubmit={handleSubmit}
        className="max-h-[90vh] w-full max-w-lg overflow-y-auto rounded-2xl border border-[var(--line)] bg-[var(--bg-card)] shadow-md"
        onClick={(event) => event.stopPropagation()}
      >
        <div className="flex items-start justify-between gap-4 border-b border-[var(--line)] px-5 py-4">
          <div>
            <h3 className="text-[15px] font-semibold text-[var(--ink)]">编辑版本</h3>
            <p className="mt-1 text-[11px] text-[var(--ink-muted)]">
              {version.productName} / {version.projectName}
            </p>
          </div>
          <button type="button" onClick={onClose} className="rounded-md p-1.5 text-[var(--ink-muted)] hover:bg-[var(--bg-subtle)] hover:text-[var(--ink)]">
            <X className="h-4 w-4" />
          </button>
        </div>

        <div className="space-y-4 px-5 py-4">
          <label className="block">
            <span className="mb-1.5 block text-[12px] font-medium text-[var(--ink-soft)]">版本号<span className="text-red-500">*</span></span>
            <input
              value={versionNumber}
              onChange={(event) => handleVersionInput(event.target.value)}
              placeholder="1.0"
              className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg)] px-3 text-[13px] tabular-nums text-[var(--ink)] outline-none focus:border-[var(--accent)]"
            />
            {version.projectName && versionNumber && (
              <p className="mt-1.5 text-[11px] text-[var(--ink-muted)]">保存后版本名：<span className="font-medium text-[var(--ink-soft)]">{buildVersionName(version.projectName, versionNumber)}</span></p>
            )}
          </label>

          <div>
            <div className="mb-1.5 text-[12px] font-medium text-[var(--ink-soft)]">优先级</div>
            <div className="grid grid-cols-5 gap-2">
              {PRIORITY_OPTIONS.map((item) => (
                <button
                  key={item}
                  type="button"
                  onClick={() => setPriority(item)}
                  className={`h-9 rounded-lg border px-3 text-[12px] font-medium transition-colors ${priority === item ? 'border-[var(--accent)] bg-[var(--accent-soft)] text-[var(--accent)]' : 'border-[var(--line)] bg-[var(--bg-card)] text-[var(--ink-soft)] hover:border-[var(--accent)]'}`}
                >
                  {item}
                </button>
              ))}
            </div>
          </div>

          <div>
            <div className="mb-1.5 text-[12px] font-medium text-[var(--ink-soft)]">项目开发类型</div>
            <div className="grid grid-cols-2 gap-2">
              {VERSION_DEVELOPMENT_TYPE_OPTIONS.map((option) => (
                <button
                  key={option.key}
                  type="button"
                  onClick={() => handleDevelopmentTypeChange(option.key)}
                  className={`rounded-lg border px-3 py-2 text-left transition-colors ${developmentType === option.key ? 'border-[var(--accent)] bg-[var(--accent-soft)] text-[var(--accent)]' : 'border-[var(--line)] bg-[var(--bg-card)] text-[var(--ink-soft)] hover:border-[var(--accent)]'}`}
                >
                  <span className="block text-[12px] font-medium">{option.label}</span>
                  <span className="mt-0.5 block text-[10px] text-[var(--ink-muted)]">{option.description}</span>
                </button>
              ))}
            </div>
          </div>

          <div>
            <div className="mb-1.5 text-[12px] font-medium text-[var(--ink-soft)]">产品设计是否已完成</div>
            <div className="grid grid-cols-2 gap-2">
              <button
                type="button"
                onClick={() => handleProductDesignCompletedChange(false)}
                className={`h-9 rounded-lg border px-3 text-[12px] font-medium transition-colors ${!productDesignCompleted ? 'border-[var(--accent)] bg-[var(--accent-soft)] text-[var(--accent)]' : 'border-[var(--line)] bg-[var(--bg-card)] text-[var(--ink-soft)] hover:border-[var(--accent)]'}`}
              >
                否，计入产品设计
              </button>
              <button
                type="button"
                onClick={() => handleProductDesignCompletedChange(true)}
                className={`h-9 rounded-lg border px-3 text-[12px] font-medium transition-colors ${productDesignCompleted ? 'border-[var(--accent)] bg-[var(--accent-soft)] text-[var(--accent)]' : 'border-[var(--line)] bg-[var(--bg-card)] text-[var(--ink-soft)] hover:border-[var(--accent)]'}`}
              >
                是，只算开发测试
              </button>
            </div>
          </div>

          <label className="block">
            <span className="mb-1.5 flex items-center gap-1.5 text-[12px] font-medium text-[var(--ink-soft)]">
              <Calendar className="h-3.5 w-3.5" />
              期望发版日期<span className="text-red-500">*</span>
            </span>
            <input
              type="date"
              value={expectedReleaseDate}
              onChange={(event) => setExpectedReleaseDate(event.target.value)}
              required
              className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg)] px-3 text-[13px] text-[var(--ink)] outline-none focus:border-[var(--accent)]"
            />
            {selectedDevelopmentType && (
              <p className="mt-1.5 text-[11px] leading-5 text-[var(--ink-muted)]">
                推荐周期 {recommendedWorkDays} 个工作日：
                {selectedDevelopmentType.stages.map((stage, index) => {
                  const skipped = productDesignCompleted && stage.key === 'product_design';
                  return (
                    <span key={stage.key}>
                      {index > 0 && ' + '}
                      <span className={skipped ? 'text-[var(--ink-muted)] line-through' : ''}>
                        {stage.label}{stage.workDays}天
                      </span>
                    </span>
                  );
                })}
              </p>
            )}
          </label>
        </div>

        <div className="flex items-center justify-end gap-2 border-t border-[var(--line)] px-5 py-4">
          <button type="button" onClick={onClose} className="h-8 rounded-md border border-[var(--line)] px-3 text-[12px] font-medium text-[var(--ink-soft)] hover:bg-[var(--bg-subtle)]">
            取消
          </button>
          <button type="submit" disabled={!canSubmit} className="h-8 rounded-md bg-[var(--accent)] px-4 text-[12px] font-semibold text-white hover:bg-[var(--accent-hover)] disabled:cursor-not-allowed disabled:opacity-50">
            {submitting ? '保存中' : '保存'}
          </button>
        </div>
      </form>
    </div>
  );
}

function ReleaseVersionModal({ progress, initialDate, onSubmit, onClose }: {
  progress: number;
  initialDate: string;
  onSubmit: (releaseDate: string) => void;
  onClose: () => void;
}) {
  const [releaseDate, setReleaseDate] = useState(initialDate);
  const warning = getReleaseProgressWarning(progress);
  const canSubmit = canSubmitReleaseForm(releaseDate);

  const handleSubmit = (event: FormEvent) => {
    event.preventDefault();
    if (!canSubmit) return;
    onSubmit(releaseDate.trim());
  };

  return (
    <div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/40 px-4" onClick={onClose}>
      <form
        onSubmit={handleSubmit}
        className="w-full max-w-md rounded-2xl border border-[var(--line)] bg-[var(--bg-card)] shadow-md"
        onClick={(event) => event.stopPropagation()}
      >
        <div className="flex items-start justify-between gap-4 border-b border-[var(--line)] px-5 py-4">
          <div>
            <h3 className="text-[15px] font-semibold text-[var(--ink)]">发版</h3>
            <p className="mt-1 text-[11px] text-[var(--ink-muted)]">填写实际发版日期后，版本状态会更新为已发布。</p>
          </div>
          <button type="button" onClick={onClose} className="rounded-md p-1.5 text-[var(--ink-muted)] hover:bg-[var(--bg-subtle)] hover:text-[var(--ink)]">
            <X className="h-4 w-4" />
          </button>
        </div>

        <div className="space-y-4 px-5 py-4">
          {warning ? (
            <div className="flex gap-2 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2.5 text-[12px] leading-5 text-amber-800">
              <AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" />
              <span>{warning}</span>
            </div>
          ) : (
            <div className="rounded-xl border border-emerald-100 bg-emerald-50 px-3 py-2.5 text-[12px] font-medium text-emerald-700">
              当前版本进度 100%，可记录实际发版日期。
            </div>
          )}

          <label className="block">
            <span className="mb-1.5 flex items-center gap-1.5 text-[12px] font-medium text-[var(--ink-soft)]">
              <Calendar className="h-3.5 w-3.5" />
              实际发版日期
            </span>
            <input
              type="date"
              value={releaseDate}
              onChange={(event) => setReleaseDate(event.target.value)}
              className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg)] px-3 text-[13px] outline-none focus:border-[var(--accent)]"
              required
            />
          </label>
        </div>

        <div className="flex items-center justify-end gap-2 border-t border-[var(--line)] px-5 py-4">
          <button type="button" onClick={onClose} className="h-8 rounded-md border border-[var(--line)] px-3 text-[12px] font-medium text-[var(--ink-soft)] hover:bg-[var(--bg-subtle)]">
            取消
          </button>
          <button type="submit" disabled={!canSubmit} className="h-8 rounded-md bg-emerald-600 px-4 text-[12px] font-semibold text-white hover:bg-emerald-700 disabled:cursor-not-allowed disabled:opacity-50">
            确认发版
          </button>
        </div>
      </form>
    </div>
  );
}

function OverviewMetric({ label, value, icon, sub, tone }: {
  label: string;
  value: string | number;
  icon?: JSX.Element;
  sub?: string;
  tone?: 'accent' | 'orange';
}) {
  const valueColor = tone === 'orange' ? 'text-orange-600' : tone === 'accent' ? 'text-[var(--accent)]' : 'text-[var(--ink)]';
  return (
    <div className="min-w-0 border-b border-r border-[var(--line-soft)] p-3.5">
      <div className="flex items-center gap-1.5 text-[11px] font-medium text-[var(--ink-muted)]">
        {icon && <span className="shrink-0 text-[var(--ink-muted)]">{icon}</span>}
        <span>{label}</span>
      </div>
      <div className={`mt-1 truncate text-[14px] font-semibold tabular-nums ${valueColor}`} title={String(value)}>
        {value}
      </div>
      {sub && <div className="mt-1 truncate text-[10px] text-[var(--ink-muted)]">{sub}</div>}
    </div>
  );
}

function formatRecommendationHours(hours: number): string {
  if (hours <= 0) return '0h';
  return `${Math.round(hours * 10) / 10}h`;
}

function formatAvailableDays(days: number): string {
  if (days <= 0) return '今天';
  return `约${days}天`;
}

function getRecommendationScoreClass(score: number): string {
  if (score >= 80) return 'bg-emerald-50 text-emerald-700 border-emerald-100';
  if (score >= 65) return 'bg-blue-50 text-blue-700 border-blue-100';
  if (score >= 50) return 'bg-amber-50 text-amber-700 border-amber-100';
  return 'bg-red-50 text-red-700 border-red-100';
}

function getRecommendationBarClass(score: number): string {
  if (score >= 80) return 'bg-emerald-500';
  if (score >= 65) return 'bg-blue-500';
  if (score >= 50) return 'bg-amber-500';
  return 'bg-red-500';
}

function getInitialRecommendationSelection(groups: MemberRecommendationGroup[]): Set<string> {
  return new Set(getDefaultRecommendedMemberNames(groups));
}

function MemberRecommendationModal({ groups, members, onAdd, onClose }: {
  groups: MemberRecommendationGroup[];
  members: { role: Role; name: string }[];
  onAdd: (selectedNames: string[]) => void;
  onClose: () => void;
}) {
  const allItems = groups.flatMap((group) => group.items);
  const [selectedNames, setSelectedNames] = useState<Set<string>>(() => getInitialRecommendationSelection(groups));
  const hasHistoricalSignal = allItems.some((item) => item.confidence === 'high');

  const toggleName = (name: string) => {
    setSelectedNames((prev) => {
      const next = new Set(prev);
      if (next.has(name)) next.delete(name);
      else next.add(name);
      return next;
    });
  };

  const handleAdd = () => {
    if (selectedNames.size === 0) return;
    onAdd(Array.from(selectedNames));
  };

  return (
    <div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/40" onClick={onClose}>
      <div className="flex max-h-[calc(100vh-48px)] w-[min(860px,calc(100vw-32px))] flex-col overflow-hidden rounded-2xl border border-[var(--line)] bg-[var(--bg-card)] shadow-2xl" onClick={(e) => e.stopPropagation()}>
        <div className="flex items-start justify-between gap-4 border-b border-[var(--line)] px-5 py-4">
          <div className="min-w-0">
            <div className="flex flex-wrap items-center gap-2">
              <h3 className="text-[15px] font-semibold text-[var(--ink)]">AI推荐参与人员</h3>
              {!hasHistoricalSignal && allItems.length > 0 && (
                <span className="rounded-full border border-amber-100 bg-amber-50 px-2 py-0.5 text-[10px] font-medium text-amber-700">历史数据待导入</span>
              )}
            </div>
            <p className="mt-1 text-[11px] text-[var(--ink-muted)]">
              当前 {members.length} 人已加入，推荐 {allItems.length} 人，已选 {selectedNames.size} 人
            </p>
          </div>
          <button onClick={onClose} className="rounded-md p-1.5 text-[var(--ink-muted)] hover:bg-[var(--bg-subtle)] hover:text-[var(--ink)]"><X className="h-4 w-4" /></button>
        </div>

        <div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
          {allItems.length === 0 ? (
            <div className="flex min-h-[260px] flex-col items-center justify-center rounded-xl border border-dashed border-[var(--line)] bg-[var(--bg)] text-center">
              <Sparkles className="mb-3 h-5 w-5 text-[var(--ink-muted)]" />
              <div className="text-[13px] font-medium text-[var(--ink)]">暂无可推荐人员</div>
              <div className="mt-1 text-[11px] text-[var(--ink-muted)]">当前推荐缺口已满足，或暂无符合前端、后端、测试部门且未加入版本的候选人。</div>
            </div>
          ) : (
            <div className="space-y-4">
              {groups.map((group) => (
                <section key={group.role}>
                  <div className="mb-2 flex items-center justify-between">
                    <div className="flex items-center gap-2">
                      <span className="text-[12px] font-semibold text-[var(--ink)]">{RECOMMENDATION_ROLE_LABEL[group.role]}</span>
                      <span className="rounded-full bg-[var(--bg-subtle)] px-2 py-0.5 text-[10px] text-[var(--ink-muted)]">
                        建议 {group.requiredCount} / 已有 {group.currentCount} / 缺 {group.missingCount}
                      </span>
                    </div>
                    <span className="text-[10px] text-[var(--ink-muted)]">范围 {formatRecommendationHours(group.scopeHours)}</span>
                  </div>
                  <div className="space-y-2">
                    {group.items.map((item) => {
                      const selected = selectedNames.has(item.name);
                      const workloadCount = item.metrics.activeTaskCount + item.metrics.activeBugCount;
                      const stats = [
                        { label: '剩余', value: formatRecommendationHours(item.metrics.remainingHours) },
                        { label: '可接入', value: formatAvailableDays(item.metrics.availableInDays) },
                        { label: '事项', value: `${workloadCount}项` },
                        { label: '加班', value: formatRecommendationHours(item.metrics.recentOvertimeHours) },
                      ];

                      return (
                        <button
                          key={item.name}
                          type="button"
                          onClick={() => toggleName(item.name)}
                          className={`w-full overflow-hidden rounded-xl border text-left transition-colors ${selected ? 'border-violet-300 bg-violet-50/60' : 'border-[var(--line)] bg-[var(--bg)] hover:border-violet-200 hover:bg-[var(--bg-subtle)]'}`}
                        >
                          <div className={`h-1 ${getRecommendationBarClass(item.score)}`} style={{ width: `${item.score}%` }} />
                          <div className="flex gap-3 px-3 py-3">
                            <span className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border ${selected ? 'border-violet-600 bg-violet-600 text-white' : 'border-[var(--line)] text-transparent'}`}>
                              <Check className="h-3 w-3" />
                            </span>
                            <div className="min-w-0 flex-1">
                              <div className="flex flex-wrap items-center gap-2">
                                <span className="truncate text-[13px] font-semibold text-[var(--ink)]">{item.name}</span>
                                <span className={`rounded-full border px-2 py-0.5 text-[10px] font-semibold tabular-nums ${getRecommendationScoreClass(item.score)}`}>{item.score}分</span>
                                <span className="rounded-full bg-[var(--bg-card)] px-2 py-0.5 text-[10px] text-[var(--ink-muted)]">{RECOMMENDATION_CONFIDENCE_LABEL[item.confidence]}</span>
                              </div>
                              <div className="mt-2 grid grid-cols-2 gap-2 sm:grid-cols-4">
                                {stats.map((stat) => (
                                  <span key={stat.label} className="rounded-lg bg-[var(--bg-card)] px-2 py-1">
                                    <span className="block text-[10px] text-[var(--ink-muted)]">{stat.label}</span>
                                    <span className="block text-[11px] font-semibold text-[var(--ink)] tabular-nums">{stat.value}</span>
                                  </span>
                                ))}
                              </div>
                              {(item.reasons.length > 0 || item.warnings.length > 0 || typeof item.metrics.bugRate === 'number') && (
                                <div className="mt-2 flex flex-wrap gap-1.5">
                                  {item.reasons.slice(0, 2).map((reason) => (
                                    <span key={reason} className="rounded-full bg-emerald-50 px-2 py-0.5 text-[10px] text-emerald-700">{reason}</span>
                                  ))}
                                  {typeof item.metrics.bugRate === 'number' && (
                                    <span className="rounded-full bg-blue-50 px-2 py-0.5 text-[10px] text-blue-700">Bug率 {item.metrics.bugRate}</span>
                                  )}
                                  {item.warnings.slice(0, 2).map((warning) => (
                                    <span key={warning} className="inline-flex items-center gap-1 rounded-full bg-amber-50 px-2 py-0.5 text-[10px] text-amber-700">
                                      <AlertTriangle className="h-3 w-3" />
                                      {warning}
                                    </span>
                                  ))}
                                </div>
                              )}
                            </div>
                          </div>
                        </button>
                      );
                    })}
                  </div>
                </section>
              ))}
            </div>
          )}
        </div>

        <div className="flex items-center justify-between gap-3 border-t border-[var(--line)] px-5 py-4">
          <button
            type="button"
            onClick={() => setSelectedNames(new Set())}
            disabled={selectedNames.size === 0}
            className="h-8 rounded-lg border border-[var(--line)] px-3 text-[12px] font-medium text-[var(--ink-soft)] hover:bg-[var(--bg-subtle)] disabled:cursor-not-allowed disabled:opacity-45"
          >
            清空选择
          </button>
          <div className="flex items-center gap-2">
            <button onClick={onClose} className="h-8 rounded-lg border border-[var(--line)] px-3 text-[12px] font-medium text-[var(--ink-soft)] hover:bg-[var(--bg-subtle)]">取消</button>
            <button onClick={handleAdd} disabled={selectedNames.size === 0} className="inline-flex h-8 items-center gap-1.5 rounded-lg bg-violet-600 px-4 text-[12px] font-medium text-white disabled:cursor-not-allowed disabled:opacity-45">
              <UserPlus className="h-3.5 w-3.5" />
              添加 {selectedNames.size} 人
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

function MemberSettingModal({ members, allMembers, onSave, onClose }: {
  members: { role: Role; name: string }[];
  allMembers: { id: string; name: string; departmentName?: string }[];
  onSave: (members: { role: Role; name: string }[]) => void;
  onClose: () => void;
}) {
  const [list, setList] = useState<{ role: Role; name: string }[]>([...members]);
  const [query, setQuery] = useState('');
  const [selectedNames, setSelectedNames] = useState<Set<string>>(new Set());
  const roleOptions: { value: Role; label: string }[] = [
    { value: 'product', label: '产品' },
    { value: 'ui', label: 'UI' },
    { value: 'frontend', label: '前端' },
    { value: 'backend', label: '后端' },
    { value: 'testing', label: '测试' },
  ];

  const candidates = useMemo(
    () => filterVersionMemberCandidates(allMembers, list, query),
    [allMembers, list, query],
  );

  const toggleCandidate = (name: string) => {
    setSelectedNames((prev) => {
      const next = new Set(prev);
      if (next.has(name)) next.delete(name);
      else next.add(name);
      return next;
    });
  };

  const handleSelectAll = () => {
    setSelectedNames((prev) => {
      const next = new Set(prev);
      candidates.forEach((member) => next.add(member.name));
      return next;
    });
  };

  const handleAddSelected = () => {
    if (selectedNames.size === 0) return;
    setList(addVersionMembers(list, selectedNames, allMembers, DEFAULT_VERSION_MEMBER_ROLE));
    setSelectedNames(new Set());
    setQuery('');
  };

  const handleRoleChange = (name: string, role: Role) => {
    setList(list.map((member) => (member.name === name ? { ...member, role } : member)));
  };

  const handleRemove = (name: string) => {
    setList(list.filter((member) => member.name !== name));
    setSelectedNames((prev) => {
      const next = new Set(prev);
      next.delete(name);
      return next;
    });
  };

  return (
    <div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/40" onClick={onClose}>
      <div className="flex max-h-[calc(100vh-48px)] w-[min(960px,calc(100vw-32px))] flex-col overflow-hidden rounded-2xl border border-[var(--line)] bg-[var(--bg-card)] shadow-2xl" onClick={(e) => e.stopPropagation()}>
        <div className="flex items-center justify-between border-b border-[var(--line)] px-6 py-4">
          <div>
            <h3 className="text-[15px] font-semibold text-[var(--ink)]">设置参与人员</h3>
            <p className="mt-1 text-[11px] text-[var(--ink-muted)]">{list.length} 人已加入版本</p>
          </div>
          <button onClick={onClose} className="rounded-md p-1.5 text-[var(--ink-muted)] hover:bg-[var(--bg-subtle)] hover:text-[var(--ink)]"><X className="h-4 w-4" /></button>
        </div>

        <div className="grid min-h-0 flex-1 grid-cols-1 gap-4 overflow-y-auto p-5 lg:grid-cols-[minmax(0,1fr)_360px]">
          <section className="flex min-h-[420px] flex-col overflow-hidden rounded-xl border border-[var(--line)] bg-[var(--bg)]">
            <div className="border-b border-[var(--line)] p-4">
              <div className="mb-3 flex items-center justify-between gap-3">
                <div className="text-[12px] font-semibold text-[var(--ink)]">添加成员</div>
                <button
                  type="button"
                  onClick={handleAddSelected}
                  disabled={selectedNames.size === 0}
                  className="inline-flex h-8 items-center gap-1.5 rounded-lg bg-[var(--accent)] px-3 text-[12px] font-medium text-white disabled:cursor-not-allowed disabled:opacity-45"
                >
                  <UserPlus className="h-3.5 w-3.5" />
                  {selectedNames.size > 0 ? `添加 ${selectedNames.size} 人` : '添加'}
                </button>
              </div>
              <div className="relative">
                <Search className="pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-[var(--ink-muted)]" />
                <input
                  value={query}
                  onChange={(e) => setQuery(e.target.value)}
                  placeholder="搜索成员"
                  className="h-9 w-full rounded-lg border border-[var(--line)] bg-[var(--bg-card)] pl-9 pr-3 text-[13px] text-[var(--ink)] outline-none transition-colors placeholder:text-[var(--ink-muted)] focus:border-[var(--accent)]"
                />
              </div>
              <div className="mt-2 flex items-center justify-between gap-3 text-[11px] text-[var(--ink-muted)]">
                <span>可添加 {candidates.length} 人</span>
                <div className="flex items-center gap-2">
                  <button type="button" onClick={handleSelectAll} disabled={candidates.length === 0} className="text-[var(--accent)] hover:underline disabled:cursor-not-allowed disabled:text-[var(--ink-muted)] disabled:no-underline">全选当前</button>
                  <button type="button" onClick={() => setSelectedNames(new Set())} disabled={selectedNames.size === 0} className="hover:text-[var(--ink-soft)] disabled:cursor-not-allowed disabled:text-[var(--ink-muted)]">清空</button>
                </div>
              </div>
            </div>
            <div className="min-h-0 flex-1 overflow-y-auto p-3">
              {candidates.length === 0 ? (
                <div className="flex h-full min-h-[180px] items-center justify-center rounded-lg border border-dashed border-[var(--line)] text-[12px] text-[var(--ink-muted)]">暂无可添加成员</div>
              ) : (
                <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
                  {candidates.map((member) => {
                    const selected = selectedNames.has(member.name);
                    return (
                      <button
                        key={member.id}
                        type="button"
                        onClick={() => toggleCandidate(member.name)}
                        className={`flex h-12 items-center justify-between rounded-lg border px-3 text-left transition-colors ${selected ? 'border-[var(--accent)] bg-[var(--accent-soft)] text-[var(--accent)]' : 'border-[var(--line)] bg-[var(--bg-card)] text-[var(--ink-soft)] hover:border-[var(--accent)] hover:bg-[var(--bg-subtle)]'}`}
                      >
                        <span className="min-w-0 flex-1">
                          <span className="block truncate text-[12px] font-medium">{member.name}</span>
                          {member.departmentName && <span className="mt-0.5 block truncate text-[10px] text-[var(--ink-muted)]">{member.departmentName}</span>}
                        </span>
                        <span className={`flex h-4 w-4 shrink-0 items-center justify-center rounded border ${selected ? 'border-[var(--accent)] bg-[var(--accent)] text-white' : 'border-[var(--line)] text-transparent'}`}>
                          <Check className="h-3 w-3" />
                        </span>
                      </button>
                    );
                  })}
                </div>
              )}
            </div>
          </section>

          <section className="flex min-h-[420px] flex-col overflow-hidden rounded-xl border border-[var(--line)] bg-[var(--bg)]">
            <div className="flex items-center justify-between border-b border-[var(--line)] p-4">
              <div className="text-[12px] font-semibold text-[var(--ink)]">已添加</div>
              <span className="rounded-full bg-[var(--bg-subtle)] px-2 py-0.5 text-[11px] tabular-nums text-[var(--ink-muted)]">{list.length} 人</span>
            </div>
            <div className="min-h-0 flex-1 overflow-y-auto p-3">
              {list.length === 0 ? (
                <div className="flex h-full min-h-[180px] items-center justify-center rounded-lg border border-dashed border-[var(--line)] text-[12px] text-[var(--ink-muted)]">暂无成员</div>
              ) : (
                <div className="space-y-2">
                  {list.map((member) => (
                    <div key={member.name} className="flex items-center gap-2 rounded-lg border border-[var(--line)] bg-[var(--bg-card)] px-3 py-2">
                      <span className="min-w-0 flex-1 truncate text-[13px] font-medium text-[var(--ink)]">{member.name}</span>
                      <FilterSelect
                        value={member.role}
                        onChange={(value) => handleRoleChange(member.name, value as Role)}
                        options={roleOptions}
                        showAllOption={false}
                        className="w-24 shrink-0"
                      />
                      <button type="button" onClick={() => handleRemove(member.name)} className="rounded-md p-1 text-red-400 hover:bg-red-50 hover:text-red-600">
                        <X className="h-3.5 w-3.5" />
                      </button>
                    </div>
                  ))}
                </div>
              )}
            </div>
          </section>
        </div>

        <div className="flex justify-end gap-2 border-t border-[var(--line)] px-6 py-4">
          <button onClick={onClose} className="h-8 rounded-lg border border-[var(--line)] px-3 text-[12px] font-medium text-[var(--ink-soft)] hover:bg-[var(--bg-subtle)]">取消</button>
          <button onClick={() => onSave(list)} className="h-8 rounded-lg bg-[var(--accent)] px-4 text-[12px] font-medium text-white">保存</button>
        </div>
      </div>
    </div>
  );
}
