import React, { useEffect, useMemo, useRef, useState } from 'react';
import { ChevronDown, Tag, Eye, Plus, X, Loader2, GripVertical } from 'lucide-react';
import { useStore } from '../store/useStore';
import type { Mask, TemplateClass } from '../store/useStore';
import { cn } from '../lib/utils';
import { getActiveTemplate } from '../lib/templateSelection';
import { analyzeMask, deleteAnnotation, smoothMaskGeometry, updateTemplate, type MaskAnalysisResult, type SmoothMaskGeometryResult } from '../lib/api';
import { isReservedUnclassifiedClass, nextClassMaskId, normalizeClassMaskIds } from '../lib/maskIds';

const SMOOTHING_PREVIEW_DEBOUNCE_MS = 220;

const isRequestAbortError = (err: unknown) => {
  const error = err as { code?: string; message?: string; name?: string } | null;
  const message = error?.message || '';
  return error?.code === 'ERR_CANCELED'
    || error?.code === 'ECONNABORTED'
    || error?.name === 'AbortError'
    || /request aborted|aborted|cancell?ed/i.test(message);
};

function metadataNumber(value: unknown): number | null {
  const parsed = Number(value);
  return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
}

function isNotFoundError(error: unknown): boolean {
  const maybeError = error as { response?: { status?: number }; status?: number } | null;
  return maybeError?.response?.status === 404 || maybeError?.status === 404;
}

async function deleteAnnotationIfExists(annotationId: string) {
  try {
    await deleteAnnotation(annotationId);
  } catch (error) {
    if (!isNotFoundError(error)) throw error;
  }
}

function propagationSourceMaskTokens(value: unknown): string[] {
  if (typeof value !== 'string' || value.length === 0) return [];
  const tokens = [`mask:${value}`];
  const annotationMatch = value.match(/^annotation-(\d+)$/);
  if (annotationMatch) {
    tokens.push(`annotation:${annotationMatch[1]}`);
  }
  return tokens;
}

function propagationLineageTokens(mask: { id: string; annotationId?: string; metadata?: Record<string, unknown> }): Set<string> {
  const metadata = mask.metadata || {};
  const tokens = new Set<string>([`mask:${mask.id}`]);
  if (mask.annotationId) {
    tokens.add(`annotation:${mask.annotationId}`);
  }
  const sourceAnnotationId = metadataNumber(metadata.source_annotation_id);
  if (sourceAnnotationId !== null) {
    tokens.add(`annotation:${sourceAnnotationId}`);
  }
  propagationSourceMaskTokens(metadata.source_mask_id).forEach((token) => tokens.add(token));
  if (typeof metadata.propagation_seed_key === 'string' && metadata.propagation_seed_key.length > 0) {
    tokens.add(`seed-key:${metadata.propagation_seed_key}`);
  }
  return tokens;
}

function findPropagationChainMaskIds(selectedMask: Pick<Mask, 'id' | 'annotationId' | 'metadata'>, masks: Mask[]): Set<string> {
  const selectedTokens = propagationLineageTokens(selectedMask);
  return new Set(
    masks
      .filter((mask) => Array.from(selectedTokens).some((token) => propagationLineageTokens(mask).has(token)))
      .map((mask) => mask.id),
  );
}

