import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { ArrowDown, ArrowUp, ArrowUpDown, CircleHelp } from 'lucide-react';
import { OperationActions } from '../../../common/OperationActions';
import type { HydrogenFeeRecord, LeaseLedgerRow, MaintenanceLedgerRecord, ReceiptRecord, ReturnSettlementFeeRecord } from '../types';
import {
  calcMonthlyIncome,
  calcUnreceived,
  canStaffEditRow,
  displayMoney,
  displayText,
  formatBillingYearMonth,
  formatMoney,
  getHydrogenFeesForBill,
  getLinkedReceiptsForRow,
  getMaintenanceLedgerRecordsForBill,
  getReturnSettlementFeesForBill,
  isBlankDisplayValue,
} from '../utils/ledger';
import { LEASE_TABLE_COLUMNS, LEFT_STICKY_LAST_COLUMN_KEY, STATUS_CLASS, ARCHIVE_STATUS_CLASS, type LeaseTableColumn } from './tableColumns';
import { FilterPickerField } from '../../vehicle-management/components/FilterPickerField';
import { EXEMPTION_POLICY_OPTIONS } from '../utils/constants';
import { resolveColumnHeaderTip } from '../columnHeaderTips';
import { LedgerDetailPopover, LedgerDetailTable } from './LedgerDetailPopover';
import { LedgerLineItemsCell } from './LedgerLineItemsCell';
import { OperationHistoryModal } from './OperationHistoryModal';

interface LeaseLedgerTableProps {
  records: LeaseLedgerRow[];
  selectedIds: Set<string>;
  loading?: boolean;
  returnSettlementFees: ReturnSettlementFeeRecord[];
  maintenanceLedgerRecords: MaintenanceLedgerRecord[];
  receipts: ReceiptRecord[];
  hydrogenFees: HydrogenFeeRecord[];
  onToggleRow: (id: string, checked: boolean) => void;
  onToggleAll: (checked: boolean) => void;
  onEdit: (record: LeaseLedgerRow) => void;
  onSave: (record: LeaseLedgerRow) => void;
  editingRowId: string | null;
  onLinkReceipt: (record: LeaseLedgerRow) => void;
  onOtherIncomeItemsChange: (recordId: string, items: LeaseLedgerRow['otherIncomeItems']) => void;
  onOtherDeductionItemsChange: (recordId: string, items: LeaseLedgerRow['otherDeductionItems']) => void;
  onOtherCostChange: (recordId: string, value: number) => void;
  onBrokerageFeeChange: (recordId: string, value: number) => void;
  onRemarkChange: (recordId: string, value: string) => void;
  onDeductionDetailChange: (recordId: string, value: string) => void;
  onExemptionPolicyChange: (recordId: string, value: string) => void;
}

type SortDir = 'asc' | 'desc';

const CHECK_COL_WIDTH = 48;
const ACTION_COL_WIDTH = 168;

function buildLeftStickyLayout(columnWidths: Record<string, number>) {
  const keys: string[] = [];
  for (const col of LEASE_TABLE_COLUMNS) {
    keys.push(col.key);
    if (col.key === LEFT_STICKY_LAST_COLUMN_KEY) break;
  }
  const keySet = new Set(keys);
  let left = CHECK_COL_WIDTH;
  const leftOffsets: Record<string, number> = {};
  for (const key of keys) {
    leftOffsets[key] = left;
    left += columnWidths[key] ?? DEFAULT_COLUMN_WIDTHS[key] ?? 100;
  }
  return { keys, keySet, leftOffsets, lastKey: LEFT_STICKY_LAST_COLUMN_KEY };
}

function stickyLeftClass(colKey: string, layout: ReturnType<typeof buildLeftStickyLayout>): string {
  if (!layout.keySet.has(colKey)) return '';
  const classes = ['sticky-col-left'];
  if (colKey === layout.lastKey) classes.push('sticky-col-left-last');
  return classes.join(' ');
}

