import React, { useEffect, useRef, useState, useCallback } from 'react';
import { Stage, Layer, Image as KonvaImage, Circle, Rect, Path, Group } from 'react-konva';
import polygonClipping, { type MultiPolygon, type Pair } from 'polygon-clipping';
import useImage from 'use-image';
import { useStore } from '../store/useStore';
import { predictMask } from '../lib/api';
import type { Frame, Mask } from '../store/useStore';

interface CanvasAreaProps {
  activeTool: string;
  frame: Frame | null;
  onClearMasks?: () => void;
  onDeleteMaskAnnotations?: (annotationIds: string[]) => Promise<void> | void;
}

type CanvasPoint = { x: number; y: number };
type PromptPoint = CanvasPoint & { type: 'pos' | 'neg' };
type PromptBox = { x1: number; y1: number; x2: number; y2: number };
type ToolHint = { title: string; body: string };

const DRAG_MANUAL_TOOLS = new Set(['create_rectangle', 'create_circle', 'create_line']);
const POLYGON_TOOL = 'create_polygon';
const EDIT_POLYGON_TOOL = 'edit_polygon';
const POINT_TOOL = 'create_point';
const BOOLEAN_TOOLS = new Set(['area_merge', 'area_remove']);
const POLYGON_CLOSE_RADIUS = 8;

function clamp(value: number, min: number, max: number): number {
  return Math.min(Math.max(value, min), max);
}

function polygonPath(points: CanvasPoint[]): string {
  if (points.length === 0) return '';
  return points
    .map((point, index) => `${index === 0 ? 'M' : 'L'} ${point.x} ${point.y}`)
    .join(' ')
    .concat(' Z');
}

function segmentationPath(segmentation?: number[][]): string {
  return (segmentation || [])
    .map((polygon) => polygonPath(flatPolygonToPoints(polygon)))
    .filter(Boolean)
    .join(' ');
}

function segmentationPolygonPath(segmentation: number[][] | undefined, polygonIndex: number): string {
  const polygon = segmentation?.[polygonIndex];
  return polygon ? polygonPath(flatPolygonToPoints(polygon)) : '';
}

function polygonSegmentation(points: CanvasPoint[]): number[][] {
  return [points.flatMap((point) => [point.x, point.y])];
}

function segmentationToPoints(segmentation?: number[][], polygonIndex = 0): CanvasPoint[] {
  const polygon = segmentation?.[polygonIndex] || [];
  const points: CanvasPoint[] = [];
  for (let index = 0; index < polygon.length - 1; index += 2) {
    points.push({ x: polygon[index], y: polygon[index + 1] });
  }
  return points;
}

function flatPolygonToPoints(polygon: number[]): CanvasPoint[] {
  const points: CanvasPoint[] = [];
  for (let index = 0; index < polygon.length - 1; index += 2) {
    points.push({ x: polygon[index], y: polygon[index + 1] });
  }
  return points;
}

function segmentationAllPoints(segmentation?: number[][]): CanvasPoint[] {
  return (segmentation || []).flatMap((polygon) => flatPolygonToPoints(polygon));
}

function polygonBbox(points: CanvasPoint[]): [number, number, number, number] {
  const xs = points.map((point) => point.x);
  const ys = points.map((point) => point.y);
  const minX = Math.min(...xs);
  const minY = Math.min(...ys);
  const maxX = Math.max(...xs);
  const maxY = Math.max(...ys);
  return [minX, minY, maxX - minX, maxY - minY];
}

function polygonArea(points: CanvasPoint[]): number {
  if (points.length < 3) return 0;
  const sum = points.reduce((acc, point, index) => {
    const next = points[(index + 1) % points.length];
    return acc + point.x * next.y - next.x * point.y;
  }, 0);
  return Math.abs(sum) / 2;
}

function pointDistance(a: CanvasPoint, b: CanvasPoint): number {
  return Math.hypot(a.x - b.x, a.y - b.y);
}

function distanceToSegmentSquared(point: CanvasPoint, start: CanvasPoint, end: CanvasPoint): number {
  const dx = end.x - start.x;
  const dy = end.y - start.y;
  const lengthSquared = dx * dx + dy * dy;
  if (lengthSquared === 0) {
    return (point.x - start.x) ** 2 + (point.y - start.y) ** 2;
  }
  const t = clamp(((point.x - start.x) * dx + (point.y - start.y) * dy) / lengthSquared, 0, 1);
  const projected = { x: start.x + t * dx, y: start.y + t * dy };
  return (point.x - projected.x) ** 2 + (point.y - projected.y) ** 2;
}

function nearestPolygonEdgeIndex(points: CanvasPoint[], point: CanvasPoint): number {
  return points.reduce((bestIndex, start, index) => {
    const end = points[(index + 1) % points.length];
    if (!end) return bestIndex;
    const bestStart = points[bestIndex];
    const bestEnd = points[(bestIndex + 1) % points.length];
    const currentDistance = distanceToSegmentSquared(point, start, end);
    const bestDistance = bestStart && bestEnd
      ? distanceToSegmentSquared(point, bestStart, bestEnd)
      : Number.POSITIVE_INFINITY;
    return currentDistance < bestDistance ? index : bestIndex;
  }, 0);
}

function segmentationArea(segmentation?: number[][]): number {
  return (segmentation || []).reduce((sum, polygon) => sum + polygonArea(flatPolygonToPoints(polygon)), 0);
}

function segmentationBbox(segmentation?: number[][]): [number, number, number, number] | undefined {
  const points = segmentationAllPoints(segmentation);
  return points.length > 0 ? polygonBbox(points) : undefined;
}

function closeRing(points: CanvasPoint[]): Pair[] {
  const ring = points.map((point) => [point.x, point.y] as Pair);
  const first = ring[0];
  const last = ring[ring.length - 1];
  if (first && last && (first[0] !== last[0] || first[1] !== last[1])) {
    ring.push([first[0], first[1]]);
  }
  return ring;
}

function maskToMultiPolygon(mask: Mask): MultiPolygon | null {
  const polygons = (mask.segmentation || [])
    .map((polygon) => flatPolygonToPoints(polygon))
    .filter((points) => points.length >= 3)
    .map((points) => [closeRing(points)]);
  return polygons.length > 0 ? polygons : null;
}

function openRingPoints(ring: Pair[]): CanvasPoint[] {
  const openRing = ring.length > 1
    && ring[0][0] === ring[ring.length - 1][0]
    && ring[0][1] === ring[ring.length - 1][1]
    ? ring.slice(0, -1)
    : ring;
  return openRing.map(([x, y]) => ({ x, y }));
}

function multiPolygonToSegmentation(geometry: MultiPolygon): number[][] {
  return geometry
    .flatMap((polygon) => polygon)
    .map((ring) => openRingPoints(ring).flatMap(({ x, y }) => [x, y]))
    .filter((polygon) => polygon.length >= 6);
}

