import { useState, useRef } from 'react'
import { usePageSize } from '../../hooks/usePageSize'
import { toast } from 'sonner'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useConfirm } from '../../hooks/useConfirm'
import { Calculator, AlertCircle, Info, Upload, Layers, Settings as SettingsIcon, Archive, Plus, Trash2, AlertTriangle, Download, FileText, X, ChevronLeft, Clock, Users, TrendingDown, TrendingUp, BadgeCheck, DollarSign, CalendarCheck } from 'lucide-react'
import { Stepper, type Step } from '../../components/ui/Stepper'
import { InlineAlert } from '../../components/ui/InlineAlert'
import PageGuide from '../../components/ui/PageGuide'
import { payrollApi, rosterApi, employeeApi } from '../../lib/api-services'
import { useAuthStore } from '../../store/authStore'
import Card from '../../components/ui/Card'
import Button from '../../components/ui/Button'
import { Input, Label, Select } from '../../components/ui/Input'
import EmptyState from '../../components/ui/EmptyState'
import Pagination from '../../components/ui/Pagination'
// 金额格式化：保留两位小数 + 千分位
const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })

// ========== 发薪批次管理 ==========

function CustomEmployeeSelector({ selectedIds, onChange }: { selectedIds: string[]; onChange: (ids: string[]) => void }) {
  const [search, setSearch] = useState('')
  const [filterDept, setFilterDept] = useState('')

  const { data: employees } = useQuery<any>({
    queryKey: ['roster-for-batch', search, filterDept],
    queryFn: async () => {
      const res = await rosterApi.list({ pageSize: 999, search, department: filterDept, status: 'ACTIVE' })
      return res.data || []
    },
  })

  const { data: deptList } = useQuery<string[]>({
    queryKey: ['roster-departments'],
    queryFn: () => rosterApi.departments(),
  })

  const toggle = (id: string) => {
    if (selectedIds.includes(id)) {
      onChange(selectedIds.filter(x => x !== id))
    } else {
      onChange([...selectedIds, id])
    }
  }

  const toggleAll = () => {
    if (employees && employees.every((e: any) => selectedIds.includes(e.id))) {
      onChange(selectedIds.filter(id => !employees.some((e: any) => e.id === id)))
    } else {
      const newIds = new Set([...selectedIds, ...(employees?.map((e: any) => e.id) || [])])
      onChange(Array.from(newIds))
    }
  }

  return (
    <div className="border rounded-md p-3 space-y-2">
      <div className="flex items-center justify-between">
        <Label>选择发薪人员</Label>
        <span className="text-xs text-gray-500">已选 {selectedIds.length} 人</span>
      </div>
      <div className="flex gap-2">
        <Input placeholder="搜索姓名" value={search} onChange={(e) => setSearch(e.target.value)} className="!w-40" />
        <select value={filterDept} onChange={(e) => setFilterDept(e.target.value)} className="h-9 rounded-md border border-gray-200 bg-white px-3 text-sm">
          <option value="">全部部门</option>
          {deptList?.map((d: string) => <option key={d} value={d}>{d}</option>)}
        </select>
        {employees && employees.length > 0 && (
          <button onClick={toggleAll} className="text-xs text-primary hover:underline whitespace-nowrap">
            {employees.every((e: any) => selectedIds.includes(e.id)) ? '取消全选' : '全选当前'}
          </button>
        )}
      </div>
      <div className="max-h-48 overflow-y-auto border rounded">
        {employees && employees.length > 0 ? (
          <table className="w-full text-xs">
            <tbody>
              {employees.map((e: any) => (
                <tr key={e.id} className="border-b last:border-0 hover:bg-gray-50 cursor-pointer" onClick={() => toggle(e.id)}>
                  <td className="px-2 py-1.5 w-8">
                    <input type="checkbox" checked={selectedIds.includes(e.id)} onChange={() => toggle(e.id)} />
                  </td>
                  <td className="px-2 py-1.5 font-medium">{e.name}</td>
                  <td className="px-2 py-1.5 text-gray-500">{e.department}</td>
                  <td className="px-2 py-1.5 text-gray-400 text-right">¥{fmt(e.monthlySalary)}</td>
                </tr>
              ))}
            </tbody>
          </table>
        ) : (
          <div className="py-4 text-center text-gray-400 text-xs">暂无员工</div>
        )}
      </div>
    </div>
  )
}