function stickyLeftStyle(
  colKey: string,
  layout: ReturnType<typeof buildLeftStickyLayout>,
  isHeader: boolean,
): React.CSSProperties | undefined {
  const index = layout.keys.indexOf(colKey);
  if (index < 0) return undefined;
  return {
    left: layout.leftOffsets[colKey],
    zIndex: (isHeader ? 4 : 3) + index,
  };
}

const CHECK_STICKY_HEAD_STYLE: React.CSSProperties = { left: 0, zIndex: 3 };
const CHECK_STICKY_BODY_STYLE: React.CSSProperties = { left: 0, zIndex: 2 };

const DEFAULT_COLUMN_WIDTHS: Record<string, number> = {
  status: 88,
  billingYearMonth: 88,
  maintainerName: 88,
  archiveStatus: 88,
  maintainerTime: 140,
  dept: 120,
  paymentDueDate: 108,
  plateNo: 120,
  customerName: 160,
  valueAddedService: 96,
  exemptionPolicy: 176,
  pickupDate: 108,
  contractStartDate: 116,
  contractEndDate: 116,
  periodStartDate: 108,
  returnDate: 108,
  avgDays: 88,
  deposit: 88,
  contractRent: 112,
  receivableTotal: 112,
  receivableRent: 112,
  monthlyIncome: 112,
  monthlyRent: 112,
  maintenanceIncome: 112,
  insuranceSurcharge: 112,
  opsFeeIncome: 100,
  otherIncome: 100,
  mileageDeduction: 112,
  otherDeduction: 112,
  receivedAmount: 112,
  unreceived: 112,
  invoiceDate: 108,
  actualPaymentDate: 116,
  paymentMethod: 96,
  vehicleStandardCost: 112,
  vehicleActualCost: 112,
  insuranceFee: 96,
  hydrogenFee: 96,
  opsFeeCost: 100,
  brokerageFee: 96,
  otherCost: 96,
  totalCost: 112,
  profitLoss: 112,
  gpsMileage: 96,
  otherDeductionDetail: 160,
  assetOwner: 140,
  signingCompany: 160,
  remark: 140,
};

const HEADER_TIP_MAX_WIDTH = 280;
const HEADER_TIP_VIEWPORT_MARGIN = 12;

function TableHeaderTip({ tip, title }: { tip: string; title: string }) {
  const anchorRef = useRef<HTMLSpanElement>(null);
  const [visible, setVisible] = useState(false);
  const [style, setStyle] = useState<React.CSSProperties>({});

  const reposition = useCallback(() => {
    const el = anchorRef.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    const maxWidth = Math.min(HEADER_TIP_MAX_WIDTH, window.innerWidth - HEADER_TIP_VIEWPORT_MARGIN * 2);
    let left = rect.left;
    left = Math.max(
      HEADER_TIP_VIEWPORT_MARGIN,
      Math.min(left, window.innerWidth - HEADER_TIP_VIEWPORT_MARGIN - maxWidth),
    );
    setStyle({
      top: rect.bottom + 6,
      left,
      maxWidth,
    });
  }, []);

  useEffect(() => {
    if (!visible) return undefined;
    reposition();
    window.addEventListener('scroll', reposition, true);
    window.addEventListener('resize', reposition);
    return () => {
      window.removeEventListener('scroll', reposition, true);
      window.removeEventListener('resize', reposition);
    };
  }, [visible, reposition]);

  return (
    <>
      <span
        ref={anchorRef}
        className="ldb-th-tip"
        tabIndex={0}
        aria-label={`${title}说明`}
        onClick={(e) => e.stopPropagation()}
        onMouseEnter={() => {
          reposition();
          setVisible(true);
        }}
        onMouseLeave={() => setVisible(false)}
        onFocus={() => {
          reposition();
          setVisible(true);
        }}
        onBlur={() => setVisible(false)}
      >
        <CircleHelp size={14} aria-hidden />
      </span>
      {visible && createPortal(
        <span className="ldb-kpi-tooltip ldb-kpi-tooltip--portal" role="tooltip" style={style}>
          {tip}
        </span>,
        document.body,
      )}
    </>
  );
}