function multiPolygonArea(geometry: MultiPolygon): number {
  return geometry.reduce((sum, polygon) => {
    const [outerRing, ...holeRings] = polygon;
    const outerArea = outerRing ? polygonArea(openRingPoints(outerRing)) : 0;
    const holesArea = holeRings.reduce((holeSum, ring) => holeSum + polygonArea(openRingPoints(ring)), 0);
    return sum + Math.max(outerArea - holesArea, 0);
  }, 0);
}

function multiPolygonHasHoles(geometry: MultiPolygon): boolean {
  return geometry.some((polygon) => polygon.length > 1);
}

function rectanglePoints(start: CanvasPoint, end: CanvasPoint): CanvasPoint[] {
  const x1 = Math.min(start.x, end.x);
  const y1 = Math.min(start.y, end.y);
  const x2 = Math.max(start.x, end.x);
  const y2 = Math.max(start.y, end.y);
  return [
    { x: x1, y: y1 },
    { x: x2, y: y1 },
    { x: x2, y: y2 },
    { x: x1, y: y2 },
  ];
}

function circlePoints(start: CanvasPoint, end: CanvasPoint): CanvasPoint[] {
  const cx = (start.x + end.x) / 2;
  const cy = (start.y + end.y) / 2;
  const rx = Math.abs(end.x - start.x) / 2;
  const ry = Math.abs(end.y - start.y) / 2;
  return Array.from({ length: 32 }, (_, index) => {
    const angle = (Math.PI * 2 * index) / 32;
    return { x: cx + Math.cos(angle) * rx, y: cy + Math.sin(angle) * ry };
  });
}

function pointRegion(point: CanvasPoint, radius = 5): CanvasPoint[] {
  return Array.from({ length: 12 }, (_, index) => {
    const angle = (Math.PI * 2 * index) / 12;
    return { x: point.x + Math.cos(angle) * radius, y: point.y + Math.sin(angle) * radius };
  });
}

function lineRegion(start: CanvasPoint, end: CanvasPoint, halfWidth = 4): CanvasPoint[] {
  const dx = end.x - start.x;
  const dy = end.y - start.y;
  const length = Math.hypot(dx, dy) || 1;
  const nx = (-dy / length) * halfWidth;
  const ny = (dx / length) * halfWidth;
  return [
    { x: start.x + nx, y: start.y + ny },
    { x: end.x + nx, y: end.y + ny },
    { x: end.x - nx, y: end.y - ny },
    { x: start.x - nx, y: start.y - ny },
  ];
}

