import React, { useState, useEffect, useMemo } from 'react';
import { Database, Trash2, Edit3, Plus, Loader2, X, GripVertical, Import, Copy } from 'lucide-react';
import { cn } from '../lib/utils';
import { useStore } from '../store/useStore';
import { getTemplates, createTemplate, updateTemplate, deleteTemplate } from '../lib/api';
import { RESERVED_UNCLASSIFIED_CLASS, isReservedUnclassifiedClass, nextClassMaskId, normalizeClassMaskIds } from '../lib/maskIds';
import type { Template, TemplateClass } from '../store/useStore';
import { TransientNotice, type NoticeState, type NoticeTone } from './TransientNotice';

// HSL to Hex color generator
function hslToHex(h: number, s: number, l: number): string {
  l /= 100;
  const a = s * Math.min(l, 1 - l) / 100;
  const f = (n: number) => {
    const k = (n + h / 30) % 12;
    const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
    return Math.round(255 * color).toString(16).padStart(2, '0');
  };
  return `#${f(0)}${f(8)}${f(4)}`;
}

function generateColor(index: number, total: number): string {
  const hue = (index * 360 / Math.max(total, 1)) % 360;
  return hslToHex(hue, 75, 55);
}

const extractImportJsonText = (raw: string): string => {
  let text = raw.trim();
  const fenceMatch = text.match(/^```(?:json|javascript|js)?\s*([\s\S]*?)\s*```$/i);
  if (fenceMatch) text = fenceMatch[1].trim();

  const firstObject = text.indexOf('{');
  const firstArray = text.indexOf('[');
  const starts = [firstObject, firstArray].filter((index) => index >= 0);
  if (starts.length > 0) {
    const start = Math.min(...starts);
    const end = Math.max(text.lastIndexOf('}'), text.lastIndexOf(']'));
    if (end >= start) text = text.slice(start, end + 1);
  }
  return text;
};

const parseImportJson = (raw: string): any => {
  const text = extractImportJsonText(raw);
  const attempts = [
    text,
    text
      .replace(/[，]/g, ',')
      .replace(/[：]/g, ':')
      .replace(/([{,]\s*)(colors|names|color|name)(\s*:)/gi, '$1"$2"$3')
      .replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, (_match, inner) => `"${inner.replace(/"/g, '\\"')}"`)
      .replace(/,\s*([}\]])/g, '$1'),
  ];

  for (const candidate of attempts) {
    try {
      return JSON.parse(candidate);
    } catch {
      // Try the next normalized candidate.
    }
  }
  throw new Error('JSON 解析失败');
};

const normalizeImportRgb = (value: unknown, index: number): [number, number, number] => {
  if (value === undefined) return [100, 100, 100];
  if (!Array.isArray(value) || value.length < 3) {
    throw new Error(`第 ${index + 1} 个颜色不是 [R,G,B] 三元组`);
  }
  const rgb = value.slice(0, 3).map((part) => Number(part));
  if (rgb.some((part) => !Number.isFinite(part) || part < 0 || part > 255)) {
    throw new Error(`第 ${index + 1} 个颜色值必须在 0-255 之间`);
  }
  return [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2])];
};