function HeaderWithTip({
  title,
  tip,
  sortable,
  sortActive,
  sortDir,
  onSort,
}: {
  title: string;
  tip?: string;
  sortable?: boolean;
  sortActive?: boolean;
  sortDir?: SortDir;
  onSort?: () => void;
}) {
  const label = (
    <span className="ldb-th-label">
      {title}
      {tip && <TableHeaderTip tip={tip} title={title} />}
    </span>
  );

  if (!sortable) {
    return tip ? <span className="ldb-th-with-tip">{label}</span> : <>{title}</>;
  }

  return (
    <button type="button" className={`ldb-th-sort ${sortActive ? 'is-active' : ''}`} onClick={onSort}>
      {label}
      <span className="ldb-th-sort-icon" aria-hidden>
        {sortActive
          ? (sortDir === 'asc' ? <ArrowUp size={14} /> : <ArrowDown size={14} />)
          : <ArrowUpDown size={14} />}
      </span>
    </button>
  );
}

function MoneyInput({
  value,
  placeholder,
  required,
  ariaLabel,
  onChange,
}: {
  value: number;
  placeholder?: string;
  required?: boolean;
  ariaLabel: string;
  onChange: (v: number) => void;
}) {
  return (
    <input
      type="number"
      className={`vm-input ldb-income-input tabular-nums ${required && !value ? 'ldb-input-required' : ''}`}
      value={value || ''}
      min={0}
      step={0.01}
      placeholder={placeholder}
      aria-label={ariaLabel}
      aria-required={required}
      onChange={(e) => onChange(Number(e.target.value) || 0)}
    />
  );
}

function TextInput({
  value,
  placeholder,
  ariaLabel,
  onChange,
}: {
  value: string;
  placeholder?: string;
  ariaLabel: string;
  onChange: (v: string) => void;
}) {
  return (
    <input
      type="text"
      className="vm-input ldb-text-input"
      value={value}
      placeholder={placeholder}
      aria-label={ariaLabel}
      onChange={(e) => onChange(e.target.value)}
    />
  );
}

function getSortValue(row: LeaseLedgerRow, col: LeaseTableColumn): string | number {
  switch (col.render) {
    case 'monthly-income':
      return calcMonthlyIncome(row);
    case 'stack-dept':
      return row.dept;
    case 'stack-plate':
      return row.plateNo;
    case 'stack-customer':
      return row.customerName;
    case 'status':
      return row.status;
    case 'year-month':
      return row.year * 100 + row.month;
    default:
      break;
  }
  if (col.key === 'unreceived') return calcUnreceived(row);
  if (col.key === 'profitLoss') return row.profitLoss;
  const raw = row[col.key as keyof LeaseLedgerRow];
  if (typeof raw === 'number') return raw;
  if (typeof raw === 'string') return raw;
  return '';
}