export function CanvasArea({ activeTool, frame, onClearMasks, onDeleteMaskAnnotations }: CanvasAreaProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [stageSize, setStageSize] = useState({ width: 800, height: 600 });
  const [scale, setScale] = useState(1);
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [points, setPoints] = useState<PromptPoint[]>([]);
  const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
  const [boxStart, setBoxStart] = useState<{ x: number, y: number } | null>(null);
  const [boxCurrent, setBoxCurrent] = useState<{ x: number, y: number } | null>(null);
  const [samPromptBox, setSamPromptBox] = useState<PromptBox | null>(null);
  const [samCandidateMaskId, setSamCandidateMaskId] = useState<string | null>(null);
  const [manualStart, setManualStart] = useState<CanvasPoint | null>(null);
  const [manualCurrent, setManualCurrent] = useState<CanvasPoint | null>(null);
  const [polygonPoints, setPolygonPoints] = useState<CanvasPoint[]>([]);
  const [selectedMaskId, setSelectedMaskId] = useState<string | null>(() => useStore.getState().selectedMaskIds[0] || null);
  const [selectedMaskIds, setSelectedMaskIds] = useState<string[]>(() => useStore.getState().selectedMaskIds);
  const [selectedPolygonIndex, setSelectedPolygonIndex] = useState(0);
  const [selectedVertexIndex, setSelectedVertexIndex] = useState<number | null>(null);
  const previousFrameIdRef = useRef<string | undefined>(frame?.id);
  const [isInferencing, setIsInferencing] = useState(false);
  const [inferenceMessage, setInferenceMessage] = useState('');

  const masks = useStore((state) => state.masks);
  const addMask = useStore((state) => state.addMask);
  const updateMask = useStore((state) => state.updateMask);
  const clearMasks = useStore((state) => state.clearMasks);
  const setMasks = useStore((state) => state.setMasks);
  const setGlobalSelectedMaskIds = useStore((state) => state.setSelectedMaskIds);
  const storeActiveTool = useStore((state) => state.activeTool);
  const aiModel = useStore((state) => state.aiModel);
  const activeTemplateId = useStore((state) => state.activeTemplateId);
  const activeClass = useStore((state) => state.activeClass);
  const undoMasks = useStore((state) => state.undoMasks);
  const redoMasks = useStore((state) => state.redoMasks);

  const effectiveTool = activeTool || storeActiveTool;

  // Load the actual frame image
  const [image] = useImage(frame?.url || '');
  const frameMasks = masks.filter((mask) => mask.frameId === frame?.id);
  const selectedMask = React.useMemo(
    () => frameMasks.find((mask) => mask.id === selectedMaskId) || null,
    [frameMasks, selectedMaskId],
  );
  const booleanSelectedMasks = React.useMemo(
    () => selectedMaskIds
      .map((id) => frameMasks.find((mask) => mask.id === id))
      .filter((mask): mask is Mask => Boolean(mask)),
    [frameMasks, selectedMaskIds],
  );
  const selectedMaskPoints = React.useMemo(
    () => segmentationToPoints(selectedMask?.segmentation, selectedPolygonIndex),
    [selectedMask?.segmentation, selectedPolygonIndex],
  );
  const savedMaskCount = frameMasks.filter((mask) => mask.saveStatus === 'saved' || mask.saved).length;
  const draftMaskCount = frameMasks.filter((mask) => !mask.annotationId).length;
  const dirtyMaskCount = frameMasks.filter((mask) => mask.saveStatus === 'dirty').length;
  const isBooleanTool = BOOLEAN_TOOLS.has(effectiveTool);
  const isPolygonEditTool = effectiveTool === 'move' || effectiveTool === EDIT_POLYGON_TOOL;
  const currentLayerLabel = selectedMask
    ? `${selectedMask.className || selectedMask.label}${selectedMask.annotationId ? ` #${selectedMask.annotationId}` : ' (未保存)'}`
    : '未选择';
  const toolHint = React.useMemo<ToolHint | null>(() => {
    if (!frame) return null;
    if (effectiveTool === POLYGON_TOOL) {
      if (polygonPoints.length === 0) {
        return {
          title: '创建多边形',
          body: '点击画布添加顶点；至少 3 个点后，点击首节点或按 Enter 完成，按 Esc 取消。',
        };
      }
      if (polygonPoints.length < 3) {
        return {
          title: `创建多边形 · 已放置 ${polygonPoints.length} 点`,
          body: '继续点击添加顶点；满 3 个点后才能闭合，按 Esc 可取消当前多边形。',
        };
      }
      return {
        title: `创建多边形 · 已放置 ${polygonPoints.length} 点`,
        body: '点击黄色首节点或按 Enter 闭合完成；按 Esc 放弃当前多边形。',
      };
    }
    if (effectiveTool === 'create_rectangle') {
      return { title: '创建矩形', body: '按住并拖拽框出区域，松开鼠标后生成 mask；切换工具可放弃当前操作。' };
    }
    if (effectiveTool === 'create_circle') {
      return { title: '创建圆形', body: '按住并拖拽确定外接范围，松开鼠标后生成椭圆 mask。' };
    }
    if (effectiveTool === 'create_line') {
      return { title: '创建线段', body: '按住并拖拽画出线段，松开后生成有宽度的线状 mask。' };
    }
    if (effectiveTool === POINT_TOOL) {
      return { title: '创建点区域', body: '点击画布创建一个小型点区域；也可以在已有 mask 上继续落点。' };
    }
    if (effectiveTool === 'box_select') {
      return {
        title: samPromptBox ? '边界框已建立' : '边界框选',
        body: samPromptBox
          ? '继续添加正向/反向点可细化同一个候选区域；重新拖拽会替换当前框。'
          : '按住并拖拽建立框选区域，松开后会触发 SAM 推理。',
      };
    }
    if (effectiveTool === 'point_pos') {
      return { title: '正向选点', body: '点击目标内部添加正向点并触发细化；点击已有提示点可删除并重新推理。' };
    }
    if (effectiveTool === 'point_neg') {
      return { title: '反向选点', body: '点击不应包含的区域添加反向点；点击已有提示点可删除并重新推理。' };
    }
    if (effectiveTool === 'area_merge') {
      return {
        title: '区域合并',
        body: booleanSelectedMasks.length > 0
          ? `已选 ${booleanSelectedMasks.length} 个区域；第一个选中的是主区域，点击“合并选中”完成。`
          : '依次点击多个 mask；第一个选中的区域会作为合并后的主区域。',
      };
    }
    if (effectiveTool === 'area_remove') {
      return {
        title: '重叠区域去除',
        body: booleanSelectedMasks.length > 0
          ? `已选 ${booleanSelectedMasks.length} 个区域；第一个是保留主区域，后续区域会被扣除。`
          : '先点击要保留的主区域，再点击要扣除的干涉区域。',
      };
    }
    if (effectiveTool === EDIT_POLYGON_TOOL || (effectiveTool === 'move' && selectedMask)) {
      return {
        title: selectedMask ? '调整多边形' : '调整多边形',
        body: selectedMask
          ? '可直接拖动白色顶点；点击青色边中点或双击边线新增顶点；选中顶点/区域后按 Delete 删除。'
          : '点击一个 mask 后，可拖动顶点、点击边中点新增顶点，或按 Delete 删除选中区域。',
      };
    }
    return null;
  }, [booleanSelectedMasks.length, effectiveTool, frame, polygonPoints.length, samPromptBox, selectedMask]);

  useEffect(() => {
    const handleResize = () => {
      if (containerRef.current) {
        setStageSize({
          width: containerRef.current.clientWidth,
          height: containerRef.current.clientHeight,
        });
      }
    };
    
    handleResize();
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  useEffect(() => {
    setManualStart(null);
    setManualCurrent(null);
    setPolygonPoints([]);
    setSelectedVertexIndex(null);
    if (!isPolygonEditTool && !isBooleanTool) {
      setSelectedMaskId(null);
      setSelectedMaskIds([]);
      setSelectedPolygonIndex(0);
    }
  }, [effectiveTool, isBooleanTool, isPolygonEditTool]);

  useEffect(() => {
    if (previousFrameIdRef.current === frame?.id) return;
    previousFrameIdRef.current = frame?.id;
    setSelectedMaskId(null);
    setSelectedMaskIds([]);
    setSelectedPolygonIndex(0);
    setSelectedVertexIndex(null);
  }, [frame?.id]);

  useEffect(() => {
    setPoints([]);
    setSamPromptBox(null);
    setSamCandidateMaskId(null);
  }, [frame?.id]);

  useEffect(() => {
    const currentGlobalSelectedIds = useStore.getState().selectedMaskIds;
    if (selectedMaskIds.length === 0) {
      const validGlobalSelectedIds = currentGlobalSelectedIds.filter((id) => (
        frameMasks.some((mask) => mask.id === id)
      ));
      if (validGlobalSelectedIds.length > 0) return;
    }
    const isSameSelection = currentGlobalSelectedIds.length === selectedMaskIds.length
      && currentGlobalSelectedIds.every((id, index) => id === selectedMaskIds[index]);
    if (!isSameSelection) {
      setGlobalSelectedMaskIds(selectedMaskIds);
    }
  }, [frameMasks, selectedMaskIds, setGlobalSelectedMaskIds]);

  useEffect(() => () => setGlobalSelectedMaskIds([]), [setGlobalSelectedMaskIds]);

  useEffect(() => {
    if (!selectedMaskId) {
      const validGlobalSelectedIds = useStore.getState().selectedMaskIds.filter((id) => (
        frameMasks.some((mask) => mask.id === id)
      ));
      if (validGlobalSelectedIds.length > 0) {
        setSelectedMaskId(validGlobalSelectedIds[0]);
        setSelectedMaskIds(validGlobalSelectedIds);
        setSelectedPolygonIndex(0);
        setSelectedVertexIndex(null);
        return;
      }
    }
    if (selectedMaskId && !frameMasks.some((mask) => mask.id === selectedMaskId)) {
      setSelectedMaskId(null);
      setSelectedMaskIds([]);
      setSelectedPolygonIndex(0);
      setSelectedVertexIndex(null);
    }
  }, [frameMasks, selectedMaskId]);

  const handleWheel = (e: any) => {
    e.evt.preventDefault();
    const scaleBy = 1.1;
    const stage = e.target.getStage();
    const oldScale = stage.scaleX();

    const mousePointTo = {
      x: stage.getPointerPosition().x / oldScale - stage.x() / oldScale,
      y: stage.getPointerPosition().y / oldScale - stage.y() / oldScale,
    };

    const newScale = e.evt.deltaY < 0 ? oldScale * scaleBy : oldScale / scaleBy;
    setScale(newScale);
    setPosition({
      x: -(mousePointTo.x - stage.getPointerPosition().x / newScale) * newScale,
      y: -(mousePointTo.y - stage.getPointerPosition().y / newScale) * newScale,
    });
  };

  const stagePoint = (e: any): CanvasPoint | null => {
    const stage = e.target.getStage();
    const relPos = stage?.getRelativePointerPosition();
    if (!relPos) return null;
    const imageWidth = frame?.width || image?.naturalWidth || image?.width || stageSize.width;
    const imageHeight = frame?.height || image?.naturalHeight || image?.height || stageSize.height;
    return {
      x: clamp(relPos.x, 0, imageWidth),
      y: clamp(relPos.y, 0, imageHeight),
    };
  };

  const createManualMask = useCallback((shape: string, polygon: CanvasPoint[]) => {
    if (!frame?.id || polygon.length < 3) return;
    const area = polygonArea(polygon);
    if (area <= 1) return;
    const color = activeClass?.color || '#06b6d4';
    const label = activeClass?.name || `手工${shape}`;
    const mask: Mask = {
      id: `manual-${frame.id}-${shape}-${Date.now()}`,
      frameId: frame.id,
      templateId: activeTemplateId || undefined,
      classId: activeClass?.id,
      className: activeClass?.name,
      classZIndex: activeClass?.zIndex,
      saveStatus: 'draft',
      saved: false,
      pathData: polygonPath(polygon),
      label,
      color,
      segmentation: polygonSegmentation(polygon),
      points: shape === '点区域'
        ? [[
          polygon.reduce((sum, point) => sum + point.x, 0) / polygon.length,
          polygon.reduce((sum, point) => sum + point.y, 0) / polygon.length,
        ]]
        : undefined,
      bbox: polygonBbox(polygon),
      area,
      metadata: { source: 'manual', shape },
    };
    addMask(mask);
  }, [activeClass, activeTemplateId, addMask, frame?.id]);

  const finishPolygon = useCallback(() => {
    if (polygonPoints.length < 3) return;
    createManualMask('多边形', polygonPoints);
    setPolygonPoints([]);
  }, [createManualMask, polygonPoints]);

  const handleMouseMove = (e: any) => {
    const stage = e.target.getStage();
    if (!stage) return;
    const pos = stage.getPointerPosition();
    if (pos) {
       const imageX = (pos.x - position.x) / scale;
       const imageY = (pos.y - position.y) / scale;
       setCursorPos({ x: imageX, y: imageY });
    }

    if (boxStart && effectiveTool === 'box_select') {
      const relPos = stage.getRelativePointerPosition();
      if (relPos) {
        setBoxCurrent({ x: relPos.x, y: relPos.y });
      }
    }

    if (manualStart && DRAG_MANUAL_TOOLS.has(effectiveTool)) {
      const pos = stage.getRelativePointerPosition();
      if (pos) {
        setManualCurrent({ x: pos.x, y: pos.y });
      }
    }
  };

  const runInference = useCallback(async (
    promptPoints?: PromptPoint[],
    promptBox?: PromptBox,
    options: { resetCandidate?: boolean } = {},
  ) => {
    if (!frame?.id) {
      console.warn('Inference skipped: no active frame');
      setInferenceMessage('请先选择一帧图像。');
      return;
    }
    const imageWidth = frame.width || image?.naturalWidth || image?.width || 0;
    const imageHeight = frame.height || image?.naturalHeight || image?.height || 0;
    if (imageWidth <= 0 || imageHeight <= 0) {
      console.warn('Inference skipped: active frame dimensions are unavailable');
      setInferenceMessage('当前帧缺少宽高信息，无法推理。');
      return;
    }

    setIsInferencing(true);
    setInferenceMessage('');
    try {
      const hasNegativePrompt = Boolean(promptPoints?.some((point) => point.type === 'neg'));
      const existingCandidate = !options.resetCandidate && samCandidateMaskId
        ? masks.find((mask) => mask.id === samCandidateMaskId)
        : null;
      const result = await predictMask({
        imageId: frame.id,
        imageWidth,
        imageHeight,
        model: aiModel,
        points: promptPoints && promptPoints.length > 0
          ? promptPoints.map((p) => ({ x: p.x, y: p.y, type: p.type }))
          : undefined,
        box: promptBox,
        ...(hasNegativePrompt ? { options: { auto_filter_background: true, min_score: 0.05 } } : {}),
      });

      const [m] = result.masks;
      if (m) {
        const label = activeClass?.name || existingCandidate?.label || m.label;
        const color = activeClass?.color || existingCandidate?.color || m.color;
        const metadata = {
          ...(existingCandidate?.metadata || {}),
          source: 'sam2_interactive',
          promptBox: promptBox || null,
          promptPointCount: promptPoints?.length || 0,
          promptNegativePointCount: promptPoints?.filter((point) => point.type === 'neg').length || 0,
        };
        const nextMask = {
          frameId: frame.id,
          templateId: activeTemplateId || existingCandidate?.templateId || undefined,
          classId: activeClass?.id || existingCandidate?.classId,
          className: activeClass?.name || existingCandidate?.className,
          classZIndex: activeClass?.zIndex ?? existingCandidate?.classZIndex,
          saveStatus: existingCandidate?.annotationId ? 'dirty' as const : 'draft' as const,
          saved: false,
          pathData: m.pathData,
          label,
          color,
          segmentation: m.segmentation,
          points: promptPoints?.filter((p) => p.type === 'pos').map((p) => [p.x, p.y]),
          bbox: m.bbox,
          area: m.area,
          metadata,
        };
        if (existingCandidate) {
          updateMask(existingCandidate.id, nextMask);
          setSelectedMaskId(existingCandidate.id);
          setSelectedMaskIds([existingCandidate.id]);
        } else {
          const id = m.id;
          setSamCandidateMaskId(id);
          setSelectedMaskId(id);
          setSelectedMaskIds([id]);
          addMask({
            id,
            ...nextMask,
          });
        }
      } else {
        if (existingCandidate && hasNegativePrompt) {
          setMasks(masks.filter((mask) => mask.id !== existingCandidate.id));
          setSamCandidateMaskId(null);
          setSelectedMaskId(null);
          setSelectedMaskIds([]);
          setInferenceMessage('反向点已排除当前候选区域，请重新框选或添加新的正向点。');
        } else {
          setInferenceMessage('模型没有返回可用区域，请调整点/框提示后重试。');
        }
      }
    } catch (err) {
      console.error('Inference failed:', err);
      const detail = (err as any)?.response?.data?.detail;
      setInferenceMessage(detail || 'AI 推理失败，请查看模型状态或后端日志。');
    } finally {
      setIsInferencing(false);
    }
  }, [activeClass, activeTemplateId, addMask, aiModel, frame?.height, frame?.id, frame?.width, image?.height, image?.naturalHeight, image?.naturalWidth, image?.width, masks, samCandidateMaskId, setMasks, updateMask]);

  const handleApplyActiveClass = () => {
    if (!frame?.id || !activeClass) return;
    setMasks(masks.map((mask) => {
      if (mask.frameId !== frame.id) return mask;
      return {
        ...mask,
        templateId: activeTemplateId || mask.templateId,
        classId: activeClass.id,
        className: activeClass.name,
        classZIndex: activeClass.zIndex,
        label: activeClass.name,
        color: activeClass.color,
        saveStatus: mask.annotationId ? 'dirty' : 'draft',
        saved: Boolean(mask.annotationId) ? false : mask.saved,
      };
    }));
  };

  const handleClearMasks = () => {
    if (onClearMasks) {
      onClearMasks();
      return;
    }
    clearMasks();
  };

  const deleteMasksById = useCallback((maskIds: string[]) => {
    if (maskIds.length === 0) return;
    const idSet = new Set(maskIds);
    const deletingMasks = masks.filter((mask) => idSet.has(mask.id));
    if (deletingMasks.length === 0) return;
    setMasks(masks.filter((mask) => !idSet.has(mask.id)));
    const annotationIds = deletingMasks
      .map((mask) => mask.annotationId)
      .filter((annotationId): annotationId is string => Boolean(annotationId));
    if (annotationIds.length > 0) {
      void onDeleteMaskAnnotations?.(annotationIds);
    }
    if (samCandidateMaskId && idSet.has(samCandidateMaskId)) {
      setSamCandidateMaskId(null);
      setSamPromptBox(null);
      setPoints([]);
    }
    setSelectedMaskId(null);
    setSelectedMaskIds([]);
    setSelectedPolygonIndex(0);
    setSelectedVertexIndex(null);
  }, [masks, onDeleteMaskAnnotations, samCandidateMaskId, setMasks]);

  const handleStageMouseDown = (e: any) => {
    if (DRAG_MANUAL_TOOLS.has(effectiveTool)) {
      const pos = stagePoint(e);
      if (pos) {
        setManualStart(pos);
        setManualCurrent(pos);
      }
      return;
    }

    if (effectiveTool === 'box_select') {
      const stage = e.target.getStage();
      const pos = stage.getRelativePointerPosition();
      if (pos) {
        setBoxStart({ x: pos.x, y: pos.y });
        setBoxCurrent({ x: pos.x, y: pos.y });
      }
    }
  };

  const handleStageMouseUp = (e: any) => {
    if (DRAG_MANUAL_TOOLS.has(effectiveTool) && manualStart) {
      const end = stagePoint(e) || manualCurrent || manualStart;
      const width = Math.abs(end.x - manualStart.x);
      const height = Math.abs(end.y - manualStart.y);
      const distance = Math.hypot(width, height);

      if (effectiveTool === 'create_rectangle' && width > 4 && height > 4) {
        createManualMask('矩形', rectanglePoints(manualStart, end));
      }
      if (effectiveTool === 'create_circle' && width > 4 && height > 4) {
        createManualMask('圆形', circlePoints(manualStart, end));
      }
      if (effectiveTool === 'create_line' && distance > 4) {
        createManualMask('线段', lineRegion(manualStart, end));
      }

      setManualStart(null);
      setManualCurrent(null);
      return;
    }

    if (effectiveTool === 'box_select' && boxStart && boxCurrent) {
      const x1 = Math.min(boxStart.x, boxCurrent.x);
      const y1 = Math.min(boxStart.y, boxCurrent.y);
      const x2 = Math.max(boxStart.x, boxCurrent.x);
      const y2 = Math.max(boxStart.y, boxCurrent.y);
      
      if (Math.abs(x2 - x1) > 5 && Math.abs(y2 - y1) > 5) {
        const nextBox = { x1, y1, x2, y2 };
        setPoints([]);
        setSamPromptBox(nextBox);
        setSamCandidateMaskId(null);
        runInference([], nextBox, { resetCandidate: true });
      }
      
      setBoxStart(null);
      setBoxCurrent(null);
    }
  };

  const handleStageClick = (e: any) => {
    if (isPolygonEditTool) return;
    if (effectiveTool === 'box_select') return; // handled by mouseup
    if (DRAG_MANUAL_TOOLS.has(effectiveTool)) return;

    if (effectiveTool === POINT_TOOL) {
      const pos = stagePoint(e);
      if (pos) {
        createManualMask('点区域', pointRegion(pos));
      }
      return;
    }

    if (effectiveTool === POLYGON_TOOL) {
      const pos = stagePoint(e);
      if (pos) {
        const closeRadius = POLYGON_CLOSE_RADIUS / Math.max(scale, 0.1);
        if (polygonPoints.length >= 3 && pointDistance(pos, polygonPoints[0]) <= closeRadius) {
          finishPolygon();
          return;
        }
        setPolygonPoints((current) => [...current, pos]);
      }
      return;
    }

    if (effectiveTool === 'point_pos' || effectiveTool === 'point_neg') {
      const stage = e.target.getStage();
      const pos = stage.getRelativePointerPosition();
      if (pos) {
        const newPoints = [
          ...points,
          { x: pos.x, y: pos.y, type: (effectiveTool === 'point_pos' ? 'pos' : 'neg') as 'pos' | 'neg' },
        ];
        setPoints(newPoints);
        runInference(newPoints, samPromptBox || undefined);
      }
    }
  };

  const removePromptPoint = useCallback((pointIndex: number, event?: any) => {
    if (event) event.cancelBubble = true;
    const nextPoints = points.filter((_, index) => index !== pointIndex);
    setPoints(nextPoints);

    if (nextPoints.length > 0 || samPromptBox) {
      runInference(nextPoints, samPromptBox || undefined);
      return;
    }

    if (samCandidateMaskId) {
      setMasks(masks.filter((mask) => mask.id !== samCandidateMaskId));
      setSamCandidateMaskId(null);
      setSelectedMaskId(null);
      setSelectedMaskIds([]);
      setInferenceMessage('已移除最后一个提示点和对应候选区域。');
    }
  }, [masks, points, runInference, samCandidateMaskId, samPromptBox, setMasks]);

  const updatePolygonMask = useCallback((mask: Mask, nextPoints: CanvasPoint[], polygonIndex = 0) => {
    if (nextPoints.length < 3) return;
    const nextSegmentation = [...(mask.segmentation || [])];
    nextSegmentation[polygonIndex] = nextPoints.flatMap((point) => [point.x, point.y]);
    const bbox = segmentationBbox(nextSegmentation) || polygonBbox(nextPoints);
    updateMask(mask.id, {
      pathData: segmentationPath(nextSegmentation),
      segmentation: nextSegmentation,
      bbox,
      area: segmentationArea(nextSegmentation),
      saveStatus: mask.annotationId ? 'dirty' : 'draft',
      saved: mask.annotationId ? false : mask.saved,
    });
  }, [updateMask]);

  const updateMaskFromSegmentation = useCallback((
    mask: Mask,
    segmentation: number[][],
    options: { area?: number; hasHoles?: boolean } = {},
  ): Mask => {
    const bbox = segmentationBbox(segmentation);
    const metadata = { ...(mask.metadata || {}) };
    if (options.hasHoles === true) metadata.hasHoles = true;
    if (options.hasHoles === false) delete metadata.hasHoles;
    return {
      ...mask,
      pathData: segmentationPath(segmentation),
      segmentation,
      bbox,
      area: options.area ?? segmentationArea(segmentation),
      metadata,
      saveStatus: mask.annotationId ? 'dirty' : 'draft',
      saved: mask.annotationId ? false : mask.saved,
    };
  }, []);

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      const key = event.key.toLowerCase();
      if ((event.metaKey || event.ctrlKey) && key === 'z') {
        event.preventDefault();
        if (event.shiftKey) redoMasks();
        else undoMasks();
        return;
      }
      if ((event.metaKey || event.ctrlKey) && key === 'y') {
        event.preventDefault();
        redoMasks();
        return;
      }
      if ((event.key === 'Delete' || event.key === 'Backspace') && selectedMask && selectedVertexIndex !== null) {
        const currentPoints = segmentationToPoints(selectedMask.segmentation, selectedPolygonIndex);
        if (currentPoints.length > 3) {
          event.preventDefault();
          const nextPoints = currentPoints.filter((_, index) => index !== selectedVertexIndex);
          updatePolygonMask(selectedMask, nextPoints, selectedPolygonIndex);
          setSelectedVertexIndex(null);
        }
        return;
      }
      if ((event.key === 'Delete' || event.key === 'Backspace') && selectedMask) {
        event.preventDefault();
        const ids = selectedMaskIds.length > 0 ? selectedMaskIds : [selectedMask.id];
        deleteMasksById(ids);
        return;
      }
      if (effectiveTool !== POLYGON_TOOL) return;
      if (event.key === 'Enter' && polygonPoints.length >= 3) {
        event.preventDefault();
        finishPolygon();
      }
      if (event.key === 'Escape') {
        event.preventDefault();
        setPolygonPoints([]);
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [deleteMasksById, effectiveTool, finishPolygon, isPolygonEditTool, polygonPoints, redoMasks, selectedMask, selectedMaskIds, selectedPolygonIndex, selectedVertexIndex, undoMasks, updatePolygonMask]);

  const boxRect = React.useMemo(() => {
    if (!boxStart || !boxCurrent) return null;
    const x = Math.min(boxStart.x, boxCurrent.x);
    const y = Math.min(boxStart.y, boxCurrent.y);
    const width = Math.abs(boxCurrent.x - boxStart.x);
    const height = Math.abs(boxCurrent.y - boxStart.y);
    return { x, y, width, height };
  }, [boxStart, boxCurrent]);

  const manualPreviewPath = React.useMemo(() => {
    if (manualStart && manualCurrent) {
      if (effectiveTool === 'create_rectangle') return polygonPath(rectanglePoints(manualStart, manualCurrent));
      if (effectiveTool === 'create_circle') return polygonPath(circlePoints(manualStart, manualCurrent));
      if (effectiveTool === 'create_line') return polygonPath(lineRegion(manualStart, manualCurrent));
    }
    if (effectiveTool === POLYGON_TOOL && polygonPoints.length > 0) {
      const previewPoints = [...polygonPoints, cursorPos];
      return polygonPath(previewPoints);
    }
    return null;
  }, [cursorPos, effectiveTool, manualCurrent, manualStart, polygonPoints]);

  const handleSeedPointDragEnd = (mask: Mask, pointIndex: number, event: any) => {
    const x = event.target.x();
    const y = event.target.y();
    const nextPoints = [...(mask.points || [])];
    nextPoints[pointIndex] = [x, y];
    updateMask(mask.id, {
      points: nextPoints,
      saveStatus: mask.annotationId ? 'dirty' : 'draft',
      saved: mask.annotationId ? false : mask.saved,
    });
  };

  const handleMaskSelect = (mask: Mask, event: any, polygonIndex = 0) => {
    if (!isPolygonEditTool && !isBooleanTool) return;
    event.cancelBubble = true;
    if (isBooleanTool) {
      setSelectedMaskIds((current) => (
        current.includes(mask.id)
          ? current.filter((id) => id !== mask.id)
          : [...current, mask.id]
      ));
      setSelectedMaskId(mask.id);
      setSelectedPolygonIndex(polygonIndex);
      setSelectedVertexIndex(null);
      return;
    }
    setSelectedMaskId(mask.id);
    setSelectedMaskIds([mask.id]);
    setSelectedPolygonIndex(polygonIndex);
    setSelectedVertexIndex(null);
  };

  const handleVertexDragStart = (mask: Mask, vertexIndex: number, event?: any) => {
    if (event) event.cancelBubble = true;
    setSelectedMaskId(mask.id);
    setSelectedMaskIds([mask.id]);
    setSelectedVertexIndex(vertexIndex);
  };

  const handleVertexDrag = (mask: Mask, vertexIndex: number, event: any) => {
    const imageWidth = frame?.width || image?.naturalWidth || image?.width || stageSize.width;
    const imageHeight = frame?.height || image?.naturalHeight || image?.height || stageSize.height;
    const currentPoints = segmentationToPoints(mask.segmentation, selectedPolygonIndex);
    if (!currentPoints[vertexIndex]) return;
    const nextPoints = currentPoints.map((point, index) => (
      index === vertexIndex
        ? {
          x: clamp(event.target.x(), 0, imageWidth),
          y: clamp(event.target.y(), 0, imageHeight),
        }
        : point
    ));
    setSelectedMaskId(mask.id);
    setSelectedMaskIds([mask.id]);
    setSelectedVertexIndex(vertexIndex);
    updatePolygonMask(mask, nextPoints, selectedPolygonIndex);
  };

  const handleEdgeInsert = (mask: Mask, edgeIndex: number, event: any) => {
    event.cancelBubble = true;
    const currentPoints = segmentationToPoints(mask.segmentation, selectedPolygonIndex);
    const start = currentPoints[edgeIndex];
    const end = currentPoints[(edgeIndex + 1) % currentPoints.length];
    if (!start || !end) return;
    const inserted = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
    const nextPoints = [
      ...currentPoints.slice(0, edgeIndex + 1),
      inserted,
      ...currentPoints.slice(edgeIndex + 1),
    ];
    setSelectedMaskId(mask.id);
    setSelectedVertexIndex(edgeIndex + 1);
    updatePolygonMask(mask, nextPoints, selectedPolygonIndex);
  };

  const handlePathDoubleClick = (mask: Mask, event: any, polygonIndex = 0) => {
    if (effectiveTool !== EDIT_POLYGON_TOOL) return;
    event.cancelBubble = true;
    const point = stagePoint(event);
    const currentPoints = segmentationToPoints(mask.segmentation, polygonIndex);
    if (!point || currentPoints.length < 3) return;
    const edgeIndex = nearestPolygonEdgeIndex(currentPoints, point);
    const nextPoints = [
      ...currentPoints.slice(0, edgeIndex + 1),
      point,
      ...currentPoints.slice(edgeIndex + 1),
    ];
    setSelectedMaskId(mask.id);
    setSelectedMaskIds([mask.id]);
    setSelectedPolygonIndex(polygonIndex);
    setSelectedVertexIndex(edgeIndex + 1);
    updatePolygonMask(mask, nextPoints, polygonIndex);
  };

  const handleBooleanOperation = async () => {
    if (!frame || booleanSelectedMasks.length < 2) return;
    const primary = booleanSelectedMasks[0];
    const primaryGeometry = maskToMultiPolygon(primary);
    if (!primaryGeometry) return;

    const clipGeometries = booleanSelectedMasks
      .slice(1)
      .map(maskToMultiPolygon)
      .filter((geometry): geometry is MultiPolygon => Boolean(geometry));
    if (clipGeometries.length === 0) return;

    const resultGeometry = effectiveTool === 'area_merge'
      ? polygonClipping.union(primaryGeometry, ...clipGeometries)
      : polygonClipping.difference(primaryGeometry, ...clipGeometries);
    const resultSegmentation = multiPolygonToSegmentation(resultGeometry);

    if (resultSegmentation.length === 0) {
      const deleteIds = primary.annotationId ? [primary.annotationId] : [];
      setMasks(masks.filter((mask) => mask.id !== primary.id));
      if (deleteIds.length > 0) await onDeleteMaskAnnotations?.(deleteIds);
      setSelectedMaskId(null);
      setSelectedMaskIds([]);
      setSelectedVertexIndex(null);
      return;
    }

    const nextPrimary = updateMaskFromSegmentation(primary, resultSegmentation, {
      area: multiPolygonArea(resultGeometry),
      hasHoles: multiPolygonHasHoles(resultGeometry),
    });
    const secondaryIds = effectiveTool === 'area_merge'
      ? new Set(booleanSelectedMasks.slice(1).map((mask) => mask.id))
      : new Set<string>();
    const secondaryAnnotationIds = effectiveTool === 'area_merge'
      ? booleanSelectedMasks
        .slice(1)
        .map((mask) => mask.annotationId)
        .filter((annotationId): annotationId is string => Boolean(annotationId))
      : [];

    setMasks(masks
      .filter((mask) => !secondaryIds.has(mask.id))
      .map((mask) => (mask.id === primary.id ? nextPrimary : mask)));
    if (secondaryAnnotationIds.length > 0) await onDeleteMaskAnnotations?.(secondaryAnnotationIds);
    setSelectedMaskId(primary.id);
    setSelectedMaskIds([primary.id]);
    setSelectedVertexIndex(null);
  };

  return (
    <div ref={containerRef} className="w-full h-full relative cursor-crosshair overflow-hidden rounded-sm">
       {isInferencing && (
         <div className="absolute top-4 right-4 z-20 flex items-center gap-2 bg-[#111] border border-white/10 px-3 py-2 rounded-lg shadow-xl">
           <div className="w-3 h-3 border-2 border-cyan-500 border-t-transparent rounded-full animate-spin" />
           <span className="text-xs text-cyan-400 font-mono">AI 推理中...</span>
         </div>
       )}
       {!isInferencing && inferenceMessage && (
         <div className="absolute top-4 right-4 z-20 max-w-xs bg-[#111] border border-white/10 px-3 py-2 rounded-lg shadow-xl text-xs leading-relaxed text-gray-300">
           {inferenceMessage}
         </div>
       )}
       {toolHint && (
         <div className="absolute top-4 left-4 z-20 max-w-sm rounded-lg border border-cyan-400/20 bg-[#0d0d0d]/95 px-3 py-2 shadow-xl pointer-events-none">
           <div className="text-[10px] font-semibold uppercase tracking-widest text-cyan-300">{toolHint.title}</div>
           <div className="mt-1 text-xs leading-relaxed text-gray-300">{toolHint.body}</div>
         </div>
       )}

       <Stage
        width={stageSize.width}
        height={stageSize.height}
        onWheel={handleWheel}
        onMouseMove={handleMouseMove}
        onMouseDown={handleStageMouseDown}
        onMouseUp={handleStageMouseUp}
        scaleX={scale}
        scaleY={scale}
        x={position.x}
        y={position.y}
        draggable={effectiveTool === 'move'}
        onClick={handleStageClick}
       >
         <Layer>
            {/* Background Image Layer */}
            {image && (
              <KonvaImage 
                image={image} 
                x={0} 
                y={0}
                opacity={0.8}
              />
            )}
            
            {/* AI Returned Masks */}
            {frameMasks.map((mask) => {
              const selectedIndex = selectedMaskIds.indexOf(mask.id);
              const isMaskSelected = selectedIndex >= 0;
              const isBooleanPrimary = isBooleanTool && selectedIndex === 0;
              const isBooleanSecondary = isBooleanTool && selectedIndex > 0;
              const strokeColor = isBooleanPrimary
                ? '#facc15'
                : isBooleanSecondary
                  ? '#fb7185'
                  : mask.color;
              const strokeDash = isBooleanSecondary ? [6 / scale, 4 / scale] : undefined;
              const hasHoles = Boolean(mask.metadata?.hasHoles);
              const paths = hasHoles
                ? [{ data: segmentationPath(mask.segmentation), polygonIndex: 0, fillRule: 'evenodd' }]
                : (mask.segmentation && mask.segmentation.length > 0 ? mask.segmentation : [undefined]).map((_, polygonIndex) => ({
                  data: mask.segmentation ? segmentationPolygonPath(mask.segmentation, polygonIndex) : mask.pathData,
                  polygonIndex,
                  fillRule: undefined,
                }));
              return (
                <Group key={mask.id} opacity={isMaskSelected ? 0.65 : 0.5}>
                  {paths.map(({ data, polygonIndex, fillRule }) => (
                    <Path
                      key={`${mask.id}-polygon-${polygonIndex}`}
                      data={data}
                      fill={mask.color}
                      fillRule={fillRule}
                      stroke={strokeColor}
                      strokeWidth={(isMaskSelected ? 2 : 1) / scale}
                      dash={strokeDash}
                      onClick={(event: any) => handleMaskSelect(mask, event, polygonIndex)}
                      onTap={(event: any) => handleMaskSelect(mask, event, polygonIndex)}
                      onDblClick={(event: any) => handlePathDoubleClick(mask, event, polygonIndex)}
                      onDblTap={(event: any) => handlePathDoubleClick(mask, event, polygonIndex)}
                    />
                  ))}
                </Group>
              );
            })}

            {/* Box selection preview */}
            {boxRect && effectiveTool === 'box_select' && (
              <Rect
                x={boxRect.x}
                y={boxRect.y}
                width={boxRect.width}
                height={boxRect.height}
                fill="rgba(6, 182, 212, 0.1)"
                stroke="#06b6d4"
                strokeWidth={2 / scale}
                dash={[4 / scale, 4 / scale]}
              />
            )}

            {/* Manual shape preview */}
            {manualPreviewPath && (
              <Path
                data={manualPreviewPath}
                fill="rgba(34, 211, 238, 0.12)"
                stroke="#22d3ee"
                strokeWidth={2 / scale}
                dash={[5 / scale, 5 / scale]}
              />
            )}

            {polygonPoints.map((point, index) => (
              <Circle
                key={`poly-point-${index}`}
                x={point.x}
                y={point.y}
                radius={(index === 0 && polygonPoints.length >= 3 ? 6 : 4) / scale}
                fill={index === 0 && polygonPoints.length >= 3 ? '#facc15' : '#22d3ee'}
                stroke={index === 0 && polygonPoints.length >= 3 ? '#fef3c7' : '#ffffff'}
                strokeWidth={1 / scale}
                onClick={(event: any) => {
                  if (index !== 0 || polygonPoints.length < 3) return;
                  event.cancelBubble = true;
                  finishPolygon();
                }}
                onTap={(event: any) => {
                  if (index !== 0 || polygonPoints.length < 3) return;
                  event.cancelBubble = true;
                  finishPolygon();
                }}
              />
            ))}

            {/* Imported GT seed points / editable point regions */}
            {frameMasks.flatMap((mask) => (mask.points || []).map(([x, y], index) => (
              <Group key={`${mask.id}-seed-${index}`} x={x} y={y}>
                <Circle
                  radius={5 / scale}
                  fill="#facc15"
                  stroke="#111827"
                  strokeWidth={2 / scale}
                  draggable
                  onDragEnd={(event: any) => handleSeedPointDragEnd(mask, index, event)}
                />
                <Circle radius={1.5 / scale} fill="#111827" />
              </Group>
            )))}

            {/* Polygon edge insertion handles */}
            {isPolygonEditTool && selectedMask && selectedMaskPoints.map((point, index) => {
              const next = selectedMaskPoints[(index + 1) % selectedMaskPoints.length];
              if (!next) return null;
              return (
                <Circle
                  key={`${selectedMask.id}-edge-${selectedPolygonIndex}-${index}`}
                  x={(point.x + next.x) / 2}
                  y={(point.y + next.y) / 2}
                  radius={3.5 / scale}
                  fill="#22d3ee"
                  stroke="#111827"
                  strokeWidth={1.5 / scale}
                  onClick={(event: any) => handleEdgeInsert(selectedMask, index, event)}
                  onTap={(event: any) => handleEdgeInsert(selectedMask, index, event)}
                />
              );
            })}

            {/* Polygon vertex editor */}
            {isPolygonEditTool && selectedMask && selectedMaskPoints.map((point, index) => (
              <Circle
                key={`${selectedMask.id}-vertex-${selectedPolygonIndex}-${index}`}
                x={point.x}
                y={point.y}
                radius={(selectedVertexIndex === index ? 6 : 4.5) / scale}
                fill={selectedVertexIndex === index ? '#22d3ee' : '#ffffff'}
                stroke={selectedMask.color}
                strokeWidth={2 / scale}
                draggable
                onMouseDown={(event: any) => handleVertexDragStart(selectedMask, index, event)}
                onTouchStart={(event: any) => handleVertexDragStart(selectedMask, index, event)}
                onDragStart={(event: any) => handleVertexDragStart(selectedMask, index, event)}
                onClick={(event: any) => {
                  event.cancelBubble = true;
                  setSelectedVertexIndex(index);
                }}
                onTap={(event: any) => {
                  event.cancelBubble = true;
                  setSelectedVertexIndex(index);
                }}
                onDragMove={(event: any) => handleVertexDrag(selectedMask, index, event)}
                onDragEnd={(event: any) => handleVertexDrag(selectedMask, index, event)}
              />
            ))}

            {/* AI Prompts Point Regions */}
            {points.map((p, i) => (
              <Group key={i} x={p.x} y={p.y}>
                 <Circle 
                   radius={6 / scale} 
                   fill={p.type === 'pos' ? '#22c55e' : '#ef4444'}
                   stroke="#ffffff"
                   strokeWidth={2 / scale}
                   shadowColor="black"
                   shadowBlur={4}
                   onClick={(event: any) => removePromptPoint(i, event)}
                   onTap={(event: any) => removePromptPoint(i, event)}
                 />
                 <Circle 
                   radius={1.5 / scale} 
                   fill="#ffffff"
                   onClick={(event: any) => removePromptPoint(i, event)}
                   onTap={(event: any) => removePromptPoint(i, event)}
                 />
              </Group>
             ))}
         </Layer>
       </Stage>
       
       <div className="absolute bottom-4 left-4 flex gap-4 text-[10px] font-mono text-gray-500 pointer-events-none">
          <span>光标: {cursorPos.x.toFixed(2)}, {cursorPos.y.toFixed(2)}</span>
          <span>当前图层: {currentLayerLabel}</span>
          <span>缩放比: {(scale * 100).toFixed(0)}%</span>
          <span>遮罩数: {frameMasks.length}</span>
          <span>已保存: {savedMaskCount}</span>
          <span>未保存: {draftMaskCount}</span>
          <span>待更新: {dirtyMaskCount}</span>
       </div>

       {frameMasks.length > 0 && (
         <div className="absolute bottom-4 right-4 flex gap-2">
           {isBooleanTool && (
             <div className="flex items-center gap-2">
               <span className="text-xs bg-white/5 text-gray-300 border border-white/10 px-2.5 py-1.5 rounded">
                 已选 {booleanSelectedMasks.length}
               </span>
               <button
                 onClick={handleBooleanOperation}
                 disabled={booleanSelectedMasks.length < 2}
                 className="text-xs bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-300 border border-emerald-500/20 px-3 py-1.5 rounded transition-colors disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-emerald-500/10"
               >
                 {effectiveTool === 'area_merge' ? '合并选中' : '从主区域去除'}
               </button>
             </div>
           )}
           {activeClass && (
             <button
               onClick={handleApplyActiveClass}
               className="text-xs bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-300 border border-cyan-500/20 px-3 py-1.5 rounded transition-colors"
             >
               应用分类
             </button>
           )}
           <button
             onClick={handleClearMasks}
             className="text-xs bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 px-3 py-1.5 rounded transition-colors"
           >
             清空遮罩
           </button>
         </div>
       )}
    </div>
  );
}
