import { useCallback, useState, useEffect, useMemo } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { BatchIdCombobox, type BatchIdComboboxOption } from '@/components/shared/BatchIdCombobox';
import { Badge } from '@/components/ui/badge';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import { Skeleton } from '@/components/ui/skeleton';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { DataTable, Column } from '@/components/shared/DataTable';
import { ColumnConfigDialog } from '@/components/shared/ColumnConfigDialog';
import { Tabs, TabsContent } from '@/components/ui/tabs';
import { ExportDialog } from '@/components/shared/ExportDialog';
import {
  FailureResolutionDialog,
  type FailureResolutionSubmission,
} from '@/components/shared/FailureResolutionDialog';
import {
  openFailureSupplementPrintView,
  type FailureSupplementExportResult,
} from '@/components/shared/FailureSupplementPrintView';
import { searchFieldClassName } from '@/components/shared/searchFieldStyles';
import { SearchLaunchDialog } from '@/components/shared/SearchLaunchDialog';
import { StandardizationTaskMonitor } from '@/components/shared/StandardizationTaskMonitor';
import { RefreshCw, Download, Search, Pencil, LoaderCircle, Square, Trash2, Radar, Sparkles, SlidersHorizontal } from 'lucide-react';
import { retryStandardizationFailures, stopStandardizeTask } from '@/services/automationService';
import {
  createManualStandardizedResultsFromFailures,
  fetchStandardizationFailures,
  fetchStandardizationTasks,
  fetchStandardized,
  type StandardizationFailureItem,
  type StandardizationTaskRecord,
  updateStandardized,
  deleteStandardizedByIds,
  deleteStandardizationFailuresByIds,
  deleteStandardizationTaskById,
} from '@/services/standardizedService';
import { StandardizedMaterial } from '@/types';
import { formatDate } from '@/lib/dateUtils';
import { resolveStoredColumnConfig, saveStoredColumnConfig } from '@/lib/columnConfigStorage';
import { getStandardizationTaskProgressCountLabel } from '@/lib/standardizationTaskProgress';
import { useProcurementBatchOptions } from '@/hooks/useProcurementBatchOptions';
import { useAuth } from '@/hooks/useAuth';
import { toast } from 'sonner';

const STANDARDIZED_COLUMN_OPTIONS = [
  { key: 'batch_id', label: '批次ID' },
  { key: 'original_name', label: '原始名称' },
  { key: 'standardized_name', label: '标准化名称' },
  { key: 'original_spec', label: '原始规格' },
  { key: 'standardized_spec', label: '标准化规格' },
  { key: 'brand', label: '品牌' },
  { key: 'category', label: '分类(材质/用途)' },
  { key: 'quantity', label: '数量' },
  { key: 'notes', label: '备注/附件' },
  { key: 'delivery_address', label: '收货地址' },
  { key: 'invoice_type', label: '发票种类' },
  { key: 'primary_search_term', label: '主搜索词' },
  { key: 'alt_search_terms', label: '备选搜索词' },
  { key: 'product_type', label: '产品类型' },
  { key: 'llm_model', label: 'LLM模型' },
  { key: 'created_at', label: '创建时间' },
] as const;

const STANDARDIZED_STORAGE_KEY = 'autoprocurement.standardized.columns';
const STANDARDIZED_COLUMN_KEYS = STANDARDIZED_COLUMN_OPTIONS.map(column => column.key);

const STANDARDIZATION_FAILURE_COLUMN_OPTIONS = [
  { key: 'batch_id', label: '批次ID' },
  { key: 'original_name', label: '原始名称' },
  { key: 'original_spec', label: '原始规格' },
  { key: 'failure_reason', label: '失败原因' },
  { key: 'human_fix_hint', label: '补充建议' },
  { key: 'updated_at', label: '更新时间' },
] as const;

const STANDARDIZATION_FAILURE_STORAGE_KEY = 'autoprocurement.standardized-failures.columns';
const STANDARDIZATION_FAILURE_COLUMN_KEYS = STANDARDIZATION_FAILURE_COLUMN_OPTIONS.map(column => column.key);

type StandardizedTabValue = 'success' | 'failed' | 'tasks';

function getInitialStandardizedColumnConfig() {
  const config = resolveStoredColumnConfig({
    storageKey: STANDARDIZED_STORAGE_KEY,
    allKeys: STANDARDIZED_COLUMN_KEYS,
    defaultVisibleKeys: STANDARDIZED_COLUMN_KEYS,
  });

  const visibleCols = new Set(config.visibleCols);
  visibleCols.delete('material_id');

  const orderedKeys = config.orderedKeys.filter(key => key !== 'material_id' && key !== 'invoice_type');
  const deliveryAddressIndex = orderedKeys.indexOf('delivery_address');

  if (deliveryAddressIndex >= 0) {
    orderedKeys.splice(deliveryAddressIndex + 1, 0, 'invoice_type');
  } else {
    orderedKeys.push('invoice_type');
  }

  return {
    visibleCols,
    orderedKeys,
  };
}

function getInitialStandardizationFailureColumnConfig() {
  return resolveStoredColumnConfig({
    storageKey: STANDARDIZATION_FAILURE_STORAGE_KEY,
    allKeys: STANDARDIZATION_FAILURE_COLUMN_KEYS,
    defaultVisibleKeys: STANDARDIZATION_FAILURE_COLUMN_KEYS,
  });
}