function renderCell(
  row: LeaseLedgerRow,
  col: LeaseTableColumn,
  index: number,
  ctx: {
    returnSettlementFees: ReturnSettlementFeeRecord[];
    maintenanceLedgerRecords: MaintenanceLedgerRecord[];
    receipts: ReceiptRecord[];
    hydrogenFees: HydrogenFeeRecord[];
    canEdit: boolean;
    onLinkReceipt: (record: LeaseLedgerRow) => void;
    onOtherIncomeItemsChange: (recordId: string, items: LeaseLedgerRow['otherIncomeItems']) => void;
    onOtherDeductionItemsChange: (recordId: string, items: LeaseLedgerRow['otherDeductionItems']) => void;
    onOtherCostChange: (recordId: string, value: number) => void;
    onBrokerageFeeChange: (recordId: string, value: number) => void;
    onRemarkChange: (recordId: string, value: string) => void;
    onDeductionDetailChange: (recordId: string, value: string) => void;
    onExemptionPolicyChange: (recordId: string, value: string) => void;
  },
) {
  switch (col.render) {
    case 'status':
      return <span className={`ldb-status-tag ${STATUS_CLASS[row.status]}`}>{row.status}</span>;
    case 'archive-status':
      return (
        <span className={`ldb-status-tag ${ARCHIVE_STATUS_CLASS[row.archiveStatus]}`}>
          {row.archiveStatus}
        </span>
      );
    case 'year-month':
      return (
        <span className="tabular-nums">{formatBillingYearMonth(row.year, row.month)}</span>
      );
    case 'value-added-service':
      return (
        <span className="ldb-cell-multiline" title={row.valueAddedService}>
          {displayText(row.valueAddedService)}
        </span>
      );
    case 'exemption-policy':
      if (!ctx.canEdit) {
        return <span title={row.exemptionPolicy}>{displayText(row.exemptionPolicy)}</span>;
      }
      return (
        <FilterPickerField
          value={row.exemptionPolicy}
          options={[...EXEMPTION_POLICY_OPTIONS]}
          placeholder="请选择享免政策"
          ariaLabel={`${row.plateNo} 享免政策`}
          onChange={(value) => ctx.onExemptionPolicyChange(row.id, value)}
        />
      );
    case 'stack-dept':
      return (
        <div className="ldb-cell-stack">
          <span className="ldb-cell-primary">{displayText(row.dept)}</span>
          <span className="ldb-cell-secondary">{displayText(row.salesman)}</span>
        </div>
      );
    case 'stack-plate': {
      const vehicleLine = row.vehicleType || [row.vehicleBrand, row.vehicleModel].filter(Boolean).join('');
      return (
        <div className="ldb-cell-stack">
          <span className="ldb-cell-primary tabular-nums">{displayText(row.plateNo)}</span>
          <span className="ldb-cell-secondary" title={vehicleLine}>{displayText(vehicleLine)}</span>
        </div>
      );
    }
    case 'stack-customer':
      return (
        <div className="ldb-cell-stack">
          <span className="ldb-cell-primary" title={row.customerName}>{displayText(row.customerName)}</span>
          <span className="ldb-cell-secondary" title={row.projectName}>{displayText(row.projectName)}</span>
        </div>
      );
    case 'deposit':
      if (isBlankDisplayValue(row.deposit)) return '';
      return typeof row.deposit === 'number' ? formatMoney(row.deposit) : displayText(row.deposit);
    case 'asset-owner':
      return <span title={row.assetOwner}>{displayText(row.assetOwner)}</span>;
    case 'signing-company':
      return <span className="ldb-cell-readonly" title="租赁合同签订时甲方名称">{displayText(row.signingCompany)}</span>;
    case 'monthly-income':
      return (
        <span className="ldb-computed-val" title="月度租金+里程维保收入+保险上浮费+运维费+其他收入−里程减免−其他减免">
          {formatMoney(calcMonthlyIncome(row))}
        </span>
      );
    case 'maintenance-income':
      return (
        <span
          className={row.maintenancePackageEnabled ? 'ldb-computed-val' : 'ldb-cell-readonly'}
          title={
            row.maintenancePackageEnabled
              ? `单价 ${row.maintenanceUnitPricePerKm} 元/km × ${row.mileage} km`
              : '合同未勾选维保包干，默认 0 不可编辑'
          }
        >
          {formatMoney(row.maintenanceIncome)}
        </span>
      );
    case 'insurance-surcharge':
      return (
        <span className="ldb-computed-val">{formatMoney(row.insuranceSurcharge)}</span>
      );
    case 'detail-popover-maintenance-income': {
      const fees = getReturnSettlementFeesForBill(row, ctx.returnSettlementFees);
      const tableRows = fees.map((item) => ({
        date: item.settlementDate.split(' ')[0],
        plateNo: item.plateNo,
        feeItem: item.feeItem,
        amount: formatMoney(item.amount),
      }));
      return (
        <LedgerDetailPopover
          label={formatMoney(row.opsFeeIncome)}
          title="还车应结款运维费明细"
          triggerClassName="tabular-nums"
        >
            <LedgerDetailTable
              columns={[
                { key: 'date', title: '日期' },
                { key: 'plateNo', title: '车牌号' },
                { key: 'feeItem', title: '费用项' },
                { key: 'amount', title: '金额', align: 'right' },
              ]}
            rows={tableRows}
          />
        </LedgerDetailPopover>
      );
    }
    case 'detail-popover-maintenance-cost': {
      const records = getMaintenanceLedgerRecordsForBill(row, ctx.maintenanceLedgerRecords);
      const tableRows = records.map((item) => ({
        date: item.repairDate.split(' ')[0],
        plateNo: item.plateNo,
        type: item.type,
        amount: formatMoney(item.totalCost),
      }));
      return (
        <LedgerDetailPopover
          label={formatMoney(row.opsFeeCost)}
          title="维修保养明细"
          triggerClassName="tabular-nums"
        >
          <LedgerDetailTable
            columns={[
              { key: 'date', title: '日期' },
              { key: 'plateNo', title: '车牌号' },
              { key: 'type', title: '类型' },
              { key: 'amount', title: '费用总计（含税）', align: 'right' },
            ]}
            rows={tableRows}
          />
        </LedgerDetailPopover>
      );
    }
    case 'line-items-income':
      return (
        <LedgerLineItemsCell
          items={row.otherIncomeItems ?? []}
          total={row.otherIncome}
          canEdit={ctx.canEdit}
          reasonLabel="收入来源"
          title="其他收入明细"
          ariaLabel={`${row.plateNo} 其他收入`}
          onChange={(items) => ctx.onOtherIncomeItemsChange(row.id, items)}
        />
      );
    case 'line-items-deduction':
      return (
        <LedgerLineItemsCell
          items={row.otherDeductionItems ?? []}
          total={row.otherDeduction}
          canEdit={ctx.canEdit}
          reasonLabel="减免原因"
          title="其他减免金额明细"
          ariaLabel={`${row.plateNo} 其他减免`}
          onChange={(items) => ctx.onOtherDeductionItemsChange(row.id, items)}
        />
      );
    case 'other-cost':
      if (!ctx.canEdit) return displayMoney(row.otherCost, true);
      return (
        <MoneyInput
          value={row.otherCost}
          placeholder={row.otherIncome > 0 ? '必填' : '成本侧其他'}
          required={row.otherIncome > 0}
          ariaLabel={`${row.plateNo} 其他成本`}
          onChange={(v) => ctx.onOtherCostChange(row.id, v)}
        />
      );
    case 'brokerage-fee':
      if (!ctx.canEdit) return displayMoney(row.brokerageFee, true);
      return (
        <MoneyInput
          value={row.brokerageFee}
          placeholder="手工填写"
          ariaLabel={`${row.plateNo} 居间费`}
          onChange={(v) => ctx.onBrokerageFeeChange(row.id, v)}
        />
      );
    case 'received-amount': {
      const linked = getLinkedReceiptsForRow(row, ctx.receipts);
      const showReceiptLink = row.status !== '已收款';
      const tableRows = linked.map((item) => ({
        time: item.arrivalTime,
        customer: item.customerName,
        amount: formatMoney(item.amount),
        remark: item.remark ?? '',
      }));
      return (
        <LedgerDetailPopover
          label={formatMoney(row.receivedAmount)}
          title="收款明细"
          footer={showReceiptLink ? (
            <button type="button" className="vm-btn vm-btn-secondary" onClick={() => ctx.onLinkReceipt(row)} data-vm-icon="plus">
              关联收款
            </button>
          ) : undefined}
        >
          <LedgerDetailTable
            columns={[
              { key: 'time', title: '收款时间' },
              { key: 'customer', title: '客户名称' },
              { key: 'amount', title: '收款金额', align: 'right' },
              { key: 'remark', title: '备注' },
            ]}
            rows={tableRows}
            emptyText="尚未关联收款记录"
          />
        </LedgerDetailPopover>
      );
    }
    case 'invoice-date':
      return <span className="ldb-cell-readonly tabular-nums">{displayText(row.invoiceDate)}</span>;
    case 'vehicle-standard-cost':
      return (
        <span className="ldb-cell-readonly" title="车型明细维护的固定标准成本">
          {formatMoney(row.vehicleStandardCost)}
        </span>
      );
    case 'vehicle-actual-cost':
      return (
        <span className="ldb-computed-val" title={`车辆标准成本 × 实际天数（${row.actualLeaseDays ?? 0} 天）`}>
          {formatMoney(row.vehicleActualCost)}
        </span>
      );
    case 'insurance-readonly':
      return (
        <span className="ldb-computed-val" title="保险采购金额/生效天数 × 当月实际使用天数">
          {displayMoney(row.insuranceFee, true)}
        </span>
      );
    case 'detail-popover-hydrogen': {
      const records = getHydrogenFeesForBill(row, ctx.hydrogenFees);
      const tableRows = records.map((item) => ({
        refuelDate: item.refuelDate ?? `${item.year}-${String(item.month).padStart(2, '0')}-01`,
        plateNo: item.plateNo,
        stationName: item.stationName ?? '—',
        volume: item.volume != null ? String(item.volume) : '—',
        unitCost: item.unitCost != null ? formatMoney(item.unitCost) : '—',
        totalCost: formatMoney(item.totalCost ?? item.amount),
        borneBy: item.borneBy ?? '我司承担',
      }));
      return (
        <LedgerDetailPopover
          label={displayMoney(row.hydrogenFee, true)}
          title="车辆氢费明细"
          triggerClassName="tabular-nums"
        >
          <LedgerDetailTable
            columns={[
              { key: 'refuelDate', title: '加氢日期' },
              { key: 'plateNo', title: '车牌号' },
              { key: 'stationName', title: '加氢站名称' },
              { key: 'volume', title: '加氢量' },
              { key: 'unitCost', title: '成本单价' },
              { key: 'totalCost', title: '成本总价', align: 'right' },
              { key: 'borneBy', title: '承担方式' },
            ]}
            rows={tableRows}
          />
        </LedgerDetailPopover>
      );
    }
    case 'gps-mileage':
      return (
        <span className="ldb-computed-val tabular-nums" title="BI 里程 · 账单时间段">
          {displayText(row.gpsMileage)}
        </span>
      );
    case 'deduction-detail':
      if (!ctx.canEdit) return displayText(row.otherDeductionDetail);
      return (
        <TextInput
          value={row.otherDeductionDetail}
          placeholder="减免明细"
          ariaLabel={`${row.plateNo} 其他减免明细`}
          onChange={(v) => ctx.onDeductionDetailChange(row.id, v)}
        />
      );
    case 'remark-text':
      if (!ctx.canEdit) return displayText(row.remark);
      return (
        <TextInput
          value={row.remark}
          placeholder="备注"
          ariaLabel={`${row.plateNo} 备注`}
          onChange={(v) => ctx.onRemarkChange(row.id, v)}
        />
      );
    case 'maintainer-time':
      return <span className="tabular-nums">{displayText(row.maintainerTime)}</span>;
    default:
      break;
  }

  if (col.key === 'unreceived') {
    return (
      <span className="ldb-col-unreceived-warn">{formatMoney(calcUnreceived(row))}</span>
    );
  }

  if (col.key === 'avgDays') {
    return (
      <span className="tabular-nums" title="本月在租天数/当月天数">
        {row.avgDays.toFixed(2)}
      </span>
    );
  }

  if (col.key === 'mileageDeduction') {
    return (
      <span className="ldb-computed-val" title="根据 GPS 里程自动计算">
        {formatMoney(row.mileageDeduction)}
      </span>
    );
  }

  if (col.key === 'profitLoss') {
    const val = row.profitLoss;
    return (
      <span className={val >= 0 ? 'ldb-kpi-val--profit' : 'ldb-kpi-val--loss'}>
        {formatMoney(val)}
      </span>
    );
  }

  const raw = row[col.key as keyof LeaseLedgerRow];
  if (col.money && typeof raw === 'number') return formatMoney(raw);
  if (isBlankDisplayValue(raw)) return '';
  return String(raw);
}