export function TemplateRegistry() {
  const templates = useStore((state) => state.templates);
  const setTemplates = useStore((state) => state.setTemplates);
  const addTemplate = useStore((state) => state.addTemplate);
  const updateTemplateStore = useStore((state) => state.updateTemplate);
  const removeTemplateStore = useStore((state) => state.removeTemplate);
  const setMasks = useStore((state) => state.setMasks);

  const [isLoading, setIsLoading] = useState(false);
  const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null);
  const [showModal, setShowModal] = useState(false);
  const [isSaving, setIsSaving] = useState(false);
  const [isSavingOrder, setIsSavingOrder] = useState(false);
  const [copyingTemplateId, setCopyingTemplateId] = useState<string | null>(null);
  const [deleteTemplateTarget, setDeleteTemplateTarget] = useState<Template | null>(null);
  const [showImport, setShowImport] = useState(false);
  const [importText, setImportText] = useState('');

  const [editName, setEditName] = useState('');
  const [editDesc, setEditDesc] = useState('');
  const [editClasses, setEditClasses] = useState<TemplateClass[]>([]);
  const [editingClassId, setEditingClassId] = useState<string | null>(null);
  const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
  const [detailDragClassId, setDetailDragClassId] = useState<string | null>(null);
  const [detailDragOverClassId, setDetailDragOverClassId] = useState<string | null>(null);
  const [notice, setNotice] = useState<NoticeState | null>(null);

  const showNotice = (message: string, tone: NoticeTone = 'info') => {
    setNotice({ id: Date.now(), message, tone });
  };

  useEffect(() => {
    setIsLoading(true);
    getTemplates()
      .then((data) => setTemplates(data))
      .catch(console.error)
      .finally(() => setIsLoading(false));
  }, [setTemplates]);

  const openCreate = () => {
    setSelectedTemplate(null);
    setEditName('');
    setEditDesc('');
    setEditClasses(normalizeClassMaskIds([]));
    setShowModal(true);
  };

  const openEdit = (template: Template) => {
    setSelectedTemplate(template);
    setEditName(template.name);
    setEditDesc(template.description || '');
    setEditClasses(normalizeClassMaskIds(template.classes ? [...template.classes] : []));
    setShowModal(true);
  };

  const buildNewClass = (classes: TemplateClass[]): TemplateClass => ({
    id: `cls-${Date.now()}`,
    name: '新类别',
    color: generateColor(classes.length, Math.max(classes.length + 1, 8)),
    zIndex: classes.length > 0 ? Math.max(...classes.map((c) => c.zIndex)) + 10 : 10,
    maskId: nextClassMaskId(classes),
    category: '未分类',
  });

  const buildTemplatePayload = (template: Template | null, classes: TemplateClass[]) => ({
    name: template ? template.name : editName.trim(),
    description: template ? template.description || undefined : editDesc.trim() || undefined,
    classes: normalizeClassMaskIds(classes),
    rules: template ? template.rules || [] : [],
    color: template ? template.color || '#06b6d4' : '#06b6d4',
    z_index: template ? template.z_index ?? 0 : 0,
  });

  const nextCopyName = (name: string) => {
    const baseName = `${name} 副本`;
    const existingNames = new Set(templates.map((template) => template.name));
    if (!existingNames.has(baseName)) return baseName;

    let suffix = 2;
    while (existingNames.has(`${baseName} ${suffix}`)) {
      suffix += 1;
    }
    return `${baseName} ${suffix}`;
  };

  const copyTemplateClasses = (template: Template) => {
    const timestamp = Date.now();
    return normalizeClassMaskIds(template.classes || []).map((templateClass, index) => ({
      ...templateClass,
      id: isReservedUnclassifiedClass(templateClass) ? RESERVED_UNCLASSIFIED_CLASS.id : `cls-copy-${timestamp}-${index}`,
    }));
  };

  const recalculateClassOrder = (classes: TemplateClass[]) => (
    normalizeClassMaskIds(classes)
      .filter((templateClass) => !isReservedUnclassifiedClass(templateClass))
      .map((templateClass, index, activeClasses) => ({ ...templateClass, zIndex: (activeClasses.length - index) * 10 }))
      .concat(normalizeClassMaskIds(classes).filter(isReservedUnclassifiedClass))
  );

  const syncMaskClassOrder = (classes: TemplateClass[]) => {
    const zIndexByClassId = new Map(classes.map((templateClass) => [templateClass.id, templateClass.zIndex]));
    setMasks(useStore.getState().masks.map((mask) => (
      mask.classId && zIndexByClassId.has(mask.classId)
        ? {
          ...mask,
          classZIndex: zIndexByClassId.get(mask.classId),
          saveStatus: mask.annotationId ? 'dirty' as const : 'draft' as const,
          saved: mask.annotationId ? false : mask.saved,
        }
        : mask
    )));
  };

  const handleSave = async () => {
    if (!editName.trim()) return;
    setIsSaving(true);
    try {
      const basePayload = {
        name: editName.trim(),
        description: editDesc.trim() || undefined,
        classes: normalizeClassMaskIds(editClasses),
        rules: [],
        color: selectedTemplate ? selectedTemplate.color || '#06b6d4' : '#06b6d4',
        z_index: selectedTemplate ? selectedTemplate.z_index ?? 0 : 0,
      };
      if (selectedTemplate) {
        const updated = await updateTemplate(selectedTemplate.id, basePayload);
        updateTemplateStore(updated);
        setSelectedTemplate(updated);
        syncMaskClassOrder(normalizeClassMaskIds(updated.classes || []));
      } else {
        const created = await createTemplate(basePayload);
        addTemplate(created);
        setSelectedTemplate(created);
      }
      setShowModal(false);
    } catch (err) {
      console.error('Failed to save template:', err);
      showNotice('保存失败，请查看控制台', 'error');
    } finally {
      setIsSaving(false);
    }
  };

  const handleCopy = async (template: Template) => {
    setCopyingTemplateId(template.id);
    try {
      const created = await createTemplate({
        name: nextCopyName(template.name),
        description: template.description || undefined,
        classes: copyTemplateClasses(template),
        rules: template.rules || [],
        color: template.color || '#06b6d4',
        z_index: template.z_index ?? 0,
      });
      addTemplate(created);
      setSelectedTemplate(created);
      showNotice(`已复制模板：${created.name}`, 'success');
    } catch (err) {
      console.error('Failed to copy template:', err);
      showNotice('复制失败，请检查后端服务', 'error');
    } finally {
      setCopyingTemplateId(null);
    }
  };

  const handleDelete = async () => {
    const target = deleteTemplateTarget;
    if (!target) return;
    try {
      await deleteTemplate(target.id);
      removeTemplateStore(target.id);
      if (selectedTemplate?.id === target.id) {
        setSelectedTemplate(null);
      }
      setDeleteTemplateTarget(null);
    } catch (err) {
      console.error('Failed to delete template:', err);
      showNotice('删除失败，请检查后端服务', 'error');
    }
  };

  const addClass = () => {
    const newClass = buildNewClass(editClasses);
    setEditClasses(recalculateClassOrder([...editClasses, newClass]));
    setEditingClassId(newClass.id);
  };

  const updateClass = (id: string, updates: Partial<TemplateClass>) => {
    setEditClasses(editClasses.map((c) => (c.id === id ? { ...c, ...updates } : c)));
  };

  const removeClass = (id: string) => {
    setEditClasses(editClasses.filter((c) => c.id !== id || isReservedUnclassifiedClass(c)));
  };

  const reorderClasses = (fromIndex: number, toIndex: number) => {
    if (fromIndex === toIndex) return;
    const items = [...editClasses];
    if (isReservedUnclassifiedClass(items[fromIndex]) || isReservedUnclassifiedClass(items[toIndex])) return;
    const [moved] = items.splice(fromIndex, 1);
    items.splice(toIndex, 0, moved);
    setEditClasses(recalculateClassOrder(items));
  };

  const saveDetailClassOrder = async (sourceId: string, targetId: string) => {
    if (!activeTemplate || sourceId === targetId || isSavingOrder) return;
    const classes = normalizeClassMaskIds(activeTemplate.classes || []).sort((a, b) => b.zIndex - a.zIndex);
    if (classes.some((templateClass) => (
      (templateClass.id === sourceId || templateClass.id === targetId) && isReservedUnclassifiedClass(templateClass)
    ))) return;
    const sourceIndex = classes.findIndex((templateClass) => templateClass.id === sourceId);
    const targetIndex = classes.findIndex((templateClass) => templateClass.id === targetId);
    if (sourceIndex < 0 || targetIndex < 0 || sourceIndex === targetIndex) return;

    const reordered = [...classes];
    const [source] = reordered.splice(sourceIndex, 1);
    reordered.splice(targetIndex, 0, source);
    const nextClasses = recalculateClassOrder(reordered);

    setIsSavingOrder(true);
    try {
      const updated = await updateTemplate(activeTemplate.id, buildTemplatePayload(activeTemplate, nextClasses));
      updateTemplateStore(updated);
      setSelectedTemplate(updated);
      syncMaskClassOrder(nextClasses);
    } catch (err) {
      console.error('Failed to save template class order:', err);
      showNotice('层级顺序保存失败，请检查后端服务', 'error');
    } finally {
      setIsSavingOrder(false);
      setDetailDragClassId(null);
      setDetailDragOverClassId(null);
    }
  };

  const deleteDetailClass = async (classId: string) => {
    if (!activeTemplate || isSavingOrder) return;
    const currentClasses = normalizeClassMaskIds(activeTemplate.classes || []);
    const targetClass = currentClasses.find((templateClass) => templateClass.id === classId);
    if (!targetClass || isReservedUnclassifiedClass(targetClass)) return;
    const nextClasses = recalculateClassOrder(
      currentClasses
        .filter((templateClass) => templateClass.id !== classId)
        .sort((a, b) => b.zIndex - a.zIndex),
    );
    if (nextClasses.length === currentClasses.length) return;

    setIsSavingOrder(true);
    try {
      const updated = await updateTemplate(activeTemplate.id, buildTemplatePayload(activeTemplate, nextClasses));
      updateTemplateStore(updated);
      setSelectedTemplate(updated);
      syncMaskClassOrder(nextClasses);
      showNotice('分类已删除', 'success');
    } catch (err) {
      console.error('Failed to delete template class:', err);
      showNotice('分类删除失败，请检查后端服务', 'error');
    } finally {
      setIsSavingOrder(false);
    }
  };

  const parseImportClasses = () => {
    const data = parseImportJson(importText);
    let colors: unknown[] = [];
    let names: unknown[] = [];

    if (Array.isArray(data) && data.length === 2 && Array.isArray(data[0]) && Array.isArray(data[1])) {
      colors = data[0];
      names = data[1];
    } else if (Array.isArray(data.colors) && Array.isArray(data.names)) {
      colors = data.colors;
      names = data.names;
    } else if (Array.isArray(data.color) && Array.isArray(data.name)) {
      colors = data.color;
      names = data.name;
    } else {
      throw new Error('格式错误：请提供 [[colors...], [names...]] 或 {colors, names}');
    }

    if (names.length === 0) {
      throw new Error('names 至少需要包含 1 个分类名称');
    }
    const firstMaskId = nextClassMaskId(editClasses);
    const classes: TemplateClass[] = names.map((name, i: number) => {
      const normalizedName = String(name ?? '').trim();
      if (!normalizedName) {
        throw new Error(`第 ${i + 1} 个分类名称为空`);
      }
      const rgb = normalizeImportRgb(colors[i], i);
      const hex = `#${rgb[0].toString(16).padStart(2, '0')}${rgb[1].toString(16).padStart(2, '0')}${rgb[2].toString(16).padStart(2, '0')}`;
      return {
        id: `cls-import-${Date.now()}-${i}`,
        name: normalizedName,
        color: hex,
        zIndex: (names.length - i) * 10,
        maskId: firstMaskId + i,
        category: '批量导入',
      };
    });
    return {
      classes,
      firstMaskId,
      missingColorCount: Math.max(0, names.length - colors.length),
    };
  };

  const importPreview = useMemo(() => {
    if (!showImport || !importText.trim()) return null;
    try {
      const parsed = parseImportClasses();
      return { status: 'ready' as const, ...parsed };
    } catch (err: any) {
      return { status: 'error' as const, message: err?.message || 'JSON 解析失败' };
    }
  }, [showImport, importText, editClasses]);

  const handleImport = () => {
    try {
      const imported = parseImportClasses();
      setEditClasses(recalculateClassOrder([...editClasses, ...imported.classes]));
      setShowImport(false);
      setImportText('');
    } catch (err: any) {
      showNotice(err?.message || 'JSON 解析失败', 'error');
    }
  };

  const activeTemplate = selectedTemplate
    ? templates.find((template) => template.id === selectedTemplate.id) || selectedTemplate
    : templates[0] || null;
  const activeTemplateClasses = normalizeClassMaskIds(activeTemplate?.classes || []).sort((a, b) => b.zIndex - a.zIndex);

  return (
    <div className="p-8 w-full h-full overflow-y-auto bg-[#0a0a0a]">
      <TransientNotice notice={notice} onDismiss={() => setNotice(null)} />
      <div className="mb-8 border-b border-white/5 pb-6">
        <h1 className="text-3xl font-medium tracking-tight text-white mb-2">分割模板与分类优先级管理库</h1>
        <p className="text-gray-400 text-sm">定义业务语义本体树架构、约束覆盖遮罩优先级(Z-Index裁决权重)，以及真实标签数据的向下兼容转换映射(Dict Translation)原则。</p>
      </div>

      <div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
        <div className="xl:col-span-1 border-r border-white/5 pr-6">
          <div className="flex justify-between items-center mb-6">
             <h2 className="text-sm font-bold text-gray-500 uppercase tracking-widest">生效中模板架构清单</h2>
             <button 
               onClick={openCreate}
               className="text-cyan-400 hover:text-cyan-300 text-sm transition-colors flex items-center gap-1"
             >
               <Plus size={14} /> 新建方案
             </button>
          </div>

          {isLoading && templates.length === 0 ? (
            <div className="space-y-3">
              {Array.from({ length: 4 }).map((_, i) => (
                <div key={i} className="bg-[#111] border border-white/5 p-4 rounded-xl animate-pulse">
                  <div className="h-4 bg-[#1a1a1a] rounded w-2/3 mb-2" />
                  <div className="h-3 bg-[#1a1a1a] rounded w-1/2" />
                </div>
              ))}
            </div>
          ) : (
            <div className="space-y-3">
               {templates.map((t) => (
                 <div 
                   key={t.id} 
                   className={cn(
                     "bg-[#111] border p-4 rounded-xl cursor-pointer transition-all hover:shadow-lg hover:shadow-cyan-900/10 group",
                     activeTemplate?.id === t.id ? "border-cyan-500/50" : "border-white/5 hover:border-cyan-500/50"
                   )}
                   onClick={() => setSelectedTemplate(t)}
                 >
                   <div className="flex justify-between items-start">
                     <h3 className="font-medium text-gray-200 mb-1">{t.name}</h3>
                     <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
                       <button 
                         title="复制模板"
                         aria-label={`复制模板 ${t.name}`}
                         disabled={copyingTemplateId === t.id}
                         onClick={(e) => { e.stopPropagation(); void handleCopy(t); }}
                         className={cn(
                           "p-1 rounded text-gray-500 hover:text-emerald-400 transition-colors disabled:cursor-wait disabled:text-gray-600",
                         )}
                       >
                         {copyingTemplateId === t.id ? <Loader2 size={14} className="animate-spin" /> : <Copy size={14} />}
                       </button>
                       <button
                         title="编辑模板"
                         aria-label={`编辑模板 ${t.name}`}
                         onClick={(e) => { e.stopPropagation(); openEdit(t); }}
                         className="p-1 rounded text-gray-500 hover:text-cyan-400 transition-colors"
                       >
                         <Edit3 size={14} />
                       </button>
                       <button
                         title="删除模板"
                         aria-label={`删除模板 ${t.name}`}
                         onClick={(e) => { e.stopPropagation(); setDeleteTemplateTarget(t); }}
                         className="p-1 rounded text-gray-500 hover:text-red-400 transition-colors"
                       >
                         <Trash2 size={14} />
                       </button>
                     </div>
                   </div>
                   <div className="flex items-center gap-4 text-xs text-gray-500 font-mono">
                     <span>涵盖 {t.classes?.length ?? 0} 个字典大类</span>
                     <span>挂载 {t.rules?.length ?? 0} 项解析规则</span>
                   </div>
                 </div>
               ))}
            </div>
          )}
        </div>

        <div className="xl:col-span-2 space-y-6">
           <div className="bg-[#111] border border-white/5 rounded-xl p-6">
             <div className="flex items-center justify-between mb-6 border-b border-white/5 pb-4">
                <h2 className="text-lg font-medium text-gray-200 flex items-center gap-2">
                  <Database size={18} /> 
                  {activeTemplate ? activeTemplate.name : '未选择模板'}
                </h2>
                {activeTemplate && (
                  <button 
                    onClick={() => openEdit(activeTemplate)}
                    className="bg-white/5 hover:bg-white/10 border border-white/10 px-4 py-1.5 rounded text-sm text-gray-300 transition-colors flex items-center gap-2"
                  >
                    <Edit3 size={14} /> 编辑模板
                  </button>
                )}
             </div>
             
             {activeTemplate ? (
               <div className="space-y-6">
                 <div>
                   <h3 className="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-4">
                     语义分类树（拖拽调层级）
                   </h3>
                   <div className="space-y-2">
                      {activeTemplateClasses.map((cls) => (
                        <div
                          key={cls.id}
                          draggable={!isSavingOrder && !isReservedUnclassifiedClass(cls)}
                          onDragStart={(event) => {
                            if (isReservedUnclassifiedClass(cls)) return;
                            setDetailDragClassId(cls.id);
                            event.dataTransfer.setData('text/plain', cls.id);
                            event.dataTransfer.effectAllowed = 'move';
                          }}
                          onDragOver={(event) => {
                            if (!detailDragClassId || detailDragClassId === cls.id || isSavingOrder || isReservedUnclassifiedClass(cls)) return;
                            event.preventDefault();
                            event.dataTransfer.dropEffect = 'move';
                            setDetailDragOverClassId(cls.id);
                          }}
                          onDragLeave={() => setDetailDragOverClassId(null)}
                          onDrop={(event) => {
                            event.preventDefault();
                            const sourceId = event.dataTransfer.getData('text/plain') || detailDragClassId;
                            if (sourceId) void saveDetailClassOrder(sourceId, cls.id);
                          }}
                          onDragEnd={() => {
                            setDetailDragClassId(null);
                            setDetailDragOverClassId(null);
                          }}
                          className={cn(
                            "grid grid-cols-4 gap-4 p-3 bg-[#0d0d0d] border rounded items-center transition-all",
                            detailDragOverClassId === cls.id ? "border-cyan-500/50 bg-cyan-500/5" : "border-white/5",
                            detailDragClassId === cls.id && "opacity-50",
                            isSavingOrder ? "cursor-wait" : isReservedUnclassifiedClass(cls) ? "cursor-default" : "cursor-grab active:cursor-grabbing",
                          )}
                        >
                          <div className="col-span-1 flex items-center gap-2 min-w-0">
                             <GripVertical size={14} className={cn("shrink-0", isReservedUnclassifiedClass(cls) ? "text-gray-800" : "text-gray-600")} />
                             <div className="w-3 h-3 rounded" style={{ backgroundColor: cls.color }}></div>
                             <span className="font-medium text-sm text-gray-300 truncate">{cls.name}</span>
                          </div>
                          <div className="col-span-1 font-mono text-xs text-gray-500">maskid: {cls.maskId}</div>
                          <div className="col-span-2 flex justify-end">
                            <button
                              type="button"
                              aria-label={`删除分类 ${cls.name}`}
                              title="删除分类"
                              disabled={isSavingOrder || isReservedUnclassifiedClass(cls)}
                              onClick={(event) => {
                                event.stopPropagation();
                                void deleteDetailClass(cls.id);
                              }}
                              className="rounded p-1 text-gray-500 transition-colors hover:text-red-400 disabled:cursor-not-allowed disabled:opacity-30"
                            >
                              <Trash2 size={14} />
                            </button>
                          </div>
                        </div>
                      ))}
                      {(activeTemplate.classes || []).length === 0 && (
                        <div className="text-sm text-gray-500 text-center py-8">暂无分类定义</div>
                      )}
                   </div>
                 </div>
               </div>
             ) : (
               <div className="text-sm text-gray-500 text-center py-12">请从左侧选择一个模板或创建新模板</div>
             )}
           </div>
        </div>
      </div>

      {showModal && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
          <div className="bg-[#111] border border-white/10 rounded-2xl p-6 w-full max-w-2xl max-h-[80vh] overflow-y-auto shadow-2xl">
            <div className="flex justify-between items-center mb-4">
              <h2 className="text-lg font-semibold text-white">{selectedTemplate ? '编辑模板' : '新建模板'}</h2>
              <button onClick={() => setShowModal(false)} className="text-gray-500 hover:text-white transition-colors">
                <X size={18} />
              </button>
            </div>

            <div className="space-y-4 mb-6">
              <div>
                <label className="block text-xs font-medium text-gray-400 uppercase tracking-widest mb-2">模板名称</label>
                <input
                  type="text"
                  value={editName}
                  onChange={(e) => setEditName(e.target.value)}
                  className="w-full bg-[#1a1a1a] border border-white/10 rounded-lg px-4 py-3 text-sm focus:outline-none focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/50 transition-all"
                />
              </div>
              <div>
                <label className="block text-xs font-medium text-gray-400 uppercase tracking-widest mb-2">描述</label>
                <input
                  type="text"
                  value={editDesc}
                  onChange={(e) => setEditDesc(e.target.value)}
                  className="w-full bg-[#1a1a1a] border border-white/10 rounded-lg px-4 py-3 text-sm focus:outline-none focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/50 transition-all"
                />
              </div>
            </div>

            <div className="mb-4">
              <div className="flex justify-between items-center mb-3">
                <h3 className="text-xs font-bold text-gray-500 uppercase tracking-widest">分类定义 ({editClasses.length} 个)</h3>
                <div className="flex items-center gap-2">
                  <button
                    onClick={() => setShowImport(true)}
                    className="text-gray-400 hover:text-white text-xs transition-colors flex items-center gap-1 bg-white/5 px-2 py-1 rounded"
                  >
                    <Import size={12} /> 批量导入
                  </button>
                  <button
                    onClick={addClass}
                    className="text-cyan-400 hover:text-cyan-300 text-xs transition-colors flex items-center gap-1"
                  >
                    <Plus size={12} /> 添加分类
                  </button>
                </div>
              </div>
              <div className="space-y-2">
                {editClasses.map((cls, idx) => (
                  <div
                    key={cls.id}
                    draggable={!isReservedUnclassifiedClass(cls)}
                    onDragStart={(e) => {
                      if (isReservedUnclassifiedClass(cls)) return;
                      e.dataTransfer.setData('text/plain', String(idx));
                      e.dataTransfer.effectAllowed = 'move';
                    }}
                    onDragOver={(e) => {
                      if (isReservedUnclassifiedClass(cls)) return;
                      e.preventDefault();
                      e.dataTransfer.dropEffect = 'move';
                      setDragOverIndex(idx);
                    }}
                    onDragLeave={() => setDragOverIndex(null)}
                    onDrop={(e) => {
                      e.preventDefault();
                      const fromIndex = parseInt(e.dataTransfer.getData('text/plain'), 10);
                      if (isReservedUnclassifiedClass(cls) || Number.isNaN(fromIndex)) return;
                      reorderClasses(fromIndex, idx);
                      setDragOverIndex(null);
                    }}
                    onDragEnd={() => setDragOverIndex(null)}
                    className={cn(
                      "flex items-center gap-2 bg-[#0d0d0d] border rounded-lg p-2 transition-all",
                      dragOverIndex === idx ? "border-cyan-500/50 bg-cyan-500/5" : "border-white/5"
                    )}
                  >
                    <div className={cn("text-gray-600 shrink-0", isReservedUnclassifiedClass(cls) ? "cursor-default opacity-30" : "cursor-grab active:cursor-grabbing")}>
                      <GripVertical size={14} />
                    </div>
                    <input
                      type="color"
                      value={cls.color}
                      onChange={(e) => updateClass(cls.id, { color: e.target.value })}
                      disabled={isReservedUnclassifiedClass(cls)}
                      className="w-8 h-8 rounded bg-transparent border-0 cursor-pointer shrink-0 disabled:cursor-not-allowed disabled:opacity-50"
                    />
                    {editingClassId === cls.id ? (
                      <input
                        type="text"
                        value={cls.name}
                        onChange={(e) => updateClass(cls.id, { name: e.target.value })}
                        onBlur={() => setEditingClassId(null)}
                        onKeyDown={(e) => e.key === 'Enter' && setEditingClassId(null)}
                        autoFocus
                        readOnly={isReservedUnclassifiedClass(cls)}
                        className="flex-1 bg-[#1a1a1a] border border-white/10 rounded px-2 py-1 text-sm text-white"
                      />
                    ) : (
                      <>
                        <span
                          className={cn("flex-1 text-sm text-gray-300", isReservedUnclassifiedClass(cls) ? "cursor-default" : "cursor-pointer")}
                          onClick={() => {
                            if (!isReservedUnclassifiedClass(cls)) setEditingClassId(cls.id);
                          }}
                        >
                          {cls.name}
                        </span>
                        <span className="w-24 text-sm text-gray-500 font-mono text-right">maskid:{cls.maskId}</span>
                      </>
                    )}
                    <button
                      onClick={() => removeClass(cls.id)}
                      disabled={isReservedUnclassifiedClass(cls)}
                      className="text-gray-500 hover:text-red-400 transition-colors disabled:cursor-not-allowed disabled:opacity-30"
                    >
                      <Trash2 size={14} />
                    </button>
                  </div>
                ))}
                {editClasses.length === 0 && (
                  <div className="text-sm text-gray-500 text-center py-4">暂无分类，请点击上方按钮添加</div>
                )}
              </div>
            </div>

            <div className="flex justify-end gap-3">
              <button
                onClick={() => setShowModal(false)}
                className="px-4 py-2 rounded-lg text-sm text-gray-400 hover:text-white transition-colors"
              >
                取消
              </button>
              <button
                onClick={handleSave}
                disabled={isSaving || !editName.trim()}
                className={cn(
                  "px-4 py-2 rounded-lg text-sm font-medium flex items-center gap-2 transition-all",
                  isSaving || !editName.trim()
                    ? "bg-cyan-500/50 text-black/70 cursor-not-allowed"
                    : "bg-cyan-500 hover:bg-cyan-400 text-black"
                )}
              >
                {isSaving && <Loader2 size={14} className="animate-spin" />}
                保存
              </button>
            </div>
          </div>
        </div>
      )}

      {showImport && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
          <div className="bg-[#111] border border-white/10 rounded-2xl p-6 w-full max-w-lg shadow-2xl">
            <h2 className="text-lg font-semibold text-white mb-4">批量导入分类</h2>
            <p className="text-xs text-gray-500 mb-2">支持格式: JSON 数组 [[colors], [names]] 或 {'{'}&quot;colors&quot;: [...], &quot;names&quot;: [...]{'}'}</p>
            <textarea
              value={importText}
              onChange={(e) => setImportText(e.target.value)}
              placeholder='[[[255,0,0], [0,255,0]], ["分类A", "分类B"]]'
              className="w-full h-32 bg-[#1a1a1a] border border-white/10 rounded-lg px-3 py-2 text-xs text-gray-300 font-mono focus:outline-none focus:border-cyan-500/50 resize-none"
            />
            {importPreview?.status === 'ready' && (
              <div className="mt-3 rounded-lg border border-cyan-500/20 bg-cyan-950/15 px-3 py-2 text-xs text-cyan-100">
                将导入 {importPreview.classes.length} 个分类，maskid 从 {importPreview.firstMaskId} 开始分配。
                {importPreview.missingColorCount > 0 && (
                  <span className="ml-1 text-amber-200">{importPreview.missingColorCount} 个分类缺少颜色，将使用默认灰色。</span>
                )}
              </div>
            )}
            {importPreview?.status === 'error' && (
              <div className="mt-3 rounded-lg border border-red-500/20 bg-red-950/20 px-3 py-2 text-xs text-red-100">
                {importPreview.message}
              </div>
            )}
            <div className="flex justify-end items-center mt-4">
              <div className="flex gap-3">
                <button
                  onClick={() => { setShowImport(false); setImportText(''); }}
                  className="px-4 py-2 rounded-lg text-sm text-gray-400 hover:text-white transition-colors"
                >
                  取消
                </button>
                <button
                  onClick={handleImport}
                  disabled={importPreview?.status === 'error'}
                  className="px-4 py-2 rounded-lg text-sm font-medium bg-cyan-500 hover:bg-cyan-400 text-black transition-all disabled:cursor-not-allowed disabled:opacity-50"
                >
                  导入
                </button>
              </div>
            </div>
          </div>
        </div>
      )}
      {deleteTemplateTarget && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
          <div className="w-full max-w-md rounded-2xl border border-red-500/20 bg-[#111] p-6 shadow-2xl">
            <h2 className="text-lg font-semibold text-white">删除模板</h2>
            <p className="mt-3 text-sm leading-6 text-gray-400">
              确认删除模板“<span className="text-gray-100">{deleteTemplateTarget.name}</span>”？该操作不会删除项目标注，但后续打开项目时，引用已删除分类的 mask 会进入“待分类”。
            </p>
            <div className="mt-6 flex justify-end gap-3">
              <button
                type="button"
                onClick={() => setDeleteTemplateTarget(null)}
                className="rounded-lg px-4 py-2 text-sm text-gray-400 transition-colors hover:text-white"
              >
                取消
              </button>
              <button
                type="button"
                onClick={() => void handleDelete()}
                className="rounded-lg bg-red-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-400"
              >
                确认删除
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