export default function Standardized() {
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();
  const [loading, setLoading] = useState(true);
  const [search, setSearch] = useState('');
  const [batchId, setBatchId] = useState('');
  const [selected, setSelected] = useState<StandardizedMaterial | null>(null);
  const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set());
  const [exportOpen, setExportOpen] = useState(false);
  const [configOpen, setConfigOpen] = useState(false);
  const [allData, setAllData] = useState<StandardizedMaterial[]>([]);
  const [editing, setEditing] = useState<StandardizedMaterial | null>(null);
  const [editForm, setEditForm] = useState<Partial<StandardizedMaterial>>({});
  const [saving, setSaving] = useState(false);
  const [deleting, setDeleting] = useState(false);
  const [searchLaunchOpen, setSearchLaunchOpen] = useState(false);
  const [searchLaunchMode, setSearchLaunchMode] = useState<'batch' | 'selected'>('batch');
  const [columnConfig, setColumnConfig] = useState(() => getInitialStandardizedColumnConfig());
  const { batchOptions } = useProcurementBatchOptions();
  const { visibleCols, orderedKeys: orderedColKeys } = columnConfig;
  const activeTabParam = searchParams.get('tab');
  const activeTab: StandardizedTabValue = activeTabParam === 'failed' || activeTabParam === 'tasks' ? activeTabParam : 'success';

  const openEdit = useCallback((row: StandardizedMaterial) => {
    setEditing(row);
    setEditForm({ ...row });
  }, []);

  async function handleRefreshSuccessData() {
    setLoading(true);
    try {
      const data = await fetchStandardized();
      setAllData(data);
    } catch (err) {
      console.error('Failed to fetch standardized:', err);
      toast.error(`读取标准化成功数据失败：${err instanceof Error ? err.message : String(err)}`);
    } finally {
      setLoading(false);
    }
  }

  async function handleSaveEdit() {
    if (!editing) return;
    setSaving(true);
    try {
      const { id: _id, created_at: _createdAt, ...rest } = editForm as StandardizedMaterial;
      await updateStandardized(editing.id, rest);
      // Update local data
      setAllData(prev => prev.map(item => item.id === editing.id ? { ...item, ...rest } : item));
      toast.success('标准化结果已更新');
      setEditing(null);
    } catch (err) {
      toast.error(`更新失败：${err instanceof Error ? err.message : String(err)}`);
    } finally {
      setSaving(false);
    }
  }

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const data = await fetchStandardized();
        if (!cancelled) setAllData(data);
      } catch (err) {
        console.error('Failed to fetch standardized:', err);
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, []);

  const filtered = useMemo(() => {
    let d = [...allData];
    if (batchId) d = d.filter(m => m.batch_id === batchId);
    if (search) {
      const s = search.toLowerCase();
      d = d.filter(m => m.standardized_name.toLowerCase().includes(s) || m.primary_search_term.toLowerCase().includes(s));
    }
    return d;
  }, [allData, search, batchId]);

  const exportData = selectedKeys.size > 0
    ? filtered.filter(r => selectedKeys.has(r.id))
    : filtered;

  useEffect(() => {
    saveStoredColumnConfig(STANDARDIZED_STORAGE_KEY, {
      visibleKeys: orderedColKeys.filter(key => visibleCols.has(key)),
      orderedKeys: orderedColKeys,
    });
  }, [orderedColKeys, visibleCols]);

  const handleReset = () => { setSearch(''); setBatchId(''); };

  async function handleBatchDelete() {
    if (selectedKeys.size === 0) return;
    if (!window.confirm(`确定要批量删除选中的 ${selectedKeys.size} 条标准化结果吗？此操作不可撤销。`)) return;
    setDeleting(true);
    try {
      await deleteStandardizedByIds([...selectedKeys]);
      setAllData(prev => prev.filter(item => !selectedKeys.has(item.id)));
      setSelectedKeys(new Set());
      toast.success(`已删除 ${selectedKeys.size} 条标准化结果`);
    } catch (err) {
      toast.error(`删除失败：${err instanceof Error ? err.message : String(err)}`);
    } finally {
      setDeleting(false);
    }
  }

  const columns = useMemo<Column<StandardizedMaterial>[]>(() => [
    { key: 'batch_id', label: '批次ID', sortable: true, defaultWidth: 176, render: v => <span className="font-mono text-xs text-muted-foreground whitespace-nowrap">{v as string}</span> },
    { key: 'original_name', label: '原始名称', defaultWidth: 150 },
    { key: 'standardized_name', label: '标准化名称', defaultWidth: 160 },
    { key: 'original_spec', label: '原始规格', truncate: true, defaultWidth: 150 },
    { key: 'standardized_spec', label: '标准化规格', truncate: true, defaultWidth: 150 },
    { key: 'brand', label: '品牌', defaultWidth: 100 },
    { key: 'category', label: '分类(材质/用途)', defaultWidth: 120 },
    { key: 'quantity', label: '数量', defaultWidth: 80, render: (v, row) => `${v} ${row.standardized_unit}` },
    { key: 'notes', label: '备注/附件', truncate: true, defaultWidth: 120 },
    { key: 'delivery_address', label: '收货地址', truncate: true, defaultWidth: 140 },
    { key: 'invoice_type', label: '发票种类', defaultWidth: 100 },
    { key: 'primary_search_term', label: '主搜索词', truncate: true, defaultWidth: 150 },
    {
      key: 'alt_search_terms', label: '备选搜索词', defaultWidth: 160,
      render: (v) => {
        const arr = (v as string[] | null | undefined) ?? [];
        return (
          <div className="flex flex-wrap gap-1">
            {arr.slice(0, 2).map((t, i) => <Badge key={i} variant="outline" className="text-[10px]">{t}</Badge>)}
            {arr.length > 2 && <Badge variant="outline" className="text-[10px]">+{arr.length - 2}</Badge>}
          </div>
        );
      },
    },
    { key: 'product_type', label: '产品类型', defaultWidth: 120 },
    { key: 'llm_model', label: 'LLM模型', defaultWidth: 100, render: v => <span className="font-mono text-xs">{(v as string) || '-'}</span> },
    { key: 'created_at', label: '创建时间', sortable: true, defaultWidth: 140, render: v => formatDate(v as string) },
    {
      key: '_actions', label: '操作', defaultWidth: 120,
      render: (_, row) => (
        <div className="flex items-center gap-0.5">
          <Button variant="ghost" size="sm" className="text-xs text-blue-500 hover:text-blue-700" onClick={e => { e.stopPropagation(); setSelected(row); }}>
            详情
          </Button>
          <Button variant="ghost" size="icon" className="h-8 w-8 text-blue-500 hover:text-blue-700" onClick={e => { e.stopPropagation(); openEdit(row); }} aria-label="编辑">
            <Pencil className="h-4 w-4" />
          </Button>
        </div>
      ),
    },
  ], [openEdit]);

  const visibleColumns = useMemo(() => {
    const columnMap = new Map(columns.map(column => [column.key, column]));
    const orderedVisible = orderedColKeys
      .map(key => columnMap.get(key))
      .filter((column): column is Column<StandardizedMaterial> => Boolean(column && visibleCols.has(column.key)));
    const actionColumn = columnMap.get('_actions');

    return actionColumn ? [...orderedVisible, actionColumn] : orderedVisible;
  }, [columns, orderedColKeys, visibleCols]);

  return (
    <div className="h-full flex flex-col gap-4">
      <Tabs value={activeTab} className="flex-1 min-h-0 flex flex-col">
        <TabsContent value="success" className="mt-0 flex-1 min-h-0">
          <div className="h-full flex flex-col">
      {/* 搜索栏 */}
      <div className="mb-3 shrink-0 flex items-start gap-4 w-full">
        <div className="grid grid-cols-[max-content_10rem_max-content_14rem] gap-x-3 gap-y-2 items-center flex-none">
          <span className="text-sm text-muted-foreground">批次ID</span>
          <BatchIdCombobox value={batchId} onChange={setBatchId} options={batchOptions} className="w-40" />
          <span className="text-sm text-muted-foreground pl-4">标准化名称/搜索词</span>
          <Input placeholder="请输入搜索内容" value={search} onChange={e => setSearch(e.target.value)} className={searchFieldClassName} />
        </div>
        <div className="flex items-center gap-2 shrink-0 self-start ml-auto">
          <Button variant="outline" size="sm" className="h-9" onClick={handleReset}>
            <RefreshCw className="h-3.5 w-3.5 mr-1" />重置
          </Button>
          <Button size="sm" className="h-9" onClick={() => void handleRefreshSuccessData()}>
            <Search className="h-3.5 w-3.5 mr-1" />查询
          </Button>
        </div>
      </div>

  {/* 分割线 */}
  <Separator className="mb-3 shrink-0" />

      {/* 操作栏 */}
      <div className="mb-3 flex items-center gap-2 shrink-0">
        {batchId && (
          <Button size="sm" variant="outline" className="h-9" onClick={() => { setSearchLaunchMode('batch'); setSearchLaunchOpen(true); }}>
            <Radar className="h-3.5 w-3.5 mr-1" />搜索整批
          </Button>
        )}
        <Button
          size="sm"
          className="h-9 bg-gradient-to-r from-violet-500 to-indigo-500 hover:from-violet-600 hover:to-indigo-600 text-white"
          onClick={() => { setSearchLaunchMode('selected'); setSearchLaunchOpen(true); }}
          disabled={selectedKeys.size === 0}
        >
          <Sparkles className="h-4 w-4 mr-1" />AI搜索
        </Button>
        <div className="ml-auto flex items-center gap-2">
          <Button variant="outline" size="sm" className="h-9" onClick={() => setExportOpen(true)}>
            <Download className="h-3.5 w-3.5 mr-1" />导出
          </Button>
          <Button
            variant="destructive"
            size="sm"
            className="h-9"
            onClick={handleBatchDelete}
            disabled={selectedKeys.size === 0 || deleting}
          >
            {deleting ? <LoaderCircle className="h-3.5 w-3.5 mr-1 animate-spin" /> : <Trash2 className="h-3.5 w-3.5 mr-1" />}
            批量删除
          </Button>
          <Button variant="outline" size="sm" className="h-9" onClick={() => setConfigOpen(true)}>
            <SlidersHorizontal className="h-3.5 w-3.5 mr-1" />配置
          </Button>
        </div>
      </div>

      {/* 内容区 */}
      {loading ? (
        <div className="space-y-3">
          {Array.from({ length: 5 }).map((_, i) => <Skeleton key={i} className="h-10 w-full" />)}
        </div>
      ) : (
        <div className="flex-1 min-h-0">
          <DataTable columns={visibleColumns} data={filtered} loading={false} rowKey={(r) => r.id} onRowClick={(row) => setSelected(row)} selectable selectedKeys={selectedKeys} onSelectionChange={setSelectedKeys} showRowIndex />
        </div>
      )}

          </div>
        </TabsContent>

        <TabsContent value="failed" className="mt-0 flex-1 min-h-0">
          <StandardizationFailuresTab batchOptions={batchOptions} />
        </TabsContent>

        <TabsContent value="tasks" className="mt-0 flex-1 min-h-0">
          <StandardizationTasksTab batchOptions={batchOptions} />
        </TabsContent>
      </Tabs>

      <Dialog open={!!selected} onOpenChange={open => !open && setSelected(null)}>
        <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
          <DialogHeader>
            <DialogTitle className="text-primary">{selected?.standardized_name}</DialogTitle>
          </DialogHeader>
          {selected && (
            <div className="space-y-4 text-sm">
              <div className="grid grid-cols-2 gap-4">
                {([
                  ['批次ID', selected.batch_id], ['原始名称', selected.original_name],
                  ['原始规格', selected.original_spec], ['标准化名称', selected.standardized_name],
                  ['分类(材质/用途)', selected.category], ['品牌', selected.brand],
                  ['主搜索词', selected.primary_search_term], ['标准化规格', selected.standardized_spec],
                  ['数量', `${selected.quantity} ${selected.standardized_unit}`],
                  ['产品类型', selected.product_type],
                  ['收货地址', selected.delivery_address], ['发票种类', selected.invoice_type],
                  ['LLM模型', selected.llm_model],
                ] as [string, string][]).map(([label, value]) => (
                  <div key={label}>
                    <span className="text-muted-foreground">{label}</span>
                    <p className="mt-0.5 text-foreground">{value || '-'}</p>
                  </div>
                ))}
              </div>
              <div>
                <span className="text-muted-foreground">备注/附件</span>
                <p className="mt-1 text-foreground bg-muted p-3 rounded-md whitespace-pre-wrap">{selected.notes || '-'}</p>
              </div>
              <div>
                <span className="text-muted-foreground">备选搜索词</span>
                <div className="flex flex-wrap gap-1 mt-1">
                  {selected.alt_search_terms.length > 0 ? selected.alt_search_terms.map((t, i) => <Badge key={i} variant="outline">{t}</Badge>) : <span className="text-muted-foreground">-</span>}
                </div>
              </div>
              <div>
                <span className="text-muted-foreground">AI 推理说明</span>
                <p className="mt-1 text-foreground bg-muted p-3 rounded-md whitespace-pre-wrap">{selected.ai_reasoning || '-'}</p>
              </div>
            </div>
          )}
        </DialogContent>
      </Dialog>

      {/* 编辑标准化结果弹窗 */}
      <Dialog open={!!editing} onOpenChange={open => { if (!open) setEditing(null); }}>
        <DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
          <DialogHeader>
            <DialogTitle>编辑标准化结果</DialogTitle>
            <DialogDescription>修改标准化信息后点击保存。</DialogDescription>
          </DialogHeader>
          {editing && (
            <div className="grid grid-cols-2 gap-4 py-2">
              {([
                { key: 'original_name', label: '原始名称' },
                { key: 'original_spec', label: '原始规格' },
                { key: 'standardized_name', label: '标准化名称' },
                { key: 'category', label: '分类(材质/用途)' },
                { key: 'brand', label: '品牌' },
                { key: 'primary_search_term', label: '主搜索词' },
                { key: 'standardized_spec', label: '标准化规格' },
                { key: 'standardized_unit', label: '单位' },
                { key: 'quantity', label: '数量', type: 'number' },
                { key: 'delivery_address', label: '收货地址' },
                { key: 'invoice_type', label: '发票种类' },
              ] as { key: keyof StandardizedMaterial; label: string; type?: string; options?: string[] }[]).map(field => (
                <div key={field.key} className="space-y-1.5">
                  <Label htmlFor={`edit-std-${field.key}`} className="text-sm">{field.label}</Label>
                  {field.options ? (
                    <Select
                      value={(editForm[field.key] as string) ?? ''}
                      onValueChange={v => setEditForm(prev => ({ ...prev, [field.key]: v }))}
                    >
                      <SelectTrigger id={`edit-std-${field.key}`}><SelectValue /></SelectTrigger>
                      <SelectContent>
                        {field.options.map(opt => <SelectItem key={opt} value={opt}>{opt === 'high' ? '高' : opt === 'medium' ? '中' : '低'}</SelectItem>)}
                      </SelectContent>
                    </Select>
                  ) : (
                    <Input
                      id={`edit-std-${field.key}`}
                      type={field.type ?? 'text'}
                      value={editForm[field.key] ?? ''}
                      onChange={e => setEditForm(prev => ({ ...prev, [field.key]: field.type === 'number' ? Number(e.target.value) : e.target.value }))}
                    />
                  )}
                </div>
              ))}
              <div className="col-span-2 space-y-1.5">
                <Label htmlFor="edit-std-notes" className="text-sm">备注/附件</Label>
                <Textarea
                  id="edit-std-notes"
                  rows={2}
                  value={(editForm.notes as string) ?? ''}
                  onChange={e => setEditForm(prev => ({ ...prev, notes: e.target.value }))}
                />
              </div>
              <div className="col-span-2 space-y-1.5">
                <Label htmlFor="edit-std-alt_search_terms" className="text-sm">备选搜索词（逗号分隔）</Label>
                <Input
                  id="edit-std-alt_search_terms"
                  value={(editForm.alt_search_terms ?? []).join(', ')}
                  onChange={e => setEditForm(prev => ({ ...prev, alt_search_terms: e.target.value.split(/[,，]\s*/).filter(Boolean) }))}
                />
              </div>
              <div className="col-span-2 space-y-1.5">
                <Label htmlFor="edit-std-ai_reasoning" className="text-sm">AI 推理说明</Label>
                <Textarea
                  id="edit-std-ai_reasoning"
                  rows={3}
                  value={(editForm.ai_reasoning as string) ?? ''}
                  onChange={e => setEditForm(prev => ({ ...prev, ai_reasoning: e.target.value }))}
                />
              </div>
            </div>
          )}
          <DialogFooter>
            <Button variant="outline" onClick={() => setEditing(null)}>取消</Button>
            <Button onClick={handleSaveEdit} disabled={saving}>
              {saving ? <LoaderCircle className="h-4 w-4 animate-spin mr-1" /> : null}
              保存
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      <ColumnConfigDialog
        open={configOpen}
        onOpenChange={setConfigOpen}
        columns={STANDARDIZED_COLUMN_OPTIONS.map(column => ({ ...column, required: false }))}
        visibleCols={visibleCols}
        orderedKeys={orderedColKeys}
        onConfirm={({ visibleCols: nextVisibleCols, orderedKeys: nextOrderedKeys }) => {
          setColumnConfig({ visibleCols: nextVisibleCols, orderedKeys: nextOrderedKeys });
        }}
      />

      <ExportDialog
        open={exportOpen}
        onOpenChange={setExportOpen}
        data={exportData}
        totalCount={filtered.length}
        filename={`标准化物料_${new Date().toLocaleDateString('zh-CN').replace(/\//g, '-')}.xlsx`}
        columns={[
          { key: 'batch_id', label: '批次ID', group: '基础字段' },
          { key: 'material_id', label: '物料ID(material_id)', group: '基础字段', required: true },
          { key: 'original_name', label: '原始名称', group: '基础字段' },
          { key: 'standardized_name', label: '标准化名称', group: '基础字段' },
          { key: 'original_spec', label: '原始规格', group: '基础字段' },
          { key: 'standardized_spec', label: '标准化规格', group: '基础字段' },
          { key: 'brand', label: '品牌', group: '基础字段' },
          { key: 'category', label: '分类(材质/用途)', group: '基础字段' },
          { key: 'primary_search_term', label: '主搜索词', group: '基础字段' },
          { key: 'quantity', label: '数量', group: '基础字段' },
          { key: 'standardized_unit', label: '单位', group: '基础字段' },
          { key: 'created_at', label: '创建时间', group: '基础字段' },
          { key: 'notes', label: '备注/附件', group: '详情字段', defaultChecked: false },
          { key: 'delivery_address', label: '收货地址', group: '详情字段', defaultChecked: false },
          { key: 'invoice_type', label: '发票种类', group: '详情字段' },
          { key: 'alt_search_terms', label: '备选搜索词', group: '详情字段', defaultChecked: false },
          { key: 'product_type', label: '产品类型', group: '详情字段', defaultChecked: false },
          { key: 'llm_model', label: 'LLM模型', group: '详情字段', defaultChecked: false },
          { key: 'ai_reasoning', label: 'AI分析说明', group: '详情字段', defaultChecked: false },
        ]}
      />

      <SearchLaunchDialog
        open={searchLaunchOpen}
        onOpenChange={setSearchLaunchOpen}
        mode={searchLaunchMode}
        batchId={batchId}
        selectedItems={searchLaunchMode === 'selected' ? filtered.filter(r => selectedKeys.has(r.id)) : []}
        onStarted={({ batchId: startedBatchId }) => {
          const nextSearchParams = new URLSearchParams();
          nextSearchParams.set('tab', 'tasks');
          if (startedBatchId) {
            nextSearchParams.set('batch_id', startedBatchId);
          }
          navigate(`/search-results?${nextSearchParams.toString()}`);
        }}
      />

    </div>
  );
}