function TableSkeleton() {
  return (
    <div className="ldb-table-skeleton" aria-busy="true" aria-label="表格加载中">
      {Array.from({ length: 6 }, (_, i) => (
        <div key={i} className="ldb-skeleton-row" />
      ))}
    </div>
  );
}

export function LeaseLedgerTable({
  records,
  selectedIds,
  loading = false,
  returnSettlementFees,
  maintenanceLedgerRecords,
  receipts,
  hydrogenFees,
  onToggleRow,
  onToggleAll,
  onEdit,
  onSave,
  editingRowId,
  onLinkReceipt,
  onOtherIncomeItemsChange,
  onOtherDeductionItemsChange,
  onOtherCostChange,
  onBrokerageFeeChange,
  onRemarkChange,
  onDeductionDetailChange,
  onExemptionPolicyChange,
}: LeaseLedgerTableProps) {
  const [sortKey, setSortKey] = useState<string | null>(null);
  const [sortDir, setSortDir] = useState<SortDir>('asc');
  const [historyRow, setHistoryRow] = useState<LeaseLedgerRow | null>(null);
  const [columnWidths, setColumnWidths] = useState<Record<string, number>>(() => ({ ...DEFAULT_COLUMN_WIDTHS }));
  const resizeRef = useRef<{ key: string; startX: number; startWidth: number } | null>(null);
  const leftStickyLayout = useMemo(
    () => buildLeftStickyLayout(columnWidths),
    [columnWidths],
  );

  const sortedRecords = useMemo(() => {
    if (!sortKey) return records;
    const col = LEASE_TABLE_COLUMNS.find((c) => c.key === sortKey);
    if (!col) return records;
    const dir = sortDir === 'asc' ? 1 : -1;
    return [...records].sort((a, b) => {
      const av = getSortValue(a, col);
      const bv = getSortValue(b, col);
      if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * dir;
      return String(av).localeCompare(String(bv), 'zh-CN') * dir;
    });
  }, [records, sortKey, sortDir]);

  const allSelected = sortedRecords.length > 0 && sortedRecords.every((r) => selectedIds.has(r.id));
  const someSelected = sortedRecords.some((r) => selectedIds.has(r.id)) && !allSelected;
  const headerCheckRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (headerCheckRef.current) {
      headerCheckRef.current.indeterminate = someSelected;
    }
  }, [someSelected]);

  const handleSort = useCallback((key: string) => {
    setSortKey((prev) => {
      if (prev === key) {
        setSortDir((d) => (d === 'asc' ? 'desc' : 'asc'));
        return key;
      }
      setSortDir('asc');
      return key;
    });
  }, []);

  const handleResizeStart = useCallback((key: string, event: React.MouseEvent) => {
    event.preventDefault();
    event.stopPropagation();
    resizeRef.current = {
      key,
      startX: event.clientX,
      startWidth: columnWidths[key] ?? DEFAULT_COLUMN_WIDTHS[key] ?? 100,
    };
  }, [columnWidths]);

  useEffect(() => {
    const onMove = (event: MouseEvent) => {
      if (!resizeRef.current) return;
      const delta = event.clientX - resizeRef.current.startX;
      const next = Math.max(56, resizeRef.current.startWidth + delta);
      setColumnWidths((prev) => ({ ...prev, [resizeRef.current!.key]: next }));
    };
    const onUp = () => {
      resizeRef.current = null;
    };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    return () => {
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
    };
  }, []);

  if (loading) {
    return (
      <div className="vm-table-wrap ldb-table-wrap" data-annotation-id="lbl-table">
        <TableSkeleton />
      </div>
    );
  }

  if (!sortedRecords.length) {
    return <div className="vm-empty" data-annotation-id="lbl-table"><p>暂无符合条件的台账记录</p></div>;
  }

  const tableMinWidth = CHECK_COL_WIDTH + ACTION_COL_WIDTH + LEASE_TABLE_COLUMNS.reduce(
    (sum, col) => sum + (columnWidths[col.key] ?? DEFAULT_COLUMN_WIDTHS[col.key] ?? 100),
    0,
  );

  return (
    <div className="vm-table-wrap ldb-table-wrap" data-annotation-id="lbl-table">
      <table className="vm-table ldb-table" style={{ minWidth: tableMinWidth }}>
        <colgroup>
          <col style={{ width: CHECK_COL_WIDTH }} />
          {LEASE_TABLE_COLUMNS.map((col) => (
            <col key={col.key} style={{ width: columnWidths[col.key] ?? DEFAULT_COLUMN_WIDTHS[col.key] ?? 100 }} />
          ))}
          <col style={{ width: ACTION_COL_WIDTH }} />
        </colgroup>
        <thead>
          <tr>
            <th
              className="ldb-col-check sticky-col-left"
              style={CHECK_STICKY_HEAD_STYLE}
              data-annotation-id="lbl-col-check"
            >
              <input
                ref={headerCheckRef}
                type="checkbox"
                className="vm-checkbox"
                checked={allSelected}
                onChange={(e) => onToggleAll(e.target.checked)}
                aria-label="全选"
              />
            </th>
            {LEASE_TABLE_COLUMNS.map((col) => {
              const sortable = col.sortable !== false;
              const headerTip = resolveColumnHeaderTip(col.annotationId, col.headerTip);
              return (
                <th
                  key={col.key}
                  className={[
                    col.money ? 'ldb-col-money' : '',
                    col.className,
                    stickyLeftClass(col.key, leftStickyLayout),
                    'ldb-col-resizable',
                  ].filter(Boolean).join(' ')}
                  style={stickyLeftStyle(col.key, leftStickyLayout, true)}
                  data-annotation-id={col.annotationId}
                >
                  <div className="ldb-th-inner">
                    <HeaderWithTip
                      title={col.title}
                      tip={headerTip}
                      sortable={sortable}
                      sortActive={sortKey === col.key}
                      sortDir={sortDir}
                      onSort={() => handleSort(col.key)}
                    />
                    <span
                      className="ldb-col-resize-handle"
                      role="separator"
                      aria-orientation="vertical"
                      aria-label={`调整${col.title}列宽`}
                      onMouseDown={(e) => handleResizeStart(col.key, e)}
                    />
                  </div>
                </th>
              );
            })}
            <th className="sticky-col-right ldb-col-actions" data-annotation-id="lbl-col-actions">操作</th>
          </tr>
        </thead>
        <tbody>
          {sortedRecords.map((row, index) => {
            const canEdit = canStaffEditRow(row);
            const cellCtx = {
              returnSettlementFees,
              maintenanceLedgerRecords,
              receipts,
              hydrogenFees,
              canEdit,
              onLinkReceipt,
              onOtherIncomeItemsChange,
              onOtherDeductionItemsChange,
              onOtherCostChange,
              onBrokerageFeeChange,
              onRemarkChange,
              onDeductionDetailChange,
              onExemptionPolicyChange,
            };

            return (
              <tr key={row.id}>
                <td className="ldb-col-check sticky-col-left" style={CHECK_STICKY_BODY_STYLE}>
                  <input
                    type="checkbox"
                    className="vm-checkbox"
                    checked={selectedIds.has(row.id)}
                    onChange={(e) => onToggleRow(row.id, e.target.checked)}
                    aria-label={`选择第 ${index + 1} 行`}
                  />
                </td>
                {LEASE_TABLE_COLUMNS.map((col) => (
                  <td
                    key={col.key}
                    className={[
                      col.money ? 'ldb-col-money tabular-nums' : '',
                      col.className,
                      stickyLeftClass(col.key, leftStickyLayout),
                    ].filter(Boolean).join(' ')}
                    style={stickyLeftStyle(col.key, leftStickyLayout, false)}
                  >
                    {renderCell(row, col, index, cellCtx)}
                  </td>
                ))}
                <td className="sticky-col-right ldb-col-actions">
                  <OperationActions
                    edit={canEdit ? { onClick: () => onEdit(row) } : undefined}
                    more={[
                      {
                        key: 'history',
                        label: '操作记录',
                        onClick: () => setHistoryRow(row),
                      },
                      ...(editingRowId === row.id && canEdit
                        ? [{
                            key: 'save',
                            label: '保存',
                            onClick: () => onSave(row),
                          }]
                        : []),
                    ]}
                  />
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
      <OperationHistoryModal
        open={Boolean(historyRow)}
        plateNo={historyRow?.plateNo ?? ''}
        entries={historyRow?.operationHistory ?? []}
        onClose={() => setHistoryRow(null)}
      />
    </div>
  );
}