export function BatchManager() {
  const queryClient = useQueryClient()
  const confirm = useConfirm()
  const [month, setMonth] = useState(new Date().toISOString().slice(0, 7))
  const [monthFrom, setMonthFrom] = useState('')
  const [monthTo, setMonthTo] = useState('')
  const [dateFrom, setDateFrom] = useState('')
  const [dateTo, setDateTo] = useState('')
  const [filterStatus, setFilterStatus] = useState<string>('')
  const [filterType, setFilterType] = useState<string>('')
  const [selectedBatchId, setSelectedBatchId] = useState<string | null>(null)
  const [showCreateModal, setShowCreateModal] = useState(false)
  const [createMonth, setCreateMonth] = useState(new Date().toISOString().slice(0, 7))
  const [createPayMonth, setCreatePayMonth] = useState('')
  const [createType, setCreateType] = useState<'REGULAR' | 'TERMINATION' | 'BONUS' | 'SEVERANCE'>('REGULAR')
  const [createMode, setCreateMode] = useState<'copy_last' | 'blank_employees' | 'blank_all' | 'copy_batch' | 'custom'>('copy_last')
  const [sourceBatchId, setSourceBatchId] = useState<string>('')
  const [selectedEmployeeIds, setSelectedEmployeeIds] = useState<string[]>([])
  const pageSize = usePageSize()
  const [page, setPage] = useState(1)

  const { data: checkResult } = useQuery<any>({
    queryKey: ['batch-check', month],
    queryFn: async () => {
      return await payrollApi.batchCheck(month)
    },
  })

  const { data: batches, isLoading } = useQuery<any[]>({
    queryKey: ['batches', month, monthFrom, monthTo, dateFrom, dateTo, filterStatus, filterType],
    queryFn: async () => {
      const params: any = {}
      if (month && !monthFrom && !monthTo && !dateFrom && !dateTo) params.month = month
      if (monthFrom) params.monthFrom = monthFrom
      if (monthTo) params.monthTo = monthTo
      if (dateFrom) params.dateFrom = dateFrom
      if (dateTo) params.dateTo = dateTo
      if (filterStatus) params.status = filterStatus
      if (filterType) params.type = filterType
      return await payrollApi.batches(params)
    },
  })

  const { data: archivedBatches } = useQuery<any[]>({
    queryKey: ['archived-batches'],
    queryFn: async () => {
      return await payrollApi.archivedBatches()
    },
    enabled: createMode === 'copy_batch',
  })

  const createMutation = useMutation({
    mutationFn: (data: any) => payrollApi.createBatch(data),
    onSuccess: (_data: any, vars: any) => {
      // 自动切换列表筛选到批次所属月，确保新批次可见
      if (vars.month && vars.month !== month) {
        setMonth(vars.month)
      }
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      queryClient.invalidateQueries({ queryKey: ['batch-check'] })
      setShowCreateModal(false)
      toast.success('发薪批次已创建')
    },
    onError: (err: any) => toast.error(err?.response?.data?.error?.message || '创建失败'),
  })

  const deleteBatchMutation = useMutation({
    mutationFn: (batchId: string) => payrollApi.removeBatch(batchId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      queryClient.invalidateQueries({ queryKey: ['batch-check'] })
      toast.success('批次已删除')
    },
    onError: () => toast.error('删除失败'),
  })

  const renameBatchMutation = useMutation({
    mutationFn: ({ batchId, name }: { batchId: string; name: string }) =>
      payrollApi.renameBatch(batchId, name),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      toast.success('批次名称已更新')
    },
    onError: () => toast.error('重命名失败'),
  })

  const unarchiveBatchMutation = useMutation({
    mutationFn: (batchId: string) => payrollApi.unarchiveBatch(batchId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      queryClient.invalidateQueries({ queryKey: ['batch-check'] })
      queryClient.invalidateQueries({ queryKey: ['dashboard'] })
      toast.success('已取消归档，批次恢复为草稿状态')
    },
    onError: (err: any) => toast.error(err?.response?.data?.error?.message || '取消归档失败'),
  })

  const [renamingId, setRenamingId] = useState<string | null>(null)
  const [renameValue, setRenameValue] = useState('')

  if (selectedBatchId) {
    return <BatchDetail batchId={selectedBatchId} onBack={() => {
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      queryClient.invalidateQueries({ queryKey: ['batch-check'] })
      setSelectedBatchId(null)
    }} />
  }

  return (
    <div className="space-y-3">
      {/* 重复发薪提醒 */}
      {checkResult?.hasArchivedBatch && (
        <div className="flex items-center gap-2 p-2 rounded-md bg-amber-50 text-warning text-xs">
          <AlertCircle className="w-4 h-4 flex-shrink-0" />
          本月已有 {checkResult.archivedCount} 个已归档批次{checkResult.payslipsPublished ? '，工资条已发布' : ''}
        </div>
      )}

      <div className="flex items-center gap-2 flex-wrap">
        <div className="flex items-center gap-1 shrink-0">
          <span className="text-xs text-gray-500">从</span>
          <Input type="month" value={monthFrom} onChange={(e) => { setMonthFrom(e.target.value); setMonth('') }} className="!w-36" />
        </div>
        <div className="flex items-center gap-1 shrink-0">
          <span className="text-xs text-gray-500">至</span>
          <Input type="month" value={monthTo} onChange={(e) => { setMonthTo(e.target.value); setMonth('') }} className="!w-36" />
        </div>
        {!monthFrom && !monthTo && !dateFrom && !dateTo && (
          <Input type="month" value={month} onChange={(e) => setMonth(e.target.value)} className="!w-36 shrink-0" placeholder="单月" />
        )}
        <div className="flex items-center gap-1 shrink-0">
          <span className="text-xs text-gray-500">创建日期</span>
          <Input type="date" value={dateFrom} onChange={(e) => { setDateFrom(e.target.value); setMonth('') }} className="!w-36" placeholder="起始" />
          <span className="text-xs text-gray-500">~</span>
          <Input type="date" value={dateTo} onChange={(e) => { setDateTo(e.target.value); setMonth('') }} className="!w-36" placeholder="截止" />
        </div>
        <Select value={filterStatus} onChange={(e) => setFilterStatus(e.target.value)} className="!w-28 shrink-0">
          <option value="">全部状态</option>
          <option value="DRAFT">草稿</option>
          <option value="ARCHIVED">已归档</option>
        </Select>
        <Select value={filterType} onChange={(e) => setFilterType(e.target.value)} className="!w-28 shrink-0">
          <option value="">全部类型</option>
          <option value="REGULAR">常规发薪</option>
          <option value="TERMINATION">离职结算</option>
          <option value="BONUS">年终奖</option>
          <option value="SEVERANCE">补偿金</option>
        </Select>
        {(monthFrom || monthTo || dateFrom || dateTo || filterStatus || filterType) && (
          <button onClick={() => { setMonthFrom(''); setMonthTo(''); setDateFrom(''); setDateTo(''); setFilterStatus(''); setFilterType(''); setMonth(new Date().toISOString().slice(0, 7)) }} className="text-xs text-gray-500 hover:text-primary shrink-0">
            清除筛选
          </button>
        )}
        {batches && batches.length > 0 && (
          <span className="text-xs text-gray-500 whitespace-nowrap shrink-0">{batches.length} 个批次</span>
        )}
        <div className="flex-1" />
        <Button onClick={() => {
          setCreateType('REGULAR'); setShowCreateModal(true)
        }} className="shrink-0">
          <Plus className="w-4 h-4 mr-1" />创建发薪批次
        </Button>
      </div>

      {showCreateModal && (
        <Card>
          <h2 className="text-xs font-medium mb-3">创建发薪批次</h2>
          <div className="space-y-3">
            <div className="grid grid-cols-2 gap-3">
              <div>
                <Label>所属月（计薪月）*</Label>
                <Input type="month" value={createMonth} onChange={(e) => setCreateMonth(e.target.value)} />
                <p className="text-xs text-gray-400 mt-0.5">决定调用哪个月的社保标准、个税累计</p>
              </div>
              <div>
                <Label>发薪年月</Label>
                <Input type="month" value={createPayMonth} onChange={(e) => setCreatePayMonth(e.target.value)} />
                <p className="text-xs text-gray-400 mt-0.5">实际发放月份，留空=与所属月相同。如十一提前发薪：所属月=10月，发薪月=9月</p>
              </div>
            </div>
            <div className="grid grid-cols-2 gap-3">
              <div>
                <Label>批次类型</Label>
                <Select value={createType} onChange={(e) => setCreateType(e.target.value as any)}>
                  <option value="REGULAR">常规发薪</option>
                  <option value="TERMINATION">离职结算</option>
                  <option value="BONUS">年终奖/奖金</option>
                  <option value="SEVERANCE">补偿金批次</option>
                </Select>
              </div>
              <div>
                <Label>数据初始化模式</Label>
                <Select value={createMode} onChange={(e) => { setCreateMode(e.target.value as any); setSourceBatchId(''); setSelectedEmployeeIds([]) }}>
                  <option value="copy_last">复制上月数据</option>
                  <option value="blank_employees">本月空白（拉入员工，金额为0）</option>
                  <option value="blank_all">全空白（不拉入员工）</option>
                  <option value="copy_batch">复制指定批次</option>
                  <option value="custom">★ 自定义选择员工（按部门/姓名筛选）</option>
                </Select>
              </div>
            </div>
            {createMode === 'copy_batch' && (
              <div>
                <Label>选择源批次</Label>
                <Select value={sourceBatchId} onChange={(e) => setSourceBatchId(e.target.value)}>
                  <option value="">请选择已归档批次</option>
                  {archivedBatches?.map((b: any) => (
                    <option key={b.id} value={b.id}>{b.name}（{b.month} · {b.employeeCount}人 · 应发¥{fmt(b.totalPay)}）</option>
                  ))}
                </Select>
                {!archivedBatches?.length && (
                  <p className="text-xs text-gray-500 mt-1">暂无可复制的已归档批次</p>
                )}
              </div>
            )}
            {createMode === 'custom' && (
              <CustomEmployeeSelector selectedIds={selectedEmployeeIds} onChange={setSelectedEmployeeIds} />
            )}
            <div className="text-xs text-gray-500 space-y-0.5">
              {createMode === 'copy_last' && <p>拉入在职员工，从上月工资条复制基本工资/津贴/扣款，自动计算社保个税</p>}
              {createMode === 'blank_employees' && <p>拉入在职员工，所有金额为0，需手动填写</p>}
              {createMode === 'blank_all' && <p>创建空批次，不拉入员工，后续手动添加人员和填写数据</p>}
              {createMode === 'copy_batch' && <p>从指定的已归档批次复制员工和薪资数据</p>}
              {createMode === 'custom' && <p className="text-primary font-medium">按部门筛选、搜索、勾选指定员工创建批次。适合为部分人员单独发薪或离职结算</p>}
            </div>
            <div className="flex gap-2">
              <Button
                onClick={() => createMutation.mutate({ month: createMonth, payMonth: createPayMonth || undefined, type: createType, mode: createMode, sourceBatchId: sourceBatchId || undefined, employeeIds: createMode === 'custom' ? selectedEmployeeIds : undefined })}
                disabled={createMutation.isPending || (createMode === 'copy_batch' && !sourceBatchId) || (createMode === 'custom' && selectedEmployeeIds.length === 0)}
              >
                {createMutation.isPending ? '创建中...' : '确认创建'}
              </Button>
              <Button variant="secondary" onClick={() => setShowCreateModal(false)}>取消</Button>
            </div>
          </div>
        </Card>
      )}

      {isLoading ? (
        <div className="text-center py-8 text-gray-500">加载中...</div>
      ) : !batches || batches.length === 0 ? (
        <EmptyState title="本月暂无发薪批次" description="点击「创建发薪批次」开始" />
      ) : (
        <div className="space-y-3">
          <Card>
            <div className="overflow-x-auto">
              <table className="w-full text-sm">
                <thead>
                  <tr className="border-b text-left text-xs text-gray-500">
                    <th className="py-2 px-3">批次名称</th>
                    <th className="py-2 px-3">所属月/发薪月</th>
                    <th className="py-2 px-3">类型</th>
                    <th className="py-2 px-3 text-right">人数</th>
                    <th className="py-2 px-3 text-right">应发合计</th>
                    <th className="py-2 px-3 text-right">社保合计</th>
                    <th className="py-2 px-3 text-right">公积金合计</th>
                    <th className="py-2 px-3 text-right">个税合计</th>
                    <th className="py-2 px-3 text-right">实发合计</th>
                    <th className="py-2 px-3">创建时间</th>
                    <th className="py-2 px-3">状态</th>
                    <th className="py-2 px-3 text-right">操作</th>
                  </tr>
                </thead>
                <tbody>
                  {batches.slice((page - 1) * pageSize, page * pageSize).map((batch: any) => (
                    <tr key={batch.id} className="border-b hover:bg-gray-50 cursor-pointer" onClick={() => setSelectedBatchId(batch.id)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') setSelectedBatchId(batch.id) }}>
                      <td className="py-2.5 px-3">
                        <div className="flex items-center gap-2">
                          <Layers className="w-4 h-4 text-primary flex-shrink-0" />
                          {renamingId === batch.id ? (
                            <input
                              type="text"
                              autoFocus
                              value={renameValue}
                              onChange={(e) => setRenameValue(e.target.value)}
                              onClick={(e) => e.stopPropagation()}
                              onKeyDown={(e) => {
                                if (e.key === 'Enter') {
                                  e.preventDefault()
                                  if (renameValue.trim() && renameValue !== batch.name) {
                                    renameBatchMutation.mutate({ batchId: batch.id, name: renameValue.trim() })
                                  }
                                  setRenamingId(null)
                                }
                                if (e.key === 'Escape') setRenamingId(null)
                              }}
                              onBlur={() => {
                                if (renameValue.trim() && renameValue !== batch.name) {
                                  renameBatchMutation.mutate({ batchId: batch.id, name: renameValue.trim() })
                                }
                                setRenamingId(null)
                              }}
                              className="text-sm border-b border-primary bg-transparent px-1 py-0.5 focus:outline-none"
                            />
                          ) : (
                            <span className="text-sm font-medium">{batch.name}</span>
                          )}
                        </div>
                      </td>
                      <td className="py-2.5 px-3 text-xs text-gray-600">
                        <div>{batch.month}</div>
                        {batch.payMonth && batch.payMonth !== batch.month && (
                          <div className="text-amber-600">发薪：{batch.payMonth}</div>
                        )}
                      </td>
                      <td className="py-2.5 px-3 text-xs text-gray-600">
                        {batch.type === 'REGULAR' ? '常规发薪' : batch.type === 'BONUS' ? '年终奖/奖金' : batch.type === 'TERMINATION' ? '离职结算' : batch.type === 'SEVERANCE' ? '补偿金' : batch.type}
                      </td>
                      <td className="py-2.5 px-3 text-right text-sm">{batch.employeeCount}</td>
                      <td className="py-2.5 px-3 text-right text-sm font-medium text-primary">¥{fmt(batch.totalPay)}</td>
                      <td className="py-2.5 px-3 text-right text-sm text-amber-600">¥{fmt((batch.totalSocialOrg || 0) + (batch.totalSocialEmp || 0))}</td>
                      <td className="py-2.5 px-3 text-right text-sm text-cyan-600">¥{fmt((batch.totalHousingOrg || 0) + (batch.totalHousingEmp || 0))}</td>
                      <td className="py-2.5 px-3 text-right text-sm text-danger">¥{fmt(batch.totalTax)}</td>
                      <td className="py-2.5 px-3 text-right text-sm font-bold text-safe">¥{fmt(batch.totalNetPay)}</td>
                      <td className="py-2.5 px-3 text-xs text-gray-500">{batch.createdAt ? new Date(batch.createdAt).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : '-'}</td>
                      <td className="py-2.5 px-3">
                        {batch.status === 'ARCHIVED' ? (
                          <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-green-50 text-safe">
                            <Archive className="w-3 h-3" />已归档
                          </span>
                        ) : (
                          <span className="px-2 py-0.5 rounded text-xs bg-amber-50 text-warning">草稿</span>
                        )}
                      </td>
                      <td className="py-2.5 px-3">
                        <div className="flex items-center justify-end gap-1" onClick={(e) => e.stopPropagation()}>
                          {batch.status !== 'ARCHIVED' && (
                            <>
                              <button
                                onClick={() => { setRenamingId(batch.id); setRenameValue(batch.name) }}
                                className="p-1 rounded hover:bg-blue-50 text-gray-500 hover:text-primary transition-colors"
                                aria-label="重命名"
                                title="重命名"
                              >
                                <SettingsIcon className="w-3.5 h-3.5" />
                              </button>
                              <button
                                onClick={async () => {
                                  if (await confirm({ title: '删除批次', message: `确认删除批次「${batch.name}」？此操作不可撤销。` })) {
                                    deleteBatchMutation.mutate(batch.id)
                                  }
                                }}
                                disabled={deleteBatchMutation.isPending}
                                className="p-1 rounded hover:bg-red-50 text-gray-500 hover:text-danger transition-colors"
                                aria-label="删除"
                                title="删除"
                              >
                                <Trash2 className="w-3.5 h-3.5" />
                              </button>
                            </>
                          )}
                          {batch.status === 'ARCHIVED' && (
                            <button
                              onClick={async () => {
                                if (await confirm({ title: '取消归档', message: `确认取消归档「${batch.name}」？取消后批次恢复为草稿状态，对应的工资条和个税累计将被撤销。`, variant: 'primary' })) {
                                  unarchiveBatchMutation.mutate(batch.id)
                                }
                              }}
                              disabled={unarchiveBatchMutation.isPending}
                              className="p-1 rounded hover:bg-amber-50 text-gray-500 hover:text-warning transition-colors"
                              aria-label="取消归档"
                              title="取消归档"
                            >
                              <Archive className="w-3.5 h-3.5" />
                            </button>
                          )}
                        </div>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </Card>
          <Pagination page={page} pageSize={pageSize} total={batches.length} onPageChange={setPage} onPageSizeChange={() => setPage(1)} />
        </div>
      )}
    </div>
  )
}

function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void }) {
  const queryClient = useQueryClient()
  const confirm = useConfirm()
  const [editCell, setEditCell] = useState<{ employeeId: string; field: string } | null>(null)
  const [editValue, setEditValue] = useState<string>('')
  const pageSize = usePageSize()
  const [page, setPage] = useState(1)
  const [showAddEmployee, setShowAddEmployee] = useState(false)
  const payrollFileRef = useRef<HTMLInputElement>(null)
  const [payrollImportResult, setPayrollImportResult] = useState<any>(null)
  const [taxDetailFor, setTaxDetailFor] = useState<{ employeeId: string; name: string } | null>(null)
  const [taxDetailData, setTaxDetailData] = useState<any>(null)
  const [taxDetailLoading, setTaxDetailLoading] = useState(false)

  const { data: batch, isLoading } = useQuery<any>({
    queryKey: ['batch-detail', batchId],
    queryFn: async () => {
      return await payrollApi.batchDetail(batchId)
    },
  })

  const { data: templateItems } = useQuery<any[]>({
    queryKey: ['payslip-template'],
    queryFn: async () => {
      return await payrollApi.template()
    },
  })

  const updateEntryMutation = useMutation({
    mutationFn: ({ employeeId, data }: { employeeId: string; data: any }) =>
      payrollApi.updateBatchEntry(batchId, employeeId, data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
      queryClient.invalidateQueries({ queryKey: ['batches'] })
    },
    onError: (err: any) => {
      toast.error(err?.response?.data?.error?.message || '保存失败，请重试')
    },
  })

  const removeEmployeeMutation = useMutation({
    mutationFn: (employeeId: string) => payrollApi.removeBatchEmployee(batchId, employeeId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
      queryClient.invalidateQueries({ queryKey: ['batches'] })
    },
  })

  const archiveMutation = useMutation({
    mutationFn: () => payrollApi.archiveBatch(batchId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      queryClient.invalidateQueries({ queryKey: ['batch-check'] })
      queryClient.invalidateQueries({ queryKey: ['dashboard'] })
      toast.success('批次已归档。归档后批次锁定不可编辑。可前往「工资条管理」生成工资条。')
    },
  })

  const unarchiveMutation = useMutation({
    mutationFn: () => payrollApi.unarchiveBatch(batchId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      queryClient.invalidateQueries({ queryKey: ['batch-check'] })
      queryClient.invalidateQueries({ queryKey: ['dashboard'] })
      toast.success('已取消归档，批次恢复为草稿状态')
    },
    onError: (err: any) => toast.error(err?.response?.data?.error?.message || '取消归档失败'),
  })

  const publishPayslipMutation = useMutation({
    mutationFn: () => payrollApi.publishPayslip(batchId),
    onSuccess: (res: any) => {
      toast.success(`已发布 ${res.data?.published || 0} 条工资条`)
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
    },
    onError: (err: any) => toast.error(err?.response?.data?.error?.message || '发布失败'),
  })

  const [showScheduleModal, setShowScheduleModal] = useState(false)
  const [scheduleDate, setScheduleDate] = useState('')
  const schedulePayslipMutation = useMutation({
    mutationFn: () => payrollApi.schedulePayslip(batchId, scheduleDate),
    onSuccess: (res: any) => {
      toast.success(`已设定定时发送 ${res.data?.scheduled || 0} 条工资条`)
      setShowScheduleModal(false)
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
    },
    onError: (err: any) => toast.error(err?.response?.data?.error?.message || '设定失败'),
  })

  const importOvertimeMutation = useMutation({
    mutationFn: (force?: boolean) => payrollApi.importOvertimeToBatch(batchId, force),
    onSuccess: async (res: any) => {
      if (res?.needConfirm) {
        if (await confirm({ title: '跨批次重复提醒', message: res.message, variant: 'primary' })) {
          importOvertimeMutation.mutate(true)
        }
        return
      }
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      queryClient.invalidateQueries({ queryKey: ['overtime-records'] })
      if (res?.imported > 0) {
        toast.success(`成功获取 ${res.imported} 条加班费记录`)
      } else {
        toast.info(res?.message || '没有可获取的加班记录')
      }
    },
    onError: () => {
      toast.error('获取失败，请重试')
    },
  })

  const fetchBonusMutation = useMutation({
    mutationFn: (force?: boolean) => payrollApi.fetchBonusToBatch(batchId, force),
    onSuccess: async (res: any) => {
      if (res?.needConfirm) {
        if (await confirm({ title: '跨批次重复提醒', message: res.message, variant: 'primary' })) {
          fetchBonusMutation.mutate(true)
        }
        return
      }
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      if (res?.filled > 0) {
        toast.success(res.message)
      } else {
        toast.info(res?.message || '无提成奖金数据')
      }
    },
    onError: () => {
      toast.error('获取提成奖金失败')
    },
  })

  const fetchPerformanceMutation = useMutation({
    mutationFn: (force?: boolean) => payrollApi.fetchPerformanceToBatch(batchId, force),
    onSuccess: async (res: any) => {
      if (res?.needConfirm) {
        if (await confirm({ title: '跨批次重复提醒', message: res.message, variant: 'primary' })) {
          fetchPerformanceMutation.mutate(true)
        }
        return
      }
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      if (res?.filled > 0) {
        toast.success(res.message)
      } else {
        toast.info(res?.message || '无绩效工资数据')
      }
    },
    onError: () => {
      toast.error('获取绩效工资失败')
    },
  })

  const fetchDisciplinaryMutation = useMutation({
    mutationFn: (force?: boolean) => payrollApi.fetchDisciplinaryToBatch(batchId, force),
    onSuccess: async (res: any) => {
      if (res?.needConfirm) {
        if (await confirm({ title: '跨批次重复提醒', message: res.message, variant: 'primary' })) {
          fetchDisciplinaryMutation.mutate(true)
        }
        return
      }
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      if (res?.filled > 0) {
        toast.success(res.message)
      } else {
        toast.info(res?.message || '无违纪扣款数据')
      }
    },
    onError: () => {
      toast.error('获取违纪扣款失败')
    },
  })

  const fetchAttendanceDeductionMutation = useMutation({
    mutationFn: (force?: boolean) => payrollApi.fetchAttendanceDeductionToBatch(batchId, force),
    onSuccess: async (res: any) => {
      if (res?.needConfirm) {
        if (await confirm({ title: '跨批次重复提醒', message: res.message, variant: 'primary' })) {
          fetchAttendanceDeductionMutation.mutate(true)
        }
        return
      }
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      if (res?.filled > 0) {
        toast.success(res.message)
      } else {
        toast.info(res?.message || '无考勤扣款数据')
      }
    },
    onError: () => {
      toast.error('获取考勤扣款失败')
    },
  })

  const importPayrollMutation = useMutation({
    mutationFn: async (file: File) => {
      const token = useAuthStore.getState().accessToken
      const formData = new FormData()
      formData.append('file', file)
      formData.append('batchId', batchId)
      const res = await fetch('/api/v1/import/payroll', {
        method: 'POST',
        headers: token ? { Authorization: `Bearer ${token}` } : {},
        body: formData,
      })
      const data = await res.json()
      if (!data.success) throw new Error(data.message || '导入失败')
      return data.data
    },
    onSuccess: (data: any) => {
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      setPayrollImportResult(data)
      if (data.updated > 0) {
        toast.success(`成功更新 ${data.updated} 条工资记录`)
      } else {
        toast.info('未更新任何记录')
      }
    },
    onError: () => toast.error('工资表导入失败'),
  })

  const deleteBatchMutation = useMutation({
    mutationFn: () => payrollApi.removeBatch(batchId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      queryClient.invalidateQueries({ queryKey: ['batch-check'] })
      onBack()
    },
  })

  if (isLoading) return <div className="text-center py-8 text-gray-500">加载中...</div>
  if (!batch) return <div className="text-center py-8 text-gray-500">批次不存在</div>

  const isArchived = batch.status === 'ARCHIVED'
  const isBonus = batch.type === 'BONUS'

  const handleTaxDetailClick = async (employeeId: string, name: string) => {
    setTaxDetailFor({ employeeId, name })
    setTaxDetailData(null)
    setTaxDetailLoading(true)
    try {
      const data = await payrollApi.taxDetail(batchId, employeeId)
      setTaxDetailData(data)
    } catch {
      toast.error('获取个税明细失败')
    } finally {
      setTaxDetailLoading(false)
    }
  }

  // 可编辑的输入项字段：从模板动态获取，回退到硬编码
  const editableFields = (() => {
    if (templateItems && templateItems.length > 0) {
      const editable = templateItems.filter((i: any) => i.isEditable).map((i: any) => i.code)
      // 奖金批次只允许编辑 bonus
      if (isBonus) return ['bonus']
      // 社保/公积金允许手动覆盖
      return [...editable, 'socialEmp', 'housingEmp', 'socialOrg', 'housingOrg']
    }
    return isBonus
      ? ['bonus']
      : ['baseSalary', 'performanceSalary', 'overtimePay', 'allowance', 'deduction', 'bonus', 'socialEmp', 'housingEmp', 'socialOrg', 'housingOrg']
  })()

  // 预置字段集合（固定列存储）
  const PRESET_FIELDS = new Set(['baseSalary', 'performanceSalary', 'overtimePay', 'allowance', 'deduction', 'bonus', 'socialEmp', 'socialOrg', 'housingEmp', 'housingOrg', 'positionSalary', 'senioritySalary', 'transportAllowance', 'mealAllowance', 'housingAllowance', 'communicationAllowance', 'otherDeduction'])

  // 表格列宽计算（table-layout: fixed 需要精确宽度）
  const COL_W = { employee: 140, contract: 100, dynamic: 90, netPay: 110, deferred: 110, risk: 50, action: 60 }
  const dynamicCols = (templateItems || []).filter((t: any) => (!isBonus || t.code === 'bonus') && t.code !== 'netPay')
  const rightLockedWidth = COL_W.netPay + COL_W.deferred + COL_W.risk + (isArchived ? 0 : COL_W.action)
  const tableMinWidth = COL_W.employee + COL_W.contract + dynamicCols.length * COL_W.dynamic + rightLockedWidth
  // 右侧锁定列偏移量（从右到左累加）
  const rightOffsets = {
    action: 0,
    risk: isArchived ? 0 : COL_W.action,
    deferred: isArchived ? COL_W.risk : COL_W.action + COL_W.risk,
    netPay: isArchived ? COL_W.risk + COL_W.deferred : COL_W.action + COL_W.risk + COL_W.deferred,
  }

  // 点击单元格进入编辑
  const startEdit = (employeeId: string, field: string, currentValue: number) => {
    if (isArchived || !editableFields.includes(field)) return
    setEditCell({ employeeId, field })
    setEditValue(currentValue.toFixed(2))
  }

  // 保存编辑
  const saveEdit = () => {
    if (!editCell) return
    const { employeeId, field } = editCell
    const numValue = Number(editValue) || 0
    const entry = batch.entries.find((e: any) => e.employeeId === employeeId)
    if (!entry) { setEditCell(null); return }
    const data: any = {}
    // 社保公积金字段：编辑哪个提交哪个
    const socialFields = ['socialEmp', 'socialOrg', 'housingEmp', 'housingOrg']
    if (socialFields.includes(field)) {
      data[field] = numValue
    } else if (PRESET_FIELDS.has(field)) {
      // 预置输入项字段一起提交
      const inputFields = isBonus ? ['bonus'] : (templateItems || []).filter((t: any) => t.type === 'INPUT' && t.isEditable && PRESET_FIELDS.has(t.code)).map((t: any) => t.code)
      inputFields.forEach(f => {
        data[f] = f === field ? numValue : entry[f]
      })
    } else {
      // 自定义字段走 extraItems
      const existingExtra = entry.extraItems || {}
      data.extraItems = { ...existingExtra, [field]: numValue }
    }
    updateEntryMutation.mutate({ employeeId, data })
    setEditCell(null)
  }

  // 失焦保存
  const handleBlur = () => saveEdit()

  // Enter 保存，Esc 取消
  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') { e.preventDefault(); saveEdit() }
    if (e.key === 'Escape') setEditCell(null)
  }

  // 渲染可编辑单元格
  const renderCell = (entry: any, field: string, className: string = '') => {
    const isEditing = editCell?.employeeId === entry.employeeId && editCell?.field === field
    const canEdit = !isArchived && editableFields.includes(field)

    const value = PRESET_FIELDS.has(field) ? (entry[field] || 0) : ((entry.extraItems || {})[field] || 0)
    const displayValue = field === 'deduction' && value > 0 ? '-' + fmt(value) : fmt(value)

    if (isEditing) {
      return (
        <td className="py-1 px-1 text-right" key={field}>
          <input
            type="text"
            inputMode="decimal"
            autoFocus
            className="w-24 text-right border-b-2 border-primary bg-transparent px-1 py-0.5 text-xs focus:outline-none [appearance:none]"
            value={editValue}
            onChange={(e) => setEditValue(e.target.value)}
            onBlur={handleBlur}
            onKeyDown={handleKeyDown}
          />
        </td>
      )
    }

    return (
      <td
        key={field}
        className={`py-2 px-2 text-right ${className} ${canEdit ? 'cursor-text' : ''}`}
        onClick={() => canEdit && startEdit(entry.employeeId, field, value)}
      >
        {canEdit ? (
          <span className={`border-b border-dashed border-gray-300 hover:border-primary ${field === 'deduction' && value > 0 ? 'text-danger' : ''}`}>
            {displayValue}
          </span>
        ) : field === 'deduction' && value > 0 ? (
          <span className="text-danger">{displayValue}</span>
        ) : (
          displayValue
        )}
      </td>
    )
  }

  return (
    <div className="space-y-3">
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-3">
          <button onClick={onBack} className="flex items-center gap-1 text-gray-500 hover:text-gray-700 text-sm transition-colors">
            <ChevronLeft className="w-4 h-4" />返回
          </button>
          <h2 className="text-sm font-medium">{batch.name}</h2>
          {isArchived ? (
            <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-green-50 text-safe">
              <Archive className="w-3 h-3" />已归档
            </span>
          ) : (
            <span className="px-2 py-0.5 rounded text-xs bg-amber-50 text-warning">草稿</span>
          )}
        </div>
        {!isArchived && (
          <div className="flex gap-2">
            <Button variant="secondary" size="sm" onClick={() => setShowAddEmployee(!showAddEmployee)}>
              <Plus className="w-4 h-4 mr-1" />添加人员
            </Button>
            <input
              ref={payrollFileRef}
              type="file"
              accept=".xlsx,.xls"
              className="hidden"
              onChange={(e) => {
                const file = e.target.files?.[0]
                if (file) importPayrollMutation.mutate(file)
                e.target.value = ''
              }}
            />
            <Button
              variant="secondary"
              size="sm"
              onClick={() => payrollFileRef.current?.click()}
              disabled={importPayrollMutation.isPending}
            >
              <Upload className="w-4 h-4 mr-1" />
              {importPayrollMutation.isPending ? '导入中...' : '导入工资表'}
            </Button>
            <Button
              variant="secondary"
              size="sm"
              onClick={async () => {
                try {
                  const token = useAuthStore.getState().accessToken
                  const baseURL = import.meta.env.DEV ? 'http://localhost:3000/api/v1' : '/api/v1'
                  const res = await fetch(`${baseURL}/import/payroll-template`, {
                    headers: token ? { Authorization: `Bearer ${token}` } : {},
                  })
                  const blob = await res.blob()
                  const url = URL.createObjectURL(blob)
                  const a = document.createElement('a')
                  a.href = url
                  a.download = '工资表导入模板.xlsx'
                  a.click()
                  URL.revokeObjectURL(url)
                } catch {
                  toast.error('下载模板失败')
                }
              }}
            >
              <Download className="w-4 h-4 mr-1" />下载模板
            </Button>
            {/* 根据模板 dataSource 动态显示获取按钮 */}
            {(templateItems || []).some((t: any) => t.dataSource === 'overtime') && (
              <Button
                variant="secondary"
                size="sm"
                onClick={() => importOvertimeMutation.mutate(undefined)}
                disabled={importOvertimeMutation.isPending}
              >
                <Calculator className="w-4 h-4 mr-1" />
                {importOvertimeMutation.isPending ? '获取中...' : '获取加班费'}
              </Button>
            )}
            {(templateItems || []).some((t: any) => t.dataSource === 'bonus') && (
              <Button
                variant="secondary"
                size="sm"
                onClick={() => fetchBonusMutation.mutate(undefined)}
                disabled={fetchBonusMutation.isPending || isArchived}
                title="从提成奖金模块按月拉取填充奖金字段"
              >
                <DollarSign className="w-4 h-4 mr-1" />
                {fetchBonusMutation.isPending ? '获取中...' : '获取提成奖金'}
              </Button>
            )}
            {(templateItems || []).some((t: any) => t.dataSource === 'performance') && (
              <Button
                variant="secondary"
                size="sm"
                onClick={() => fetchPerformanceMutation.mutate(undefined)}
                disabled={fetchPerformanceMutation.isPending || isArchived}
                title="按考核等级系数计算绩效工资（A=1.2/B=1.0/C=0.8/D=0.6），无考核记录按1.0"
              >
                <TrendingUp className="w-4 h-4 mr-1" />
                {fetchPerformanceMutation.isPending ? '获取中...' : '获取绩效工资'}
              </Button>
            )}
            {(templateItems || []).some((t: any) => t.dataSource === 'deduction') && (
              <>
                <Button
                  variant="secondary"
                  size="sm"
                  onClick={() => fetchDisciplinaryMutation.mutate(undefined)}
                  disabled={fetchDisciplinaryMutation.isPending || isArchived}
                  title="按批次月份从违纪记录中汇总扣款金额（action=DEDUCTION）"
                >
                  <AlertTriangle className="w-4 h-4 mr-1" />
                  {fetchDisciplinaryMutation.isPending ? '获取中...' : '获取违纪扣款'}
                </Button>
                <Button
                  variant="secondary"
                  size="sm"
                  onClick={() => fetchAttendanceDeductionMutation.mutate(undefined)}
                  disabled={fetchAttendanceDeductionMutation.isPending || isArchived}
                  title="按批次月份从考勤确认中获取扣款金额（需先在考勤确认中填写）"
                >
                  <CalendarCheck className="w-4 h-4 mr-1" />
                  {fetchAttendanceDeductionMutation.isPending ? '获取中...' : '获取考勤扣款'}
                </Button>
              </>
            )}
            <Button
              size="sm"
              onClick={async () => {
                if (await confirm({ title: '归档确认', message: '确认归档？归档后批次将锁定不可编辑。工资条需在「工资条管理」中单独生成。', variant: 'primary' })) {
                  archiveMutation.mutate()
                }
              }}
              disabled={archiveMutation.isPending}
            >
              <Archive className="w-4 h-4 mr-1" />
              {archiveMutation.isPending ? '归档中...' : '归档'}
            </Button>
            <Button
              variant="danger"
              size="sm"
              onClick={async () => {
                if (await confirm({ title: '删除批次', message: `确认删除批次「${batch.name}」？此操作不可撤销。` })) {
                  deleteBatchMutation.mutate()
                }
              }}
              disabled={deleteBatchMutation.isPending}
            >
              <Trash2 className="w-4 h-4 mr-1" />
              {deleteBatchMutation.isPending ? '删除中...' : '删除'}
            </Button>
          </div>
        )}
        {isArchived && (
          <div className="flex gap-2">
            <Button
              variant="secondary"
              size="sm"
              onClick={async () => {
                try {
                  const token = useAuthStore.getState().accessToken
                  const baseURL = import.meta.env.DEV ? 'http://localhost:3000/api/v1' : '/api/v1'
                  const res = await fetch(`${baseURL}/payroll2/batches/${batchId}/export?format=csv`, { headers: { Authorization: `Bearer ${token}` } })
                  if (!res.ok) throw new Error('导出失败')
                  const blob = await res.blob()
                  const url = URL.createObjectURL(blob)
                  const a = document.createElement('a')
                  a.href = url
                  a.download = `银行代发文件-${batch.month}-批次${batch.batchNo}.csv`
                  a.click()
                  URL.revokeObjectURL(url)
                } catch { toast.error('导出失败') }
              }}
            >
              <Download className="w-4 h-4 mr-1" />银行代发文件
            </Button>
            <Button
              variant="secondary"
              size="sm"
              onClick={async () => {
                try {
                  const res = await payrollApi.batchSummary(batchId) as any
                  const { departments, grandTotal } = res
                  const headers = ['部门', '人数', '应发合计', '实发合计', '个人社保', '单位社保', '个人公积金', '单位公积金', '个税合计']
                  const rows = departments.map((d: any) => [
                    d.department, d.headcount, d.totalPay.toFixed(2), d.totalNetPay.toFixed(2),
                    d.totalSocialEmp.toFixed(2), d.totalSocialOrg.toFixed(2),
                    d.totalHousingEmp.toFixed(2), d.totalHousingOrg.toFixed(2), d.totalTax.toFixed(2),
                  ])
                  rows.push(['合计', grandTotal.headcount, grandTotal.totalPay.toFixed(2), grandTotal.totalNetPay.toFixed(2),
                    grandTotal.totalSocialEmp.toFixed(2), grandTotal.totalSocialOrg.toFixed(2),
                    grandTotal.totalHousingEmp.toFixed(2), grandTotal.totalHousingOrg.toFixed(2), grandTotal.totalTax.toFixed(2)])
                  const csv = [headers, ...rows].map(r => r.join(',')).join('\n')
                  const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' })
                  const url = URL.createObjectURL(blob)
                  const a = document.createElement('a')
                  a.href = url
                  a.download = `salary-summary-${batch.month}.csv`
                  a.click()
                  URL.revokeObjectURL(url)
                } catch { toast.error('导出汇总表失败') }
              }}
            >
              <FileText className="w-4 h-4 mr-1" />薪资汇总表
            </Button>
            <Button
              variant="secondary"
              size="sm"
              onClick={async () => {
                try {
                  const res = await payrollApi.batchDetailExport(batchId) as any
                  const { details } = res
                  const headers = ['姓名', '部门', '基本工资', '岗位工资', '绩效工资', '工龄工资', '加班费', '交通补贴', '餐补', '住房补贴', '通讯补贴', '其他津贴', '奖金', '扣款', '其他扣款', '个人社保', '个人公积金', '个税', '应发合计', '实发工资']
                  const rows = details.map((d: any) => [
                    d.name, d.department,
                    d.baseSalary.toFixed(2), d.positionSalary.toFixed(2), d.performanceSalary.toFixed(2),
                    d.senioritySalary.toFixed(2), d.overtimePay.toFixed(2),
                    d.transportAllowance.toFixed(2), d.mealAllowance.toFixed(2), d.housingAllowance.toFixed(2),
                    d.communicationAllowance.toFixed(2), d.allowance.toFixed(2), d.bonus.toFixed(2),
                    d.deduction.toFixed(2), d.otherDeduction.toFixed(2),
                    d.socialEmp.toFixed(2), d.housingEmp.toFixed(2), d.tax.toFixed(2),
                    d.totalPay.toFixed(2), d.netPay.toFixed(2),
                  ])
                  const csv = [headers, ...rows].map(r => r.join(',')).join('\n')
                  const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' })
                  const url = URL.createObjectURL(blob)
                  const a = document.createElement('a')
                  a.href = url
                  a.download = `salary-detail-${batch.month}.csv`
                  a.click()
                  URL.revokeObjectURL(url)
                } catch { toast.error('导出明细表失败') }
              }}
            >
              <FileText className="w-4 h-4 mr-1" />薪资明细表
            </Button>
            <Button
              variant="secondary"
              size="sm"
              onClick={async () => {
                if (await confirm({ title: '取消归档', message: '确认取消归档？取消后批次恢复为草稿状态，对应的工资条和个税累计将被撤销。', variant: 'primary' })) {
                  unarchiveMutation.mutate()
                }
              }}
              disabled={unarchiveMutation.isPending}
            >
              {unarchiveMutation.isPending ? '取消中...' : '取消归档'}
            </Button>
            <Button
              size="sm"
              onClick={async () => {
                if (await confirm({ title: '发布工资条', message: `确认发布 ${batch.month} 月工资条？发布后员工可在员工端查看。`, variant: 'primary' })) {
                  publishPayslipMutation.mutate()
                }
              }}
              disabled={publishPayslipMutation.isPending}
            >
              {publishPayslipMutation.isPending ? '发布中...' : '发布工资条'}
            </Button>
            <Button
              variant="secondary"
              size="sm"
              onClick={() => setShowScheduleModal(true)}
            >
              <Clock className="w-4 h-4 mr-1" />定时发送
            </Button>
          </div>
        )}
      </div>

      {/* 发薪工作流步骤条 */}
      <Card className="p-4">
        <Stepper
          steps={[
            { key: 'edit', title: '编辑薪资', description: '填写/导入/核对工资数据', status: isArchived ? 'complete' : 'current' },
            { key: 'archive', title: '归档锁定', description: '归档后不可编辑', status: isArchived ? 'complete' : 'pending' },
            { key: 'publish', title: '发布工资条', description: '员工端可见', status: batch.payslipPublished ? 'complete' : 'pending' },
          ] as Step[]}
        />
      </Card>

      {/* 质量门禁 — 草稿状态下检查异常 */}
      {!isArchived && batch.entries.length > 0 && (
        <>
          {batch.entries.some((e: any) => e.totalPay > 0 && e.netPay <= 0) && (
            <InlineAlert type="error" title="存在实发为负的员工">
              请检查社保/公积金基数是否过大，导致实发工资 ≤ 0 的记录需修正后再归档。
            </InlineAlert>
          )}
          {batch.entries.some((e: any) => e.minWage > 0 && e.netPay < e.minWage && (e.minWageApplied || 0) === 0) && (
            <InlineAlert type="error" title="存在实发低于最低工资的员工">
              部分员工实发工资低于当地最低工资标准 ¥{Math.min(...batch.entries.filter((e: any) => e.minWage > 0 && e.netPay < e.minWage).map((e: any) => e.minWage))}，请检查社保基数或在社保配置中设置最低工资标准。
            </InlineAlert>
          )}
          {batch.entries.some((e: any) => (e.minWageApplied || 0) > 0) && (
            <InlineAlert type="warning" title="最低工资保护已触发">
              {batch.entries.filter((e: any) => (e.minWageApplied || 0) > 0).length} 名员工实发已补齐到最低工资标准。免扣的社保/公积金及额外补齐差额已递延，将在次月工资中补扣，请在「递延扣款」列查看明细。
            </InlineAlert>
          )}
          {batch.entries.some((e: any) => (e.prevDeferredSocialEmp || 0) + (e.prevDeferredHousingEmp || 0) + (e.prevDeferredMinWage || 0) > 0) && (
            <InlineAlert type="warning" title="本月补扣上月递延">
              {batch.entries.filter((e: any) => (e.prevDeferredSocialEmp || 0) + (e.prevDeferredHousingEmp || 0) + (e.prevDeferredMinWage || 0) > 0).length} 名员工本月补扣了上月递延的社保/公积金/最低工资补齐差额。
            </InlineAlert>
          )}
          {batch.entries.some((e: any) => e.baseSalary === 0 && e.bonus === 0 && e.totalPay === 0) && (
            <InlineAlert type="warning" title="存在全零记录">
              部分员工所有金额为 0，请确认是否需要填写或移除这些人员。
            </InlineAlert>
          )}
          {batch.entries.some((e: any) => e.riskWarnings && e.riskWarnings.length > 0) && (
            <InlineAlert type="warning" title="存在风险预警">
              部分员工有薪资风险提示（如社保基数偏低/偏高），请在「风险」列查看详情。
            </InlineAlert>
          )}
        </>
      )}

      {/* 批次汇总 */}
      <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
        <Card className="flex items-center gap-3">
          <div className="w-10 h-10 rounded-lg bg-blue-50 flex items-center justify-center flex-shrink-0">
            <Users className="w-5 h-5 text-blue-600" />
          </div>
          <div>
            <div className="text-lg font-bold">{batch.employeeCount}</div>
            <div className="text-xs text-gray-500">人数</div>
          </div>
        </Card>
        <Card className="flex items-center gap-3">
          <div className="w-10 h-10 rounded-lg bg-indigo-50 flex items-center justify-center flex-shrink-0">
            <TrendingUp className="w-5 h-5 text-indigo-600" />
          </div>
          <div>
            <div className="text-lg font-bold text-primary">¥{fmt(batch.totalPay)}</div>
            <div className="text-xs text-gray-500">应发合计</div>
          </div>
        </Card>
        <Card className="flex items-center gap-3">
          <div className="w-10 h-10 rounded-lg bg-red-50 flex items-center justify-center flex-shrink-0">
            <TrendingDown className="w-5 h-5 text-red-600" />
          </div>
          <div>
            <div className="text-lg font-bold text-danger">¥{fmt(batch.totalTax)}</div>
            <div className="text-xs text-gray-500">个税合计</div>
          </div>
        </Card>
        <Card className="flex items-center gap-3">
          <div className="w-10 h-10 rounded-lg bg-green-50 flex items-center justify-center flex-shrink-0">
            <BadgeCheck className="w-5 h-5 text-green-600" />
          </div>
          <div>
            <div className="text-lg font-bold text-safe">¥{fmt(batch.totalNetPay)}</div>
            <div className="text-xs text-gray-500">实发合计</div>
          </div>
        </Card>
      </div>

      {/* 添加人员 */}
      {showAddEmployee && !isArchived && (
        <AddEmployeeToBatch batchId={batchId} onClose={() => setShowAddEmployee(false)} />
      )}

      {/* 工资表导入结果 */}
      {payrollImportResult && (
        <Card>
          <div className="flex items-center justify-between mb-2">
            <h3 className="text-xs font-medium">工资表导入结果</h3>
            <button onClick={() => setPayrollImportResult(null)} className="text-gray-500"><X className="w-4 h-4" /></button>
          </div>
          <div className="text-sm space-y-1">
            <div className="text-gray-600">总计 {payrollImportResult.total} 行，成功更新 {payrollImportResult.updated} 条</div>
            {payrollImportResult.errors?.length > 0 && (
              <div className="mt-2">
                <div className="text-warning text-xs font-medium">错误详情（{payrollImportResult.errors.length}条）：</div>
                <ul className="mt-1 space-y-0.5 text-xs text-danger max-h-40 overflow-y-auto">
                  {payrollImportResult.errors.map((err: string, i: number) => (
                    <li key={i}>{err}</li>
                  ))}
                </ul>
              </div>
            )}
          </div>
        </Card>
      )}

      {/* 提示 */}
      {!isArchived && (
        <div className="text-xs text-gray-500 flex items-center gap-1">
          <Info className="w-3.5 h-3.5" />
          带下划线的单元格可直接点击编辑，失焦自动保存。社保/公积金可手动覆盖，个税和实发自动计算。
        </div>
      )}

      {/* 人员表格 */}
      <Card>
        <div className="overflow-x-auto" style={{ maxWidth: '100%' }}>
          <table className="text-sm" style={{ borderCollapse: 'separate', borderSpacing: 0, tableLayout: 'fixed', width: tableMinWidth }}>
            <thead>
              <tr className="text-left text-xs text-gray-500">
                <th className="py-2 px-3 whitespace-nowrap sticky left-0 z-20 bg-white border-r border-gray-200" style={{ width: COL_W.employee }}>员工</th>
                <th className="py-2 px-2 whitespace-nowrap sticky z-20 bg-white border-r border-gray-200" style={{ width: COL_W.contract, left: COL_W.employee }}>合同类型</th>
                {/* 动态生成模板项列（排除 netPay，由固定实发列显示） */}
                {dynamicCols.map((t: any) => (
                  <th key={t.code} className={`py-2 px-2 text-right whitespace-nowrap ${t.type === 'CALCULATED' ? 'text-gray-500' : ''}`} style={{ width: COL_W.dynamic }}>
                    {t.name}
                  </th>
                ))}
                <th className="py-2 px-2 text-right text-gray-500 whitespace-nowrap sticky z-20 bg-white border-l border-gray-200" style={{ width: COL_W.netPay, right: rightOffsets.netPay }}>实发</th>
                <th className="py-2 px-2 text-right text-gray-500 whitespace-nowrap sticky z-20 bg-white border-l border-gray-200" style={{ width: COL_W.deferred, right: rightOffsets.deferred }}>递延扣款</th>
                <th className="py-2 px-2 text-center whitespace-nowrap sticky z-20 bg-white border-l border-gray-200" style={{ width: COL_W.risk, right: rightOffsets.risk }}>风险</th>
                {!isArchived && <th className="py-2 px-2 text-center whitespace-nowrap sticky z-20 bg-white border-l border-gray-200" style={{ width: COL_W.action, right: rightOffsets.action }}>操作</th>}
              </tr>
            </thead>
            <tbody>
              {batch.entries.slice((page - 1) * pageSize, page * pageSize).map((entry: any) => (
                <tr key={entry.id} className="group border-b last:border-0">
                  <td className="py-2 px-3 sticky left-0 z-10 bg-white group-hover:bg-gray-50 border-r border-gray-200" style={{ width: COL_W.employee }}>
                    <div className="flex flex-col gap-0.5">
                      <div className="text-xs font-medium">{entry.employee.name}</div>
                      {entry.employee.idCardNumber && <span className="text-gray-400 text-[11px] font-mono">{entry.employee.idCardNumber}</span>}
                      <div className="text-xs text-gray-500">{entry.employee.department}</div>
                      {entry.employee.status === 'RESIGNED' && (
                        <span className="text-xs text-danger">已离职</span>
                      )}
                    </div>
                  </td>
                  <td className="py-2 px-2 sticky z-10 bg-white group-hover:bg-gray-50 border-r border-gray-200" style={{ width: COL_W.contract, left: COL_W.employee }}>
                    {(() => {
                      const ct = entry.employee.contracts?.[0]?.contractType
                      const cfg: Record<string, { label: string; style: string }> = {
                        FIXED: { label: '固定期限', style: 'bg-blue-50 text-blue-700' },
                        UNFIXED: { label: '无固定期', style: 'bg-purple-50 text-purple-700' },
                        LABOR: { label: '劳务协议', style: 'bg-amber-50 text-amber-700' },
                        INTERNSHIP: { label: '实习协议', style: 'bg-teal-50 text-teal-700' },
                        PARTTIME: { label: '兼职协议', style: 'bg-cyan-50 text-cyan-700' },
                        OUTSOURCING: { label: '业务外包', style: 'bg-slate-50 text-slate-700' },
                        DISPATCH: { label: '劳务派遣', style: 'bg-cyan-50 text-cyan-700' },
                        UNSIGNED: { label: '未签合同', style: 'bg-red-50 text-danger' },
                      }
                      const c = cfg[ct || ''] || { label: '未签合同', style: 'bg-red-50 text-danger' }
                      const noSocial = ct && ['LABOR', 'INTERNSHIP', 'PARTTIME', 'OUTSOURCING', 'UNSIGNED'].includes(ct)
                      return (
                        <div className="flex flex-col gap-0.5">
                          <span className={`px-1.5 py-0.5 rounded text-[11px] ${c.style}`}>{c.label}</span>
                          {noSocial && <span className="text-[10px] text-gray-400">不缴社保</span>}
                        </div>
                      )
                    })()}
                  </td>
                  {/* 动态生成模板项单元格（排除 netPay） */}
                  {dynamicCols.map((t: any) => {
                    // 绩效工资：保留特殊渲染（显示绩效等级）
                    if (t.code === 'performanceSalary') {
                      const perfVal = entry.performanceSalary || 0
                      const grade = entry.perfGrade || null
                      const coef = entry.perfCoefficient != null ? entry.perfCoefficient : null
                      let colorClass = ''
                      if (coef != null && coef !== 1.0) {
                        if (coef > 1.0) colorClass = 'text-green-600'
                        else if (coef >= 0.8) colorClass = 'text-orange-600'
                        else colorClass = 'text-red-600'
                      }
                      const isEditing = editCell?.employeeId === entry.employeeId && editCell?.field === 'performanceSalary'
                      const canEdit = !isArchived && editableFields.includes('performanceSalary')
                      if (isEditing) {
                        return (
                          <td className="py-1 px-1 text-right" key="performanceSalary">
                            <input
                              type="text"
                              inputMode="decimal"
                              autoFocus
                              className="w-24 text-right border-b-2 border-primary bg-transparent px-1 py-0.5 text-xs focus:outline-none [appearance:none]"
                              value={editValue}
                              onChange={(e) => setEditValue(e.target.value)}
                              onBlur={handleBlur}
                              onKeyDown={handleKeyDown}
                            />
                          </td>
                        )
                      }
                      return (
                        <td
                          key="performanceSalary"
                          className={`py-1 px-2 text-right ${canEdit ? 'cursor-text' : ''}`}
                          onClick={() => canEdit && startEdit(entry.employeeId, 'performanceSalary', perfVal)}
                        >
                          <div className="flex flex-col items-end leading-tight">
                            {canEdit ? (
                              <span className={`border-b border-dashed border-gray-300 hover:border-primary ${colorClass} ${perfVal === 0 ? 'text-gray-300' : ''}`}>
                                {fmt(perfVal)}
                              </span>
                            ) : (
                              <span className={`${colorClass} ${perfVal === 0 ? 'text-gray-300' : ''}`}>{fmt(perfVal)}</span>
                            )}
                            {grade && (
                              <span className={`text-[10px] ${colorClass || 'text-gray-400'}`}>
                                {grade}·{coef != null ? coef.toFixed(1) : '1.0'}
                              </span>
                            )}
                          </div>
                        </td>
                      )
                    }
                    // 个税：保留点击查看明细
                    if (t.code === 'tax') {
                      return (
                        <td key="tax" className="py-2 px-2 text-right text-gray-500 cursor-pointer hover:text-primary hover:underline" onClick={() => handleTaxDetailClick(entry.employeeId, entry.employee.name)} title="点击查看个税计算明细">
                          {fmt(entry.tax)}
                        </td>
                      )
                    }
                    // 其他项：通用渲染
                    return renderCell(entry, t.code, t.type === 'CALCULATED' ? 'font-medium text-gray-600' : '')
                  })}
                  {/* 实发：最低工资保护触发时高亮显示 */}
                  <td className={`py-2 px-2 text-right font-bold sticky z-10 bg-white group-hover:bg-gray-50 border-l border-gray-200 ${(entry.minWageApplied || 0) > 0 ? 'text-amber-600' : 'text-safe'}`} style={{ width: COL_W.netPay, right: rightOffsets.netPay }} title={(entry.minWageApplied || 0) > 0 ? `最低工资保护已触发\n应发 ¥${fmt(entry.totalPay)} → 实发补齐到 ¥${fmt(entry.minWage)}\n免扣社保 ¥${fmt(entry.deferredSocialEmp || 0)} + 免扣公积金 ¥${fmt(entry.deferredHousingEmp || 0)} + 额外补齐 ¥${fmt(entry.deferredMinWage || 0)} = 递延 ¥${fmt((entry.deferredSocialEmp || 0) + (entry.deferredHousingEmp || 0) + (entry.deferredMinWage || 0))}（次月补扣）` : ''}>
                    {fmt(entry.netPay)}
                    {(entry.minWageApplied || 0) > 0 && <span className="text-xs text-amber-500 ml-1">★</span>}
                  </td>
                  {/* 递延扣款明细 */}
                  <td className="py-2 px-2 text-right text-xs sticky z-10 bg-white group-hover:bg-gray-50 border-l border-gray-200" style={{ width: COL_W.deferred, right: rightOffsets.deferred }}>
                    {(() => {
                      const deferred = (entry.deferredSocialEmp || 0) + (entry.deferredHousingEmp || 0) + (entry.deferredMinWage || 0)
                      const prevDeferred = (entry.prevDeferredSocialEmp || 0) + (entry.prevDeferredHousingEmp || 0) + (entry.prevDeferredMinWage || 0)
                      if (deferred > 0) {
                        return <span className="text-amber-600 cursor-help" title={`递延到次月补扣：\n免扣社保 ¥${fmt(entry.deferredSocialEmp || 0)}\n免扣公积金 ¥${fmt(entry.deferredHousingEmp || 0)}\n额外补齐 ¥${fmt(entry.deferredMinWage || 0)}\n合计 ¥${fmt(deferred)}`}>递延 ¥{fmt(deferred)}</span>
                      }
                      if (prevDeferred > 0) {
                        return <span className="text-blue-600 cursor-help" title={`本月补扣上月递延：\n社保 ¥${fmt(entry.prevDeferredSocialEmp || 0)}\n公积金 ¥${fmt(entry.prevDeferredHousingEmp || 0)}\n最低工资补齐 ¥${fmt(entry.prevDeferredMinWage || 0)}\n合计 ¥${fmt(prevDeferred)}`}>补扣 ¥{fmt(prevDeferred)}</span>
                      }
                      return <span className="text-gray-300">—</span>
                    })()}
                  </td>
                  <td className="py-2 px-2 text-center sticky z-10 bg-white group-hover:bg-gray-50 border-l border-gray-200" style={{ width: COL_W.risk, right: rightOffsets.risk }}>
                    {entry.riskWarnings && entry.riskWarnings.length > 0 ? (
                      <span className="text-danger cursor-help" title={entry.riskWarnings.join('\n')}>
                        <AlertTriangle className="w-4 h-4" />
                      </span>
                    ) : (entry.minWageApplied || 0) > 0 ? (
                      <span className="text-amber-500 cursor-help" title={`最低工资保护已触发\n应发 ¥${fmt(entry.totalPay)} → 实发 ¥${fmt(entry.minWage)}\n递延 ¥${fmt((entry.deferredSocialEmp || 0) + (entry.deferredHousingEmp || 0) + (entry.deferredMinWage || 0))} 次月补扣`}>
                        <AlertTriangle className="w-4 h-4" />
                      </span>
                    ) : (
                      <span className="text-gray-300">—</span>
                    )}
                  </td>
                  {!isArchived && (
                    <td className="py-2 px-2 text-center sticky z-10 bg-white group-hover:bg-gray-50 border-l border-gray-200" style={{ width: COL_W.action, right: rightOffsets.action }}>
                      <button
                        onClick={() => removeEmployeeMutation.mutate(entry.employeeId)}
                        className="text-gray-500 hover:text-danger p-1"
                        title="移除"
                      >
                        <Trash2 className="w-3.5 h-3.5" />
                      </button>
                    </td>
                  )}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
        {batch.entries.length > 0 && (
          <Pagination page={page} pageSize={pageSize} total={batch.entries.length} onPageChange={setPage} onPageSizeChange={() => setPage(1)} />
        )}
      </Card>

      {/* 定时发送弹窗 */}
      {showScheduleModal && (
        <div className="fixed inset-0 bg-black/30 flex items-center justify-center z-50 p-4" onClick={() => setShowScheduleModal(false)}>
          <Card className="max-w-md w-full" >
            <div onClick={(e) => e.stopPropagation()}>
              <div className="flex items-center justify-between mb-3">
                <h3 className="text-sm font-medium">定时发送工资条</h3>
                <button onClick={() => setShowScheduleModal(false)} className="text-gray-400 hover:text-gray-600"><X className="w-4 h-4" /></button>
              </div>
              <p className="text-xs text-gray-500 mb-3">设定发送时间后，工资条将在指定时间自动发布到员工端</p>
              <div className="space-y-3">
                <div>
                  <Label>发送时间</Label>
                  <Input
                    type="datetime-local"
                    value={scheduleDate}
                    onChange={(e) => setScheduleDate(e.target.value)}
                  />
                </div>
                <Button
                  size="sm"
                  onClick={() => {
                    if (!scheduleDate) { toast.error('请选择发送时间'); return }
                    schedulePayslipMutation.mutate()
                  }}
                  disabled={schedulePayslipMutation.isPending}
                >
                  {schedulePayslipMutation.isPending ? '设定中...' : '确认定时发送'}
                </Button>
              </div>
            </div>
          </Card>
        </div>
      )}

      {/* 个税计算明细弹窗 */}
      {taxDetailFor && (
        <div className="fixed inset-0 bg-black/30 flex items-center justify-center z-50 p-4" onClick={() => setTaxDetailFor(null)}>
          <Card className="max-w-lg w-full max-h-[85vh] overflow-y-auto" >
            <div onClick={(e) => e.stopPropagation()}>
              <div className="flex items-center justify-between mb-3">
                <h3 className="text-sm font-medium">个税计算明细 — {taxDetailFor.name}</h3>
                <button onClick={() => setTaxDetailFor(null)} className="text-gray-400 hover:text-gray-600"><X className="w-4 h-4" /></button>
              </div>
              {taxDetailLoading ? (
                <div className="text-center py-6 text-gray-500">计算中...</div>
              ) : taxDetailData ? (
                <div className="space-y-2 text-xs">
                  <div className="bg-gray-50 rounded-md p-2 text-gray-600">计税方式：{taxDetailData.method}</div>
                  {taxDetailData.method === '累计预扣法' ? (
                    <table className="w-full text-xs">
                      <tbody>
                        <tr className="border-b"><td className="py-1.5 text-gray-500">当年累计收入</td><td className="py-1.5 text-right font-medium">¥{fmt(taxDetailData.ytdIncome)}</td></tr>
                        <tr className="border-b"><td className="py-1.5 text-gray-500">累计减除费用（5000×{taxDetailData.month}月）</td><td className="py-1.5 text-right">-¥{fmt(taxDetailData.deductionAmount)}</td></tr>
                        <tr className="border-b"><td className="py-1.5 text-gray-500">累计个人社保</td><td className="py-1.5 text-right">-¥{fmt(taxDetailData.ytdSocialEmp)}</td></tr>
                        <tr className="border-b"><td className="py-1.5 text-gray-500">累计个人公积金</td><td className="py-1.5 text-right">-¥{fmt(taxDetailData.ytdHousingEmp)}</td></tr>
                        <tr className="border-b"><td className="py-1.5 text-gray-500">累计专项附加扣除</td><td className="py-1.5 text-right">-¥{fmt(taxDetailData.ytdSpecialDeduction)}</td></tr>
                        <tr className="border-b bg-blue-50"><td className="py-1.5 font-medium">累计应纳税所得额</td><td className="py-1.5 text-right font-medium text-primary">¥{fmt(taxDetailData.ytdTaxableIncome)}</td></tr>
                        <tr className="border-b"><td className="py-1.5 text-gray-500">累计已预扣税额</td><td className="py-1.5 text-right">¥{fmt(taxDetailData.ytdTaxDeducted)}</td></tr>
                        <tr className="bg-amber-50"><td className="py-1.5 font-medium text-danger">当月应预扣税额</td><td className="py-1.5 text-right font-bold text-danger">¥{fmt(taxDetailData.currentMonthTax)}</td></tr>
                      </tbody>
                    </table>
                  ) : (
                    <table className="w-full text-xs">
                      <tbody>
                        <tr className="border-b"><td className="py-1.5 text-gray-500">年终奖金额</td><td className="py-1.5 text-right font-medium">¥{fmt(taxDetailData.bonus)}</td></tr>
                        <tr className="bg-amber-50"><td className="py-1.5 font-medium text-danger">应纳税额</td><td className="py-1.5 text-right font-bold text-danger">¥{fmt(taxDetailData.tax)}</td></tr>
                      </tbody>
                    </table>
                  )}
                  {taxDetailData.archivedCount === 0 && taxDetailData.method === '累计预扣法' && (
                    <div className="text-xs text-gray-400 bg-gray-50 rounded p-2">
                      注：本年度暂无已归档批次，个税按当月收入全额计算。归档历史批次后累计预扣将自动生效。
                    </div>
                  )}
                  {/* 社保公积金：系统计算值 vs 实际值对比 */}
                  {(taxDetailData.systemSocialEmp !== undefined || taxDetailData.systemHousingEmp !== undefined) && (
                    <div className="border-t pt-2 mt-2">
                      <div className="font-medium text-gray-700 mb-1">社保公积金对比</div>
                      <table className="w-full text-xs">
                        <thead>
                          <tr className="border-b text-gray-500">
                            <th className="py-1 text-left">项目</th>
                            <th className="py-1 text-right">系统计算值</th>
                            <th className="py-1 text-right">实际缴纳值</th>
                            <th className="py-1 text-right">差异</th>
                          </tr>
                        </thead>
                        <tbody>
                          {taxDetailData.systemSocialEmp !== undefined && (
                            <tr className="border-b">
                              <td className="py-1 text-gray-500">个人社保</td>
                              <td className="py-1 text-right text-gray-500">¥{fmt(taxDetailData.systemSocialEmp)}</td>
                              <td className="py-1 text-right font-medium">¥{fmt(taxDetailData.actualSocialEmp)}</td>
                              <td className={`py-1 text-right ${Math.abs((taxDetailData.actualSocialEmp || 0) - (taxDetailData.systemSocialEmp || 0)) > 0.01 ? 'text-amber-600 font-medium' : 'text-gray-300'}`}>{Math.abs((taxDetailData.actualSocialEmp || 0) - (taxDetailData.systemSocialEmp || 0)) > 0.01 ? (taxDetailData.actualSocialEmp > taxDetailData.systemSocialEmp ? '+' : '') + '¥' + fmt((taxDetailData.actualSocialEmp || 0) - (taxDetailData.systemSocialEmp || 0)) : '—'}</td>
                            </tr>
                          )}
                          {taxDetailData.systemHousingEmp !== undefined && (
                            <tr className="border-b">
                              <td className="py-1 text-gray-500">个人公积金</td>
                              <td className="py-1 text-right text-gray-500">¥{fmt(taxDetailData.systemHousingEmp)}</td>
                              <td className="py-1 text-right font-medium">¥{fmt(taxDetailData.actualHousingEmp)}</td>
                              <td className={`py-1 text-right ${Math.abs((taxDetailData.actualHousingEmp || 0) - (taxDetailData.systemHousingEmp || 0)) > 0.01 ? 'text-amber-600 font-medium' : 'text-gray-300'}`}>{Math.abs((taxDetailData.actualHousingEmp || 0) - (taxDetailData.systemHousingEmp || 0)) > 0.01 ? (taxDetailData.actualHousingEmp > taxDetailData.systemHousingEmp ? '+' : '') + '¥' + fmt((taxDetailData.actualHousingEmp || 0) - (taxDetailData.systemHousingEmp || 0)) : '—'}</td>
                            </tr>
                          )}
                          {taxDetailData.systemSocialOrg !== undefined && (
                            <tr className="border-b">
                              <td className="py-1 text-gray-500">单位社保</td>
                              <td className="py-1 text-right text-gray-500">¥{fmt(taxDetailData.systemSocialOrg)}</td>
                              <td className="py-1 text-right font-medium">¥{fmt(taxDetailData.actualSocialOrg)}</td>
                              <td className={`py-1 text-right ${Math.abs((taxDetailData.actualSocialOrg || 0) - (taxDetailData.systemSocialOrg || 0)) > 0.01 ? 'text-amber-600 font-medium' : 'text-gray-300'}`}>{Math.abs((taxDetailData.actualSocialOrg || 0) - (taxDetailData.systemSocialOrg || 0)) > 0.01 ? (taxDetailData.actualSocialOrg > taxDetailData.systemSocialOrg ? '+' : '') + '¥' + fmt((taxDetailData.actualSocialOrg || 0) - (taxDetailData.systemSocialOrg || 0)) : '—'}</td>
                            </tr>
                          )}
                          {taxDetailData.systemHousingOrg !== undefined && (
                            <tr>
                              <td className="py-1 text-gray-500">单位公积金</td>
                              <td className="py-1 text-right text-gray-500">¥{fmt(taxDetailData.systemHousingOrg)}</td>
                              <td className="py-1 text-right font-medium">¥{fmt(taxDetailData.actualHousingOrg)}</td>
                              <td className={`py-1 text-right ${Math.abs((taxDetailData.actualHousingOrg || 0) - (taxDetailData.systemHousingOrg || 0)) > 0.01 ? 'text-amber-600 font-medium' : 'text-gray-300'}`}>{Math.abs((taxDetailData.actualHousingOrg || 0) - (taxDetailData.systemHousingOrg || 0)) > 0.01 ? (taxDetailData.actualHousingOrg > taxDetailData.systemHousingOrg ? '+' : '') + '¥' + fmt((taxDetailData.actualHousingOrg || 0) - (taxDetailData.systemHousingOrg || 0)) : '—'}</td>
                            </tr>
                          )}
                        </tbody>
                      </table>
                      <div className="text-xs text-gray-400 mt-1">系统计算值按参保地政策比例自动计算，实际缴纳值可手动调整以匹配社保局核定金额</div>
                    </div>
                  )}
                </div>
              ) : (
                <div className="text-center py-6 text-gray-500">无数据</div>
              )}
            </div>
          </Card>
        </div>
      )}
    </div>
  )
}

function AddEmployeeToBatch({ batchId, onClose }: { batchId: string; onClose: () => void }) {
  const queryClient = useQueryClient()
  const [selected, setSelected] = useState<string[]>([])

  const { data: employees } = useQuery<any>({
    queryKey: ['employees-for-batch'],
    queryFn: async () => {
      return await employeeApi.paged({ pageSize: 100 })
    },
  })

  const addMutation = useMutation({
    mutationFn: (employeeIds: string[]) => payrollApi.addBatchEmployees(batchId, employeeIds),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['batch-detail'] })
      queryClient.invalidateQueries({ queryKey: ['batches'] })
      onClose()
    },
  })

  return (
    <Card>
      <div className="flex items-center justify-between mb-3">
        <h3 className="text-xs font-medium">添加人员到批次</h3>
        <button onClick={onClose} className="text-gray-500">✕</button>
      </div>
      <div className="max-h-60 overflow-y-auto space-y-1">
        {employees?.items?.map((emp: any) => (
          <label key={emp.id} className="flex items-center gap-2 p-2 hover:bg-gray-50 rounded cursor-pointer">
            <input
              type="checkbox"
              checked={selected.includes(emp.id)}
              onChange={(e) => {
                if (e.target.checked) setSelected([...selected, emp.id])
                else setSelected(selected.filter(id => id !== emp.id))
              }}
            />
            <span className="text-xs">{emp.name} - {emp.department}</span>
          </label>
        ))}
      </div>
      <div className="mt-3 flex gap-2">
        <Button size="sm" onClick={() => addMutation.mutate(selected)} disabled={selected.length === 0 || addMutation.isPending}>
          {addMutation.isPending ? '添加中...' : `添加 ${selected.length} 人`}
        </Button>
        <Button size="sm" variant="secondary" onClick={onClose}>取消</Button>
      </div>
    </Card>
  )
}