function renderTaskStatusBadge(status: StandardizationTaskRecord['status']) {
  if (status === 'completed') {
    return <Badge variant="outline" className="border-emerald-200 bg-emerald-50 text-emerald-700">已完成</Badge>;
  }

  if (status === 'stopped') {
    return <Badge variant="outline" className="border-slate-200 bg-slate-50 text-slate-700">已停止</Badge>;
  }

  if (status === 'failed') {
    return <Badge variant="outline" className="border-rose-200 bg-rose-50 text-rose-700">失败</Badge>;
  }

  if (status === 'stopping') {
    return <Badge variant="outline" className="border-amber-200 bg-amber-50 text-amber-700">停止中</Badge>;
  }

  if (status === 'processing') {
    return <Badge variant="outline" className="border-sky-200 bg-sky-50 text-sky-700">处理中</Badge>;
  }

  return <Badge variant="outline" className="border-slate-200 bg-slate-50 text-slate-700">待处理</Badge>;
}

function sortFailureItems(items: StandardizationFailureItem[]) {
  return [...items].sort((left, right) => new Date(right.updated_at).getTime() - new Date(left.updated_at).getTime());
}

function StandardizationFailuresTab({ batchOptions }: { batchOptions: BatchIdComboboxOption[] }) {
  const navigate = useNavigate();
  const [loading, setLoading] = useState(true);
  const [search, setSearch] = useState('');
  const [batchId, setBatchId] = useState('');
  const [allData, setAllData] = useState<StandardizationFailureItem[]>([]);
  const [selected, setSelected] = useState<StandardizationFailureItem | null>(null);
  const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set());
  const [configOpen, setConfigOpen] = useState(false);
  const [resolutionDialogOpen, setResolutionDialogOpen] = useState(false);
  const [resolutionDialogItems, setResolutionDialogItems] = useState<StandardizationFailureItem[]>([]);
  const [resolutionDialogSubmitting, setResolutionDialogSubmitting] = useState(false);
  const [resolutionDialogRetrying, setResolutionDialogRetrying] = useState(false);
  const [deleting, setDeleting] = useState(false);
  const [columnConfig, setColumnConfig] = useState(() => getInitialStandardizationFailureColumnConfig());
  const { visibleCols, orderedKeys: orderedColKeys } = columnConfig;

  async function handleRefresh() {
    setLoading(true);
    try {
      const data = await fetchStandardizationFailures();
      setAllData(sortFailureItems(data));
    } catch (err) {
      console.error('Failed to fetch standardization failures:', err);
      toast.error(`读取标准化失败数据失败：${err instanceof Error ? err.message : String(err)}`);
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    void handleRefresh();
  }, []);

  useEffect(() => {
    saveStoredColumnConfig(STANDARDIZATION_FAILURE_STORAGE_KEY, {
      visibleKeys: orderedColKeys.filter(key => visibleCols.has(key)),
      orderedKeys: orderedColKeys,
    });
  }, [orderedColKeys, visibleCols]);

  const selectedItems = useMemo(
    () => allData.filter((item) => selectedKeys.has(item.id)),
    [allData, selectedKeys],
  );

  const removeFailureItems = useCallback((ids: string[]) => {
    if (ids.length === 0) {
      return;
    }

    const idSet = new Set(ids);

    setAllData((current) => current.filter((item) => !idSet.has(item.id)));
    setSelected((current) => (current && idSet.has(current.id) ? null : current));
    setSelectedKeys((current) => {
      const next = new Set(current);
      ids.forEach((id) => next.delete(id));
      return next;
    });
  }, []);

  const filtered = useMemo(() => {
    let data = [...allData];

    if (batchId) {
      data = data.filter((item) => item.batch_id === batchId);
    }

    if (search) {
      const searchTerm = search.toLowerCase();
      data = data.filter((item) => [
        item.original_name,
        item.original_spec,
        item.standardized_name,
        ...item.missing_required_fields,
        item.failure_reason,
        item.human_fix_hint,
        item.batch_id,
        item.source_material_id,
      ].some((field) => field.toLowerCase().includes(searchTerm)));
    }

    return data;
  }, [allData, batchId, search]);

  const actionItems = selectedKeys.size > 0 ? selectedItems : filtered;

  const columns = useMemo<Column<StandardizationFailureItem>[]>(() => [
    { key: 'batch_id', label: '批次ID', defaultWidth: 176, render: (_value, row) => <span className="font-mono text-xs text-muted-foreground whitespace-nowrap">{row.batch_id}</span> },
    { key: 'original_name', label: '原始名称', defaultWidth: 180 },
    { key: 'original_spec', label: '原始规格', truncate: true, defaultWidth: 180 },
    { key: 'failure_reason', label: '失败原因', truncate: true, defaultWidth: 220 },
    { key: 'human_fix_hint', label: '补充建议', truncate: true, defaultWidth: 220 },
    { key: 'updated_at', label: '更新时间', sortable: true, defaultWidth: 140, render: (_value, row) => formatDate(row.updated_at) },
    {
      key: '_actions', label: '操作', defaultWidth: 96,
      render: (_value, row) => (
        <Button variant="ghost" size="sm" className="text-xs text-blue-500 hover:text-blue-700" onClick={(event) => { event.stopPropagation(); setSelected(row); }}>
          详情
        </Button>
      ),
    },
  ], []);

  const visibleColumns = useMemo(() => {
    const columnMap = new Map(columns.map(column => [column.key, column]));
    const orderedVisible = orderedColKeys
      .map(key => columnMap.get(key))
      .filter((column): column is Column<StandardizationFailureItem> => Boolean(column && visibleCols.has(column.key)));
    const actionColumn = columnMap.get('_actions');

    return actionColumn ? [...orderedVisible, actionColumn] : orderedVisible;
  }, [columns, orderedColKeys, visibleCols]);

  function handleReset() {
    setSearch('');
    setBatchId('');
  }

  async function handleBatchDelete() {
    if (selectedKeys.size === 0) return;
    if (!window.confirm(`确定要批量删除选中的 ${selectedKeys.size} 条标准化失败记录吗？此操作不可撤销。`)) return;

    setDeleting(true);
    try {
      await deleteStandardizationFailuresByIds([...selectedKeys]);
      setAllData(prev => prev.filter(item => !selectedKeys.has(item.id)));
      setSelected(current => (current && selectedKeys.has(current.id) ? null : current));
      setSelectedKeys(new Set());
      toast.success(`已删除 ${selectedKeys.size} 条标准化失败记录`);
    } catch (err) {
      toast.error(`删除失败：${err instanceof Error ? err.message : String(err)}`);
    } finally {
      setDeleting(false);
    }
  }

  function handleManualSupplement() {
    if (actionItems.length === 0) {
      toast.error('没有可处理的失败记录');
      return;
    }

    setResolutionDialogItems(actionItems);
    setResolutionDialogOpen(true);
  }

  async function handleConfirmSuccessPool(submissions: FailureResolutionSubmission[]) {
    if (submissions.length === 0 || resolutionDialogSubmitting) {
      return { failedIds: [] };
    }

    const submissionIds = submissions.map((submission) => submission.failureId);
    setResolutionDialogSubmitting(true);
    try {
      const result = await createManualStandardizedResultsFromFailures(
        submissions.map((submission) => ({
          failureId: submission.failureId,
          supplement: submission.supplement,
        })),
      );

      if (result.successes.length > 0) {
        removeFailureItems(result.successes.map((item) => item.failureId));
        toast.success(`已确认 ${result.successCount} 条记录入成功池`);
      }

      if (result.failures.length > 0) {
        const preview = result.failures.slice(0, 3).map((item) => `${item.failureId}: ${item.error}`).join('；');
        toast.error(`有 ${result.failedCount} 条写入失败：${preview}`);
      }

      return { failedIds: result.failures.map((item) => item.failureId) };
    } catch (err) {
      toast.error(`确认入成功池失败：${err instanceof Error ? err.message : String(err)}`);
      return { failedIds: submissionIds };
    } finally {
      setResolutionDialogSubmitting(false);
    }
  }

  async function handleRetryAiStandardization(submissions: FailureResolutionSubmission[]) {
    if (submissions.length === 0 || resolutionDialogRetrying) {
      return { failedIds: [] };
    }

    const submissionIds = submissions.map((submission) => submission.failureId);
    setResolutionDialogRetrying(true);
    try {
      const result = await retryStandardizationFailures({
        failures: submissions.map((submission) => ({
          failureId: submission.failureId,
          supplement: submission.supplement,
        })),
      });

      if (result.acceptedIds.length > 0) {
        removeFailureItems(result.acceptedIds);
        toast.success(`已创建重跑任务 ${result.taskId ?? ''}，共 ${result.totalRows} 条`.trim());
        navigate('/standardized?tab=tasks');
      }

      if (result.rejected.length > 0) {
        const preview = result.rejected.slice(0, 3).map((item) => `${item.failureId}: ${item.error}`).join('；');
        toast.error(`有 ${result.rejected.length} 条未能重跑：${preview}`);
      }

      return { failedIds: result.rejected.map((item) => item.failureId) };
    } catch (err) {
      toast.error(`重新 AI 标准化失败：${err instanceof Error ? err.message : String(err)}`);
      return { failedIds: submissionIds };
    } finally {
      setResolutionDialogRetrying(false);
    }
  }

  function getFailureSupplementExportMessage(result: FailureSupplementExportResult) {
    if (result.mode === 'print') {
      return `已打开 ${result.count} 条失败记录补充卡打印窗口`;
    }

    const reasonText: Record<Extract<FailureSupplementExportResult, { mode: 'download' }>['reason'], string> = {
      'popup-blocked': '打印窗口被浏览器拦截',
      'print-unavailable': '当前窗口不支持调用打印',
      'print-failed': '浏览器打印调用失败',
    };

    return `${reasonText[result.reason]}，已生成补充卡文件 ${result.filename}`;
  }

  async function handleExport() {
    if (actionItems.length === 0) {
      toast.error('没有可下载的失败记录');
      return;
    }

    try {
      const result = await openFailureSupplementPrintView(actionItems);
      toast.success(getFailureSupplementExportMessage(result));
    } catch (err) {
      toast.error(err instanceof Error ? err.message : String(err));
    }
  }

  return (
    <div className="h-full flex flex-col">
      <div className="mb-3 shrink-0 flex items-start gap-4 w-full">
        <div className="grid grid-cols-[max-content_10rem_max-content_16rem] gap-x-3 gap-y-2 items-center flex-none">
          <span className="text-sm text-muted-foreground">批次ID</span>
          <BatchIdCombobox value={batchId} onChange={setBatchId} options={batchOptions} className="w-40" />
          <span className="text-sm text-muted-foreground pl-4">搜索</span>
          <Input placeholder="原始名称 / 失败原因 / 补充建议" value={search} onChange={(event) => setSearch(event.target.value)} className={searchFieldClassName} />
        </div>
        <div className="flex items-center gap-2 shrink-0 self-start ml-auto">
          <Button variant="outline" size="sm" className="h-9" onClick={handleReset}>
            <RefreshCw className="h-3.5 w-3.5 mr-1" />重置
          </Button>
          <Button size="sm" className="h-9" onClick={() => void handleRefresh()}>
            <RefreshCw className="h-3.5 w-3.5 mr-1" />刷新
          </Button>
        </div>
      </div>

      <Separator className="mb-3 shrink-0" />

      <div className="mb-3 shrink-0 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
        <Badge variant="outline">失败记录 {allData.length}</Badge>
      </div>

      <div className="mb-3 flex items-center gap-2 shrink-0">
        <div className="ml-auto flex items-center gap-2">
          <Button variant="outline" size="sm" className="h-9" onClick={handleManualSupplement}>
            <Sparkles className="h-3.5 w-3.5 mr-1" />人工补充
          </Button>
          <Button variant="outline" size="sm" className="h-9" onClick={handleExport}>
            <Download className="h-3.5 w-3.5 mr-1" />下载补充卡
          </Button>
          <Button
            variant="destructive"
            size="sm"
            className="h-9"
            onClick={handleBatchDelete}
            disabled={selectedKeys.size === 0 || deleting}
          >
            {deleting ? <LoaderCircle className="h-3.5 w-3.5 mr-1 animate-spin" /> : <Trash2 className="h-3.5 w-3.5 mr-1" />}
            批量删除
          </Button>
          <Button variant="outline" size="sm" className="h-9" onClick={() => setConfigOpen(true)}>
            <SlidersHorizontal className="h-3.5 w-3.5 mr-1" />配置
          </Button>
        </div>
      </div>

      {loading ? (
        <div className="space-y-3">
          {Array.from({ length: 5 }).map((_, index) => <Skeleton key={index} className="h-10 w-full" />)}
        </div>
      ) : (
        <div className="flex-1 min-h-0">
          <DataTable
            columns={visibleColumns}
            data={filtered}
            loading={false}
            rowKey={(row) => row.id}
            onRowClick={(row) => setSelected(row)}
            selectable
            selectedKeys={selectedKeys}
            onSelectionChange={setSelectedKeys}
            showRowIndex
          />
        </div>
      )}

      <Dialog open={!!selected} onOpenChange={(open) => !open && setSelected(null)}>
        <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
          <DialogHeader>
            <DialogTitle className="text-primary">{selected?.original_name || '失败详情'}</DialogTitle>
            <DialogDescription>{selected ? `任务 ${selected.task_id}` : ''}</DialogDescription>
          </DialogHeader>
          {selected ? (
            <div className="space-y-4 text-sm">
              <div className="grid grid-cols-2 gap-4">
                {([
                  ['批次ID', selected.batch_id],
                  ['任务ID', selected.task_id],
                  ['物料ID', selected.source_material_id || '-'],
                  ['原始名称', selected.original_name || '-'],
                  ['原始规格', selected.original_spec || '-'],
                  ['发票种类', selected.invoice_type || '-'],
                  ['数量', `${selected.quantity || 0} ${selected.unit || ''}`.trim() || '-'],
                  ['标准化名称', selected.standardized_name || '-'],
                  ['LLM模型', selected.llm_model || '-'],
                ] as [string, string][]).map(([label, value]) => (
                  <div key={label}>
                    <span className="text-muted-foreground">{label}</span>
                    <p className="mt-0.5 text-foreground break-all">{value || '-'}</p>
                  </div>
                ))}
              </div>

              <div>
                <span className="text-muted-foreground">失败原因</span>
                <p className="mt-1 rounded-md bg-muted p-3 whitespace-pre-wrap">{selected.failure_reason || '-'}</p>
              </div>

              <div>
                <span className="text-muted-foreground">人工补充建议</span>
                <p className="mt-1 rounded-md bg-muted p-3 whitespace-pre-wrap">{selected.human_fix_hint || '-'}</p>
              </div>

              <div>
                <span className="text-muted-foreground">来源备注/附件</span>
                <p className="mt-1 rounded-md bg-muted p-3 whitespace-pre-wrap">{selected.source_snapshot.remarks || '-'}</p>
              </div>
            </div>
          ) : null}
        </DialogContent>
      </Dialog>

      <FailureResolutionDialog
        open={resolutionDialogOpen}
        items={resolutionDialogItems}
        onOpenChange={(open) => {
          setResolutionDialogOpen(open);
          if (!open) {
            setResolutionDialogItems([]);
          }
        }}
        onConfirmSuccessPool={handleConfirmSuccessPool}
        onRetryAiStandardization={handleRetryAiStandardization}
        submitting={resolutionDialogSubmitting}
        retrying={resolutionDialogRetrying}
      />

      <ColumnConfigDialog
        open={configOpen}
        onOpenChange={setConfigOpen}
        columns={STANDARDIZATION_FAILURE_COLUMN_OPTIONS.map(column => ({ ...column, required: false }))}
        visibleCols={visibleCols}
        orderedKeys={orderedColKeys}
        onConfirm={({ visibleCols: nextVisibleCols, orderedKeys: nextOrderedKeys }) => {
          setColumnConfig({ visibleCols: nextVisibleCols, orderedKeys: nextOrderedKeys });
        }}
      />
    </div>
  );
}