export function OntologyInspector() {
  const templates = useStore((state) => state.templates);
  const activeTemplateId = useStore((state) => state.activeTemplateId);
  const activeClassId = useStore((state) => state.activeClassId);
  const activeClass = useStore((state) => state.activeClass);
  const frames = useStore((state) => state.frames);
  const currentFrameIndex = useStore((state) => state.currentFrameIndex);
  const masks = useStore((state) => state.masks);
  const selectedMaskIds = useStore((state) => state.selectedMaskIds);
  const maskPreviewOpacity = useStore((state) => state.maskPreviewOpacity);
  const setMasks = useStore((state) => state.setMasks);
  const setSelectedMaskIds = useStore((state) => state.setSelectedMaskIds);
  const updateTemplateStore = useStore((state) => state.updateTemplate);
  const setActiveTemplateId = useStore((state) => state.setActiveTemplateId);
  const setActiveClass = useStore((state) => state.setActiveClass);
  const setMaskPreviewOpacity = useStore((state) => state.setMaskPreviewOpacity);

  const [showAddForm, setShowAddForm] = useState(false);
  const [newClassName, setNewClassName] = useState('');
  const [newClassColor, setNewClassColor] = useState('#06b6d4');
  const [isSavingClass, setIsSavingClass] = useState(false);
  const [classSaveMessage, setClassSaveMessage] = useState('');
  const [dragClassId, setDragClassId] = useState<string | null>(null);
  const [maskAnalysis, setMaskAnalysis] = useState<MaskAnalysisResult | null>(null);
  const [analysisMessage, setAnalysisMessage] = useState('');
  const [smoothingStrength, setSmoothingStrength] = useState(0);
  const [isPreviewingSmoothing, setIsPreviewingSmoothing] = useState(false);
  const [isSmoothingMask, setIsSmoothingMask] = useState(false);
  const [pendingTemplateSwitch, setPendingTemplateSwitch] = useState<{ templateId: string | null } | null>(null);
  const [isSwitchingTemplate, setIsSwitchingTemplate] = useState(false);

  const activeTemplate = getActiveTemplate(templates, activeTemplateId);
  const templateClasses = normalizeClassMaskIds(activeTemplate?.classes || []);
  const allClasses = [...templateClasses].sort((a, b) => b.zIndex - a.zIndex);
  const selectedMask = masks.find((mask) => selectedMaskIds.includes(mask.id)) || null;
  const selectedMaskLabel = selectedMask?.className || selectedMask?.label || '未选择';
  const currentFrame = frames[currentFrameIndex] || null;
  const classButtonRefs = useRef(new Map<string, HTMLButtonElement>());
  const skipNextAutoAnalysisRef = useRef(false);
  const analysisRequestIdRef = useRef(0);
  const smoothingPreviewRef = useRef<{
    maskId: string;
    baseMask: NonNullable<typeof selectedMask>;
    strength: number;
    result: SmoothMaskGeometryResult | null;
    applied: boolean;
    requestId: number;
  } | null>(null);
  const smoothingRequestIdRef = useRef(0);
  const smoothingPreviewTimerRef = useRef<number | null>(null);

  const clearSmoothingPreviewTimer = React.useCallback(() => {
    if (smoothingPreviewTimerRef.current === null) return;
    window.clearTimeout(smoothingPreviewTimerRef.current);
    smoothingPreviewTimerRef.current = null;
  }, []);

  const selectedMaskClass = useMemo(() => {
    if (!selectedMask) return null;
    const allTemplateClasses = templates.flatMap((template) => (
      template.classes.map((templateClass) => ({ template, templateClass }))
    ));
    const selectedName = selectedMask.className || selectedMask.label;
    return allTemplateClasses.find(({ templateClass }) => selectedMask.classId && templateClass.id === selectedMask.classId)
      || allTemplateClasses.find(({ templateClass }) => templateClass.name === selectedName && templateClass.color === selectedMask.color)
      || allTemplateClasses.find(({ templateClass }) => templateClass.name === selectedName)
      || null;
  }, [selectedMask?.classId, selectedMask?.className, selectedMask?.color, selectedMask?.id, selectedMask?.label, templates]);

  useEffect(() => {
    if (!selectedMaskClass) return;
    if (activeTemplateId !== selectedMaskClass.template.id) {
      setActiveTemplateId(selectedMaskClass.template.id);
    }
    setActiveClass(selectedMaskClass.templateClass);
    const timer = window.setTimeout(() => {
      const node = classButtonRefs.current.get(selectedMaskClass.templateClass.id);
      node?.scrollIntoView?.({ block: 'nearest' });
      node?.focus?.({ preventScroll: true });
    }, 0);
    return () => window.clearTimeout(timer);
  }, [activeTemplateId, selectedMaskClass, setActiveClass, setActiveTemplateId]);

  const handleSelectClass = (templateClass: TemplateClass) => {
    if (activeTemplate && !activeTemplateId) {
      setActiveTemplateId(activeTemplate.id);
    }
    setActiveClass(templateClass);
    const selectedIdSet = new Set(selectedMaskIds);
    const hasSelectedMasks = masks.some((mask) => selectedIdSet.has(mask.id));
    if (!hasSelectedMasks) return;

    const templateId = activeTemplate?.id || activeTemplateId || undefined;
    const targetIdSet = new Set<string>();
    masks
      .filter((mask) => selectedIdSet.has(mask.id))
      .forEach((mask) => {
        findPropagationChainMaskIds(mask, masks).forEach((maskId) => targetIdSet.add(maskId));
      });
    const updatedMasks = masks.map((mask) => {
      if (!targetIdSet.has(mask.id)) return mask;
      return {
        ...mask,
        templateId: templateId || mask.templateId,
        classId: templateClass.id,
        className: templateClass.name,
        classZIndex: templateClass.zIndex,
        classMaskId: templateClass.maskId,
        label: templateClass.name,
        color: templateClass.color,
        saveStatus: mask.annotationId ? 'dirty' as const : 'draft' as const,
        saved: mask.annotationId ? false : mask.saved,
      };
    });
    const selectedMasksOnTop = selectedMaskIds
      .map((id) => updatedMasks.find((mask) => mask.id === id))
      .filter((mask): mask is (typeof updatedMasks)[number] => Boolean(mask));
    setMasks([
      ...updatedMasks.filter((mask) => !selectedIdSet.has(mask.id)),
      ...selectedMasksOnTop,
    ]);
  };

  const refreshMaskAnalysis = async () => {
    const requestId = analysisRequestIdRef.current + 1;
    analysisRequestIdRef.current = requestId;
    if (!selectedMask || !currentFrame) {
      setMaskAnalysis(null);
      setAnalysisMessage(selectedMask ? '当前帧信息不可用，无法读取后端属性' : '请选择一个 mask 查看后端属性');
      return;
    }
    setAnalysisMessage('');
    try {
      const result = await analyzeMask(selectedMask, currentFrame);
      if (analysisRequestIdRef.current !== requestId) return;
      setMaskAnalysis(result);
      setAnalysisMessage(result.message);
    } catch (err) {
      if (analysisRequestIdRef.current !== requestId || isRequestAbortError(err)) return;
      console.error('Mask analysis failed:', err);
      setMaskAnalysis(null);
      setAnalysisMessage('后端属性读取失败');
    }
  };

  const restoreSmoothingPreview = React.useCallback(() => {
    const preview = smoothingPreviewRef.current;
    if (!preview || preview.applied) {
      smoothingPreviewRef.current = null;
      return;
    }
    const state = useStore.getState();
    useStore.setState({
      masks: state.masks.map((mask) => (mask.id === preview.maskId ? preview.baseMask : mask)),
      selectedMaskIds: state.selectedMaskIds,
    });
    smoothingPreviewRef.current = null;
  }, []);

  const applyActiveTemplateChange = React.useCallback((templateId: string | null) => {
    setActiveTemplateId(templateId);
    setActiveClass(null);
  }, [setActiveClass, setActiveTemplateId]);

  const requestActiveTemplateChange = React.useCallback((templateId: string | null) => {
    if (templateId === (activeTemplateId || null)) return;
    if (!activeTemplateId && templateId && templateId === activeTemplate?.id) {
      applyActiveTemplateChange(templateId);
      return;
    }
    if (masks.length === 0) {
      applyActiveTemplateChange(templateId);
      return;
    }
    setPendingTemplateSwitch({ templateId });
  }, [activeTemplate?.id, activeTemplateId, applyActiveTemplateChange, masks.length]);

  const confirmActiveTemplateChange = React.useCallback(async () => {
    if (!pendingTemplateSwitch) return;
    const nextTemplateId = pendingTemplateSwitch.templateId;
    const annotationIds = Array.from(new Set(
      useStore.getState().masks
        .map((mask) => mask.annotationId)
        .filter((annotationId): annotationId is string => Boolean(annotationId)),
    ));
    setIsSwitchingTemplate(true);
    setClassSaveMessage(annotationIds.length > 0 ? '正在清空旧模板标注...' : '正在清空旧模板遮罩...');
    try {
      await Promise.all(annotationIds.map(deleteAnnotationIfExists));
      useStore.getState().setMasks([]);
      setSelectedMaskIds([]);
      applyActiveTemplateChange(nextTemplateId);
      setPendingTemplateSwitch(null);
      setClassSaveMessage(annotationIds.length > 0
        ? `已清空 ${annotationIds.length} 个旧模板标注并切换激活模板`
        : '已清空旧模板遮罩并切换激活模板');
    } catch (err) {
      console.error('Switch active template failed:', err);
      setClassSaveMessage('切换模板失败，旧标注未清空');
    } finally {
      setIsSwitchingTemplate(false);
    }
  }, [applyActiveTemplateChange, pendingTemplateSwitch, setSelectedMaskIds]);

  React.useEffect(() => {
    return () => {
      analysisRequestIdRef.current += 1;
      clearSmoothingPreviewTimer();
      restoreSmoothingPreview();
    };
  }, [clearSmoothingPreviewTimer, restoreSmoothingPreview]);

  React.useEffect(() => {
    const preview = smoothingPreviewRef.current;
    if (preview && preview.maskId !== selectedMask?.id) {
      restoreSmoothingPreview();
    }
  }, [restoreSmoothingPreview, selectedMask?.id]);

  React.useEffect(() => {
    if (skipNextAutoAnalysisRef.current) {
      skipNextAutoAnalysisRef.current = false;
      return;
    }
    void refreshMaskAnalysis();
    // selectedMask is intentionally tracked by id and geometry fields to avoid
    // re-running analysis for unrelated store changes.
  }, [selectedMask?.id, selectedMask?.segmentation, selectedMask?.points, currentFrame?.id]);

  React.useEffect(() => {
    const smoothing = selectedMask?.metadata?.geometry_smoothing;
    const strength = smoothing && typeof smoothing === 'object'
      ? Number((smoothing as Record<string, unknown>).strength)
      : 0;
    setSmoothingStrength(Number.isFinite(strength) ? Math.min(Math.max(strength, 0), 100) : 0);
  }, [selectedMask?.id]);

  const applySmoothingResultToMask = React.useCallback((
    mask: Mask,
    result: SmoothMaskGeometryResult,
    options: { commit: boolean },
  ): Mask => {
      const metadata = { ...(mask.metadata || {}) };
      delete metadata.geometry_smoothing_preview;
      if (options.commit) {
        delete metadata.geometry_smoothing;
      } else {
        metadata.geometry_smoothing_preview = result.smoothing;
      }
      return {
        ...mask,
        pathData: result.pathData,
        segmentation: result.segmentation,
        bbox: result.bbox,
        area: result.area,
        metadata,
        ...(options.commit
          ? {
              saveStatus: mask.annotationId ? 'dirty' as const : 'draft' as const,
              saved: mask.annotationId ? false : mask.saved,
            }
          : {}),
      };
  }, []);

  const updateMaskWithSmoothingResult = React.useCallback((
    maskId: string,
    result: SmoothMaskGeometryResult,
    options: { commit: boolean },
  ) => {
    const state = useStore.getState();
    const nextMasks = state.masks.map((mask) => (
      mask.id === maskId ? applySmoothingResultToMask(mask, result, options) : mask
    ));
    if (options.commit) {
      setMasks(nextMasks);
    } else {
      useStore.setState({ masks: nextMasks });
    }
  }, [applySmoothingResultToMask, setMasks]);

  const applySmoothingResultToAnalysis = React.useCallback((
    result: SmoothMaskGeometryResult,
    sourceMask: NonNullable<typeof selectedMask>,
    suffix: string,
  ) => {
    setMaskAnalysis({
      confidence: null,
      confidence_source: 'manual_or_imported',
      topology_anchor_count: result.topology_anchor_count,
      topology_anchors: result.topology_anchors,
      area: result.area,
      bbox: result.bbox,
      source: sourceMask.metadata?.source as string | undefined,
      message: result.message,
    });
    setAnalysisMessage(`${result.message}${suffix}`);
  }, []);

  const runSmoothingPreview = React.useCallback(async (nextStrength: number) => {
    if (!selectedMask || !currentFrame) return;

    const existingPreview = smoothingPreviewRef.current?.maskId === selectedMask.id
      ? smoothingPreviewRef.current
      : null;
    const baseMask = existingPreview?.baseMask || selectedMask;
    const requestId = smoothingRequestIdRef.current + 1;
    smoothingRequestIdRef.current = requestId;

    if (nextStrength <= 0) {
      clearSmoothingPreviewTimer();
      smoothingPreviewRef.current = {
        maskId: selectedMask.id,
        baseMask,
        strength: 0,
        result: null,
        applied: false,
        requestId,
      };
      skipNextAutoAnalysisRef.current = true;
      useStore.setState({
        masks: useStore.getState().masks.map((mask) => (mask.id === selectedMask.id ? baseMask : mask)),
      });
      setAnalysisMessage('已预览恢复原始边缘，点击应用后写入当前 mask。');
      setIsPreviewingSmoothing(false);
      return;
    }

    setAnalysisMessage('正在生成边缘平滑预览...');
    try {
      const result = await smoothMaskGeometry(baseMask, currentFrame, nextStrength);
      if (smoothingRequestIdRef.current !== requestId) return;
      smoothingPreviewRef.current = {
        maskId: selectedMask.id,
        baseMask,
        strength: nextStrength,
        result,
        applied: false,
        requestId,
      };
      skipNextAutoAnalysisRef.current = true;
      updateMaskWithSmoothingResult(selectedMask.id, result, { commit: false });
      applySmoothingResultToAnalysis(result, baseMask, '，预览中，点击应用后写入当前 mask。');
    } catch (err) {
      if (smoothingRequestIdRef.current !== requestId) return;
      console.error('Mask smoothing preview failed:', err);
      setAnalysisMessage('边缘平滑预览失败，请检查后端服务');
    } finally {
      if (smoothingRequestIdRef.current === requestId) {
        setIsPreviewingSmoothing(false);
      }
    }
  }, [applySmoothingResultToAnalysis, clearSmoothingPreviewTimer, currentFrame, selectedMask, updateMaskWithSmoothingResult]);

  const previewSmoothing = React.useCallback((nextStrength: number) => {
    setSmoothingStrength(nextStrength);
    clearSmoothingPreviewTimer();
    if (!selectedMask || !currentFrame) return;
    if (nextStrength <= 0) {
      void runSmoothingPreview(nextStrength);
      return;
    }
    setIsPreviewingSmoothing(true);
    setAnalysisMessage('正在等待停止拖动后生成边缘平滑预览...');
    smoothingPreviewTimerRef.current = window.setTimeout(() => {
      smoothingPreviewTimerRef.current = null;
      void runSmoothingPreview(nextStrength);
    }, SMOOTHING_PREVIEW_DEBOUNCE_MS);
  }, [clearSmoothingPreviewTimer, currentFrame, runSmoothingPreview, selectedMask]);

  const handleApplySmoothing = async () => {
    if (!selectedMask || !currentFrame) {
      setAnalysisMessage('请选择一个 mask 后再应用边缘平滑');
      return;
    }
    clearSmoothingPreviewTimer();
    smoothingRequestIdRef.current += 1;
    setIsSmoothingMask(true);
    setAnalysisMessage('');
    try {
      const existingPreview = smoothingPreviewRef.current?.maskId === selectedMask.id
        && smoothingPreviewRef.current.strength === smoothingStrength
        ? smoothingPreviewRef.current
        : null;
      const baseMask = existingPreview?.baseMask || selectedMask;
      if (smoothingStrength <= 0) {
        smoothingPreviewRef.current = null;
        setSmoothingStrength(0);
        setAnalysisMessage('边缘平滑强度为 0，当前 mask 保持原始边缘。');
        return;
      }

      const state = useStore.getState();
      const frameById = new Map(state.frames.map((frame) => [String(frame.id), frame]));
      const chainMaskIds = findPropagationChainMaskIds(baseMask, state.masks);
      chainMaskIds.add(selectedMask.id);
      const selectedResult = existingPreview?.result || await smoothMaskGeometry(baseMask, currentFrame, smoothingStrength);
      const resultEntries = new Map<string, SmoothMaskGeometryResult>();
      resultEntries.set(selectedMask.id, selectedResult);

      await Promise.all(
        Array.from(chainMaskIds)
          .filter((maskId) => maskId !== selectedMask.id)
          .map(async (maskId) => {
            const mask = state.masks.find((item) => item.id === maskId);
            const frame = mask ? frameById.get(String(mask.frameId)) : null;
            if (!mask || !frame) return;
            resultEntries.set(maskId, await smoothMaskGeometry(mask, frame, smoothingStrength));
          }),
      );

      const latestMasks = useStore.getState().masks;
      const historyBaseMasks = latestMasks.map((mask) => (mask.id === selectedMask.id ? baseMask : mask));
      useStore.setState({ masks: historyBaseMasks });
      const nextMasks = historyBaseMasks.map((mask) => {
        const result = resultEntries.get(mask.id);
        if (!result) return mask;
        return applySmoothingResultToMask(mask, result, { commit: true });
      });
      skipNextAutoAnalysisRef.current = true;
      setMasks(nextMasks);
      if (smoothingPreviewRef.current) {
        smoothingPreviewRef.current.applied = true;
      }
      smoothingPreviewRef.current = null;
      setSmoothingStrength(0);
      applySmoothingResultToAnalysis(
        selectedResult,
        baseMask,
        resultEntries.size > 1
          ? `，已同步应用到传播链 ${resultEntries.size} 个对应 mask，强度已重置为 0，请保存后生效`
          : '，已变为新的 mask，强度已重置为 0，请保存后生效',
      );
    } catch (err) {
      console.error('Mask smoothing failed:', err);
      setAnalysisMessage('边缘平滑失败，请检查后端服务');
    } finally {
      setIsSmoothingMask(false);
    }
  };

  const handleAddCustom = async () => {
    if (!newClassName.trim()) return;
    if (!activeTemplate) {
      setClassSaveMessage('请先选择一个模板');
      return;
    }
    const activeClasses = templateClasses.filter((templateClass) => !isReservedUnclassifiedClass(templateClass));
    const maxZ = activeClasses.length > 0 ? Math.max(...activeClasses.map((c) => c.zIndex)) : 0;
    const newClass: TemplateClass = {
      id: `custom-${Date.now()}`,
      name: newClassName.trim(),
      color: newClassColor,
      zIndex: maxZ + 10,
      maskId: nextClassMaskId(templateClasses),
      category: '自定义',
    };
    setIsSavingClass(true);
    setClassSaveMessage('');
    try {
      const updated = await updateTemplate(activeTemplate.id, {
        name: activeTemplate.name,
        description: activeTemplate.description,
        classes: normalizeClassMaskIds([...templateClasses, newClass]),
        rules: activeTemplate.rules || [],
      });
      updateTemplateStore(updated);
      setActiveTemplateId(updated.id);
      handleSelectClass(newClass);
      setNewClassName('');
      setShowAddForm(false);
      setClassSaveMessage('自定义分类已保存到后端模板');
    } catch (err) {
      console.error('Save custom class failed:', err);
      setClassSaveMessage('自定义分类保存失败');
    } finally {
      setIsSavingClass(false);
    }
  };

  const handleReorderClass = async (sourceClassId: string, targetClassId: string) => {
    if (!activeTemplate || sourceClassId === targetClassId) {
      setDragClassId(null);
      return;
    }
    const sourceIndex = allClasses.findIndex((item) => item.id === sourceClassId);
    const targetIndex = allClasses.findIndex((item) => item.id === targetClassId);
    if (sourceIndex < 0 || targetIndex < 0) {
      setDragClassId(null);
      return;
    }
    if (isReservedUnclassifiedClass(allClasses[sourceIndex]) || isReservedUnclassifiedClass(allClasses[targetIndex])) {
      setDragClassId(null);
      return;
    }

    const reordered = [...allClasses];
    const [source] = reordered.splice(sourceIndex, 1);
    reordered.splice(targetIndex, 0, source);
    const nextClasses = normalizeClassMaskIds(
      reordered
        .filter((item) => !isReservedUnclassifiedClass(item))
        .map((item, index, activeItems) => ({
        ...item,
        zIndex: (activeItems.length - index) * 10,
      })),
    );

    setIsSavingClass(true);
    setClassSaveMessage('正在保存分类覆盖顺序...');
    try {
      const updated = await updateTemplate(activeTemplate.id, {
        name: activeTemplate.name,
        description: activeTemplate.description,
        classes: nextClasses,
        rules: activeTemplate.rules || [],
      });
      updateTemplateStore(updated);
      setActiveTemplateId(updated.id);
      const zIndexByClassId = new Map(nextClasses.map((item) => [item.id, item.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 : mask.saveStatus,
              saved: mask.annotationId ? false : mask.saved,
            }
          : mask
      )));
      const nextActiveClass = nextClasses.find((item) => item.id === activeClassId);
      if (nextActiveClass) setActiveClass(nextActiveClass);
      setClassSaveMessage('分类覆盖顺序已保存');
    } catch (err) {
      console.error('Reorder class failed:', err);
      setClassSaveMessage('分类覆盖顺序保存失败');
    } finally {
      setIsSavingClass(false);
      setDragClassId(null);
    }
  };

  return (
    <div className="w-60 bg-[#0d0d0d] flex flex-col border-l border-white/5 shrink-0 z-10 overflow-hidden">
      <div className="flex-1 overflow-y-auto seg-scrollbar p-4 flex flex-col gap-6">
        {/* Template Selector */}
        <div>
          <h3 className="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-2">当前激活模板</h3>
          <div className="relative">
            <select
              value={activeTemplate?.id || ''}
              onChange={(e) => {
                requestActiveTemplateChange(e.target.value || null);
              }}
              disabled={isSwitchingTemplate}
              className="w-full bg-[#1a1a1a] border border-white/10 rounded-lg px-3 py-2 text-xs text-gray-300 appearance-none cursor-pointer focus:outline-none focus:border-cyan-500/50"
            >
              <option value="">-- 选择模板 --</option>
              {templates.map((t) => (
                <option key={t.id} value={t.id}>{t.name}</option>
              ))}
            </select>
            <ChevronDown size={12} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none" />
          </div>
          {activeTemplate && (
            <div className="mt-2 text-[10px] text-gray-600">
              {activeTemplate.classes?.length ?? 0} 个分类来自模板
            </div>
          )}
        </div>

        {/* Workspace Mask Opacity */}
        <div>
          <div className="mb-2 flex items-center justify-between">
            <label htmlFor="workspace-mask-opacity" className="text-[10px] font-bold text-gray-500 uppercase tracking-widest">
              遮罩透明度
            </label>
            <span className="text-[10px] font-mono text-cyan-400">{maskPreviewOpacity}%</span>
          </div>
          <input
            id="workspace-mask-opacity"
            aria-label="遮罩透明度"
            type="range"
            min={10}
            max={100}
            step={5}
            value={maskPreviewOpacity}
            onChange={(event) => setMaskPreviewOpacity(Number(event.target.value))}
            className="w-full accent-cyan-500"
          />
        </div>

        {/* Semantic Classification Tree */}
        <div>
           <h3 className="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-3 flex justify-between items-center">
             <span>语义分类树（拖拽调层级）</span>
           </h3>
           <div className="space-y-2">
             {allClasses.map(cls => (
               <div key={cls.id} className="flex flex-col gap-1">
                 <button
                   type="button"
                   draggable={Boolean(activeTemplate) && !isSavingClass && !isReservedUnclassifiedClass(cls)}
                   ref={(node) => {
                     if (node) {
                       classButtonRefs.current.set(cls.id, node);
                     } else {
                       classButtonRefs.current.delete(cls.id);
                     }
                   }}
                   onClick={() => handleSelectClass(cls)}
                   onDragStart={(event) => {
                     if (isReservedUnclassifiedClass(cls)) return;
                     setDragClassId(cls.id);
                     event.dataTransfer.effectAllowed = 'move';
                     event.dataTransfer.setData('text/plain', cls.id);
                   }}
                   onDragOver={(event) => {
                     if (!dragClassId || dragClassId === cls.id || isReservedUnclassifiedClass(cls)) return;
                     event.preventDefault();
                     event.dataTransfer.dropEffect = 'move';
                   }}
                   onDrop={(event) => {
                     event.preventDefault();
                     const sourceId = event.dataTransfer.getData('text/plain') || dragClassId;
                     if (sourceId) void handleReorderClass(sourceId, cls.id);
                   }}
                   onDragEnd={() => setDragClassId(null)}
                   aria-current={activeClassId === cls.id ? 'true' : undefined}
                   className={cn(
                     'flex items-center justify-between p-2 rounded bg-white/5 hover:bg-white/10 cursor-pointer group transition-colors text-left border',
                     activeClassId === cls.id ? 'border-cyan-500/50 bg-cyan-500/10' : 'border-transparent',
                     dragClassId === cls.id && 'opacity-50',
                     isReservedUnclassifiedClass(cls) && 'cursor-default',
                   )}
                 >
                   <div className="flex items-center gap-2">
                     <GripVertical size={13} className={cn("text-gray-600 group-hover:text-gray-400", isReservedUnclassifiedClass(cls) && "text-gray-800 group-hover:text-gray-800")} aria-hidden="true" />
                     <span className="w-2.5 h-2.5 rounded-sm" style={{ backgroundColor: cls.color }} />
                     <span className="text-xs font-medium text-gray-200">{cls.name}</span>
                   </div>
                   <div className="flex items-center gap-3">
                     <span className="text-[10px] text-gray-500 font-mono">maskid:{cls.maskId}</span>
                     <Eye size={14} className="text-gray-500 group-hover:text-gray-300" />
                   </div>
                 </button>
               </div>
             ))}
             {allClasses.length === 0 && (
               <div className="text-xs text-gray-600 text-center py-4">请先选择一个模板</div>
             )}
           </div>
        </div>

        {/* Add Custom Class */}
        <div>
           <div className="flex justify-between items-center mb-2">
             <h3 className="text-[10px] font-bold text-gray-500 uppercase tracking-widest">自定义分类</h3>
             <button
               onClick={() => setShowAddForm(!showAddForm)}
               className="text-cyan-400 hover:text-cyan-300 transition-colors"
             >
               <Plus size={12} />
             </button>
           </div>
           {showAddForm && (
             <div className="bg-[#1a1a1a] border border-white/10 rounded-lg p-3 space-y-2">
               <div className="flex items-center gap-2">
                 <input
                   type="color"
                   value={newClassColor}
                   onChange={(e) => setNewClassColor(e.target.value)}
                   className="w-8 h-8 rounded bg-transparent border-0 cursor-pointer"
                 />
                 <input
                   type="text"
                   value={newClassName}
                   onChange={(e) => setNewClassName(e.target.value)}
                   placeholder="分类名称"
                   className="flex-1 bg-[#111] border border-white/10 rounded px-2 py-1 text-xs text-white"
                   onKeyDown={(e) => e.key === 'Enter' && handleAddCustom()}
                 />
                 <button onClick={handleAddCustom} className="text-cyan-400 hover:text-cyan-300">
                   {isSavingClass ? <Loader2 size={14} className="animate-spin" /> : <Plus size={14} />}
                 </button>
                 <button onClick={() => setShowAddForm(false)} className="text-gray-500 hover:text-gray-300">
                   <X size={14} />
                 </button>
               </div>
             </div>
           )}
           {classSaveMessage && (
             <div className="mt-2 text-[10px] text-gray-500">{classSaveMessage}</div>
           )}
        </div>

        {/* Current Active Object Properties */}
        <div className="mt-4 pt-4 border-t border-[#222]">
           <h3 className="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-3">特定目标实例属性追踪</h3>
           <div className="bg-white/5 rounded-lg p-3">
             <div className="flex items-center gap-2 mb-3">
               <Tag size={12} className="text-cyan-400" />
               <span className="text-xs font-semibold text-gray-200">
                 {selectedMaskLabel}
               </span>
             </div>
             <div className="space-y-3">
               <div className="flex items-center justify-between">
                 <span className="text-[10px] text-gray-500 uppercase">后端拓扑锚点:</span>
                 <span className="text-xs font-mono text-gray-300">{maskAnalysis?.topology_anchor_count ?? 0} 节点</span>
               </div>
               <div>
                 <div className="mb-2 flex items-center justify-between">
                   <label htmlFor="mask-edge-smoothing" className="text-[10px] text-gray-500 uppercase">边缘平滑强度:</label>
                   <span className="text-xs font-mono text-gray-300">{smoothingStrength}%</span>
                 </div>
                 <input
                   id="mask-edge-smoothing"
                   aria-label="边缘平滑强度"
                   type="range"
                   min={0}
                   max={100}
                   step={5}
                   value={smoothingStrength}
                   onChange={(event) => void previewSmoothing(Number(event.target.value))}
                   disabled={!selectedMask || isSmoothingMask}
                   className="w-full accent-cyan-500 disabled:opacity-40"
                 />
                 <button
                   onClick={handleApplySmoothing}
                   disabled={!selectedMask || !currentFrame || isSmoothingMask || isPreviewingSmoothing}
                   className="mt-2 w-full bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-500/20 text-xs text-cyan-100 py-1.5 rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
                 >
                   {isSmoothingMask ? '平滑中...' : isPreviewingSmoothing ? '预览中...' : '应用边缘平滑'}
                 </button>
               </div>
               {analysisMessage && (
                 <div className="text-[10px] leading-relaxed text-gray-500">{analysisMessage}</div>
               )}
             </div>
           </div>
        </div>
      </div>
      {pendingTemplateSwitch && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 px-4">
          <div className="w-full max-w-md rounded-lg border border-amber-400/25 bg-[#151515] p-5 shadow-2xl">
            <h2 className="text-lg font-semibold text-white">确认修改激活模板</h2>
            <p className="mt-2 text-sm leading-relaxed text-gray-300">
              当前项目已经存在 {masks.length} 个 mask。修改激活模板会清空当前项目中所有已有 mask 和已保存标注，避免旧模板类别继续混用。
            </p>
            <p className="mt-2 text-xs leading-relaxed text-amber-200/70">
              项目没有任何 mask 时切换模板不会提示。
            </p>
            <div className="mt-5 flex justify-end gap-2">
              <button
                type="button"
                onClick={() => setPendingTemplateSwitch(null)}
                disabled={isSwitchingTemplate}
                className="rounded border border-white/10 px-3 py-2 text-xs text-gray-300 hover:bg-white/5 disabled:opacity-50"
              >
                取消
              </button>
              <button
                type="button"
                onClick={() => void confirmActiveTemplateChange()}
                disabled={isSwitchingTemplate}
                className="rounded bg-amber-500 px-3 py-2 text-xs font-semibold text-black hover:bg-amber-400 disabled:opacity-60"
              >
                {isSwitchingTemplate ? '正在切换...' : '清空并切换'}
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