function StandardizationTasksTab({ batchOptions }: { batchOptions: BatchIdComboboxOption[] }) {
  const { user } = useAuth();
  const [loading, setLoading] = useState(true);
  const [search, setSearch] = useState('');
  const [batchId, setBatchId] = useState('');
  const [status, setStatus] = useState<'all' | StandardizationTaskRecord['status']>('all');
  const [allData, setAllData] = useState<StandardizationTaskRecord[]>([]);
  const [selected, setSelected] = useState<StandardizationTaskRecord | null>(null);
  const [stoppingTaskId, setStoppingTaskId] = useState<string | null>(null);
  const [deletingTaskId, setDeletingTaskId] = useState<string | null>(null);
  const canDeleteTaskStatus = user?.username?.trim().toLowerCase() === 'admin_leliu';

  async function loadTasks(options: { silent?: boolean } = {}) {
    if (!options.silent) {
      setLoading(true);
    }

    try {
      const data = await fetchStandardizationTasks();
      setAllData(data);
    } catch (err) {
      console.error('Failed to fetch standardization tasks:', err);
      if (!options.silent) {
        toast.error(`读取标准化任务状态失败：${err instanceof Error ? err.message : String(err)}`);
      }
    } finally {
      if (!options.silent) {
        setLoading(false);
      }
    }
  }

  useEffect(() => {
    void loadTasks();
  }, []);

  useEffect(() => {
    if (!allData.some((item) => item.status === 'pending' || item.status === 'processing' || item.status === 'stopping')) {
      return;
    }

    const interval = window.setInterval(() => {
      void loadTasks({ silent: true });
    }, 2000);

    return () => {
      window.clearInterval(interval);
    };
  }, [allData]);

  const processingCount = useMemo(
    () => allData.filter((item) => item.status === 'processing').length,
    [allData],
  );

  const pendingCount = useMemo(
    () => allData.filter((item) => item.status === 'pending').length,
    [allData],
  );

  const stoppingCount = useMemo(
    () => allData.filter((item) => item.status === 'stopping').length,
    [allData],
  );

  const completedCount = useMemo(
    () => allData.filter((item) => item.status === 'completed').length,
    [allData],
  );

  const failedCount = useMemo(
    () => allData.filter((item) => item.status === 'failed').length,
    [allData],
  );

  const stoppedCount = useMemo(
    () => allData.filter((item) => item.status === 'stopped').length,
    [allData],
  );

  async function handleStopTask(task: StandardizationTaskRecord) {
    if (task.execution_mode !== 'online' || (task.status !== 'processing' && task.status !== 'stopping')) {
      return;
    }

    setStoppingTaskId(task.id);
    try {
      const response = await stopStandardizeTask(task.id);
      toast.success(response.message || `已发送停止指令：${task.batch_id}`);
      await loadTasks({ silent: true });
      setSelected((current) => current?.id === task.id
        ? { ...current, status: 'stopping' }
        : current);
    } catch (error) {
      toast.error(`停止在线标准化失败：${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setStoppingTaskId(null);
    }
  }

  function isTaskStatusDeletable(task: StandardizationTaskRecord) {
    return task.status === 'completed' || task.status === 'failed' || task.status === 'stopped';
  }

  async function handleDeleteTask(task: StandardizationTaskRecord) {
    if (!canDeleteTaskStatus) {
      return;
    }

    if (!isTaskStatusDeletable(task)) {
      toast.error('仅支持删除已结束的标准化任务状态记录');
      return;
    }

    if (!window.confirm(`确定要删除任务 ${task.id} 的状态记录吗？任务明细会一并删除，此操作不可撤销。`)) {
      return;
    }

    setDeletingTaskId(task.id);
    try {
      await deleteStandardizationTaskById(task.id);
      setSelected((current) => current?.id === task.id ? null : current);
      await loadTasks({ silent: true });
      toast.success(`已删除任务状态：${task.batch_id}`);
    } catch (error) {
      toast.error(`删除标准化任务状态失败：${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setDeletingTaskId(null);
    }
  }

  const filtered = useMemo(() => {
    let data = [...allData];

    if (batchId) {
      data = data.filter((item) => item.batch_id === batchId);
    }

    if (status !== 'all') {
      data = data.filter((item) => item.status === status);
    }

    if (search) {
      const searchTerm = search.toLowerCase();
      data = data.filter((item) => [
        item.id,
        item.batch_id,
      ].some((field) => field.toLowerCase().includes(searchTerm)));
    }

    return data;
  }, [allData, batchId, status, search]);

  const columns: Column<StandardizationTaskRecord>[] = [
    { key: 'status', label: '任务状态', defaultWidth: 96, render: (value) => renderTaskStatusBadge(value as StandardizationTaskRecord['status']) },
    { key: 'batch_id', label: '批次ID', defaultWidth: 176, render: (value) => <span className="font-mono text-xs text-muted-foreground whitespace-nowrap">{value as string}</span> },
    { key: 'progress_percent', label: '进度', defaultWidth: 192, render: (_value, row) => <StandardizationTaskMonitor task={row} compact /> },
    { key: 'success_rows', label: '成功', defaultWidth: 72 },
    { key: 'failed_rows', label: '失败', defaultWidth: 72 },
    { key: 'started_at', label: '开始时间', sortable: true, defaultWidth: 140, render: (value) => value ? formatDate(value as string) : '-' },
    { key: 'finished_at', label: '结束时间', sortable: true, defaultWidth: 140, render: (value) => value ? formatDate(value as string) : '-' },
    { key: 'updated_at', label: '更新时间', sortable: true, defaultWidth: 140, render: (value) => formatDate(value as string) },
    {
      key: '_actions', label: '操作', defaultWidth: 240,
      render: (_value, row) => (
        <div className="flex items-center justify-center gap-1">
          <Button variant="ghost" size="sm" className="text-xs text-blue-500 hover:text-blue-700" onClick={(event) => { event.stopPropagation(); setSelected(row); }}>
            详情
          </Button>
          {canDeleteTaskStatus ? (
            <Button
              variant="ghost"
              size="sm"
              className="text-xs text-rose-500 hover:text-rose-700"
              disabled={!isTaskStatusDeletable(row) || deletingTaskId === row.id}
              title={isTaskStatusDeletable(row) ? undefined : '仅支持删除已结束的任务状态记录'}
              onClick={(event) => {
                event.stopPropagation();
                void handleDeleteTask(row);
              }}
            >
              {deletingTaskId === row.id ? '删除中' : '删除'}
            </Button>
          ) : null}
          {row.execution_mode === 'online' && (row.status === 'processing' || row.status === 'stopping') ? (
            <Button
              variant="ghost"
              size="sm"
              className="text-xs text-rose-500 hover:text-rose-700"
              disabled={row.status === 'stopping' || stoppingTaskId === row.id}
              onClick={(event) => {
                event.stopPropagation();
                void handleStopTask(row);
              }}
            >
              {row.status === 'stopping' || stoppingTaskId === row.id ? '停止中' : '停止'}
            </Button>
          ) : null}
        </div>
      ),
    },
  ];

  return (
    <div className="h-full flex flex-col">
      <div className="mb-3 shrink-0 w-full">
        <div className="grid grid-cols-[max-content_10rem_max-content_10rem_max-content_16rem] gap-x-3 gap-y-2 items-center flex-none">
          <span className="text-sm text-muted-foreground">批次ID</span>
          <BatchIdCombobox value={batchId} onChange={setBatchId} options={batchOptions} className="w-40" />
          <span className="text-sm text-muted-foreground pl-4">任务状态</span>
          <Select value={status} onValueChange={(value) => setStatus(value as 'all' | StandardizationTaskRecord['status'])}>
            <SelectTrigger className={`w-40 ${searchFieldClassName}`}>
              <SelectValue placeholder="全部状态" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="all">全部状态</SelectItem>
              <SelectItem value="pending">待处理</SelectItem>
              <SelectItem value="processing">处理中</SelectItem>
              <SelectItem value="stopping">停止中</SelectItem>
              <SelectItem value="stopped">已停止</SelectItem>
              <SelectItem value="completed">已完成</SelectItem>
              <SelectItem value="failed">失败</SelectItem>
            </SelectContent>
          </Select>
          <span className="text-sm text-muted-foreground pl-4">搜索</span>
          <Input placeholder="任务ID / 批次ID" value={search} onChange={(event) => setSearch(event.target.value)} className={searchFieldClassName} />
        </div>
      </div>

      <Separator className="mb-3 shrink-0" />

      <div className="mb-3 shrink-0 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
        <Badge variant="outline">总任务 {allData.length}</Badge>
        <Badge variant="outline">待处理 {pendingCount}</Badge>
        <Badge variant="outline">处理中 {processingCount}</Badge>
        <Badge variant="outline">停止中 {stoppingCount}</Badge>
        <Badge variant="outline">已停止 {stoppedCount}</Badge>
        <Badge variant="outline">已完成 {completedCount}</Badge>
        <Badge variant="outline">失败 {failedCount}</Badge>
      </div>

      {loading ? (
        <div className="space-y-3">
          {Array.from({ length: 5 }).map((_, index) => <Skeleton key={index} className="h-10 w-full" />)}
        </div>
      ) : (
        <div className="flex-1 min-h-0">
          <DataTable columns={columns} data={filtered} loading={false} rowKey={(row) => row.id} onRowClick={(row) => setSelected(row)} showRowIndex />
        </div>
      )}

      <Dialog open={!!selected} onOpenChange={(open) => !open && setSelected(null)}>
        <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
          <DialogHeader>
            <DialogTitle className="text-primary">{selected ? `任务 ${selected.id}` : '任务详情'}</DialogTitle>
            <DialogDescription>{selected ? `批次 ${selected.batch_id}` : ''}</DialogDescription>
          </DialogHeader>
          {selected ? (
            <div className="space-y-4 text-sm">
              <div>
                <div className="mb-3 flex items-center justify-between gap-3">
                  <div className="flex items-center gap-2">
                    {renderTaskStatusBadge(selected.status)}
                  </div>
                  <div className="flex items-center gap-2">
                    {selected.execution_mode === 'online' && (selected.status === 'processing' || selected.status === 'stopping') ? (
                      <Button
                        size="sm"
                        variant="outline"
                        disabled={selected.status === 'stopping' || stoppingTaskId === selected.id}
                        onClick={() => { void handleStopTask(selected); }}
                      >
                        <Square className="h-3.5 w-3.5" />
                        {selected.status === 'stopping' || stoppingTaskId === selected.id ? '停止中' : '停止任务'}
                      </Button>
                    ) : null}
                  </div>
                </div>
                <StandardizationTaskMonitor task={selected} />
              </div>

              <div className="grid grid-cols-2 gap-4">
                {([
                  ['批次ID', selected.batch_id],
                  ['任务ID', selected.id],
                  ['监控进度', `${selected.progress_percent}%`],
                  ['总行数', String(selected.total_rows)],
                  [getStandardizationTaskProgressCountLabel(selected), `${selected.progress_current}/${selected.progress_total}`],
                  ['成功行数', String(selected.success_rows)],
                  ['失败行数', String(selected.failed_rows)],
                  ['开始时间', selected.started_at ? formatDate(selected.started_at) : '-'],
                  ['结束时间', selected.finished_at ? formatDate(selected.finished_at) : '-'],
                  ['更新时间', formatDate(selected.updated_at)],
                ] as [string, string][]).map(([label, value]) => (
                  <div key={label}>
                    <span className="text-muted-foreground">{label}</span>
                    <p className="mt-0.5 text-foreground break-all">{value}</p>
                  </div>
                ))}
              </div>
            </div>
          ) : null}
        </DialogContent>
      </Dialog>
    </div>
  );
}
