import React, { useCallback, useRef, useEffect } from 'react';
import JsBarcode from 'jsbarcode';
import { QRCodeSVG } from 'qrcode.react';
import type { LabelTemplate, LabelElement, ElementType } from '../../../types/labelTemplate';
import { isPrintInputElement } from '../../../types/labelTemplate';
import { PRESET_LABEL_SIZES } from '../../../types/labelTemplate';
import { cn } from '../../ui/utils';
import { resolvePictureUrlForDisplay } from '../../../services/imageUploadService';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '../../ui/select';

/** 真实条形码渲染（JsBarcode），支持水平/竖排 */
function BarcodeBlock({
  data,
  width,
  height,
  showText,
  orientation = 'horizontal',
}: {
  data: string;
  width: number;
  height: number;
  showText?: boolean;
  orientation?: 'horizontal' | 'vertical';
}) {
  const svgRef = useRef<SVGSVGElement>(null);
  const isVertical = orientation === 'vertical';
  const barHeight = Math.max(20, (isVertical ? width : height) - (showText ? 14 : 4));
  useEffect(() => {
    if (svgRef.current && data) {
      try {
        JsBarcode(svgRef.current, data, {
          format: 'CODE128',
          width: 1,
          height: barHeight,
          displayValue: showText !== false,
          margin: 2,
          fontOptions: '',
          fontSize: 10,
        });
      } catch {
        // invalid data, ignore
      }
    }
  }, [data, barHeight, showText]);
  const svg = <svg ref={svgRef} className="w-full h-full min-h-0" style={{ maxHeight: isVertical ? width : height }} />;
  if (isVertical) {
    return (
      <div className="w-full h-full flex items-center justify-center">
        <div
          style={{
            transform: 'rotate(-90deg)',
            transformOrigin: 'center center',
            width: height,
            height: width,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          {svg}
        </div>
      </div>
    );
  }
  return svg;
}

/** 画布网格步长（px），控件吸附到该步长 */
const GRID_SIZE = 8;

/** 将数值对齐到网格 */
function snapToGrid(value: number): number {
  return Math.round(value / GRID_SIZE) * GRID_SIZE;
}

/** 1cm ≈ 37.8px (96 DPI); 1 inch = 96px */
function unitToPx(value: number, unit: 'cm' | 'inch'): number {
  return unit === 'cm' ? value * 37.8 : value * 96;
}

/** px 转单位（用于拖拽调整纸张尺寸） */
function pxToUnit(px: number, unit: 'cm' | 'inch'): number {
  return unit === 'cm' ? px / 37.8 : px / 96;
}

/**
 * 多选项在画布上的文案：有 prefix 时与 App 打印一致（prefix + 答案/占位）；否则在已选字典时显示「字典名称: 内容」。
 */
function formatMultipleOptionsCanvasLine(
  cfg: Record<string, unknown>,
  text: string,
  selected: string[],
): string {
  const prefix = String(cfg.prefix ?? '').trim();
  const dictLabel = String(cfg.multipleOptionName ?? cfg.MultipleOptionName ?? '').trim();
  const answers = selected.filter(Boolean).join(', ');
  const fallback = text || '…';
  if (prefix) {
    return answers ? `${prefix}${answers}` : `${prefix}${fallback}`;
  }
  if (dictLabel) {
    const body = answers || fallback;
    return `${dictLabel}: ${body}`;
  }
  return answers || fallback;
}

/** 根据元素类型与 config 渲染画布上的默认内容 */
function ElementContent({ el, isAppPrintField }: { el: LabelElement; isAppPrintField?: boolean }) {
  const cfg = el.config as Record<string, unknown>;
  const type = el.type as ElementType;

  // Common styles
  const commonStyle: React.CSSProperties = {
    fontSize: (cfg?.fontSize as number) ?? 14,
    fontFamily: (cfg?.fontFamily as string) ?? 'Arial',
    fontWeight: (cfg?.fontWeight as string) ?? 'normal',
    textAlign: (cfg?.textAlign as any) ?? 'left',
    color: (cfg?.color as string) ?? '#000',
  };

  // 文本类
  const inputType = cfg?.inputType as string | undefined;
  if (type === 'TEXT_STATIC') {
    const text = (cfg?.text as string) ?? '文本';
    if (isAppPrintField) {
      if (inputType === 'options') {
        const selected = Array.isArray(cfg?.selectedOptionValues)
          ? (cfg.selectedOptionValues as string[])
          : [];
        const line = formatMultipleOptionsCanvasLine(cfg, text, selected);
        return (
          <div
            className="w-full h-full px-1 flex flex-col justify-center overflow-hidden pointer-events-none text-gray-600 italic text-[11px] leading-tight break-all"
            style={commonStyle}
            title="Filled in mobile app when printing"
          >
            {line}
          </div>
        );
      }
      const display =
        inputType === 'number' ? ((cfg?.text as string) ?? '0') : text;
      return (
        <div
          className="w-full h-full px-1 flex items-center overflow-hidden pointer-events-none text-gray-600 italic text-[11px]"
          style={commonStyle}
          title="Filled in mobile app when printing"
        >
          {display}
        </div>
      );
    }
    if (inputType === 'number') {
      return (
        <input
          type="number"
          readOnly
          value={(cfg?.text as string) ?? '0'}
          className="w-full h-full min-w-0 border border-gray-300 bg-white rounded px-1 pointer-events-none"
          style={{ ...commonStyle, textAlign: 'right' }}
        />
      );
    }
    if (inputType === 'options') {
      // 画布只展示「答案」纯文本，不出现勾选、下拉箭头等控件样式；实际选择在 APP 打印流程中完成
      const selected = Array.isArray(cfg?.selectedOptionValues)
        ? (cfg.selectedOptionValues as string[])
        : [];
      const line = formatMultipleOptionsCanvasLine(cfg, text, selected);
      const muted = selected.length === 0;
      return (
        <div
          className={cn(
            'w-full h-full px-1 overflow-hidden whitespace-pre-wrap break-all leading-tight',
            muted && 'text-gray-400',
          )}
          style={commonStyle}
          title={line}
        >
          {line}
        </div>
      );
    }
    if (inputType === 'text') {
      return (
        <input
          type="text"
          readOnly
          value={text}
          className="w-full h-full min-w-0 border border-gray-300 bg-white rounded px-1 pointer-events-none"
          style={commonStyle}
        />
      );
    }
    return (
      <div className="w-full h-full px-1 overflow-hidden whitespace-pre-wrap break-all leading-tight" style={commonStyle}>
        {text}
      </div>
    );
  }
  if (type === 'TEXT_PRODUCT') {
    const text = (cfg?.text as string) ?? '商品名';
    return (
      <div className="w-full h-full px-1 overflow-hidden whitespace-pre-wrap break-all leading-tight" style={commonStyle}>
        {text}
      </div>
    );
  }
  if (type === 'TEXT_PRICE') {
    const prefix = (cfg?.prefix as string) ?? '￥';
    const text = (cfg?.text as string) ?? '0.00';
    return (
      <div className="w-full h-full px-1 overflow-hidden flex items-center" style={{ ...commonStyle, justifyContent: commonStyle.textAlign === 'center' ? 'center' : commonStyle.textAlign === 'right' ? 'flex-end' : 'flex-start' }}>
        <span>{prefix}</span>
        <span>{text}</span>
      </div>
    );
  }

  // 条码（支持水平/竖排）
  if (type === 'BARCODE') {
    const data = (cfg?.data as string) ?? '123456789';
    const showText = (cfg?.showText as boolean) !== false;
    const orientation = ((cfg?.orientation as string) === 'vertical' ? 'vertical' : 'horizontal') as 'horizontal' | 'vertical';
    return (
      <div className="flex flex-col items-center justify-center w-full h-full overflow-hidden p-0.5">
        <div className="flex-1 w-full min-h-0 flex items-center justify-center">
          <BarcodeBlock
            data={data}
            width={el.width}
            height={el.height}
            showText={showText}
            orientation={orientation}
          />
        </div>
      </div>
    );
  }

  // 二维码
  if (type === 'QRCODE') {
    const data = (cfg?.data as string) ?? 'https://example.com';
    const size = Math.min(el.width, el.height) - 4;
    return (
      <div className="w-full h-full flex items-center justify-center p-0.5">
        <QRCodeSVG value={data} size={Math.max(20, size)} level="M" includeMargin={false} />
      </div>
    );
  }

  // 图片/Logo
  if (type === 'IMAGE') {
    const src = cfg?.src as string | undefined;
    if (src) {
      return (
        <img
          src={resolvePictureUrlForDisplay(src)}
          alt=""
          className="w-full h-full object-contain"
        />
      );
    }
    return (
      <div className="w-full h-full flex flex-col items-center justify-center bg-gray-100 text-gray-500 text-[10px] border border-dashed border-gray-300">
        <span className="font-medium">Logo</span>
      </div>
    );
  }

  // 日期/时间
  if (type === 'DATE') {
    const format =
      (typeof cfg?.format === 'string' && cfg.format.trim()
        ? cfg.format
        : typeof cfg?.Format === 'string' && cfg.Format.trim()
          ? cfg.Format
          : 'YYYY-MM-DD') ?? 'YYYY-MM-DD';
    const example = format.replace('YYYY', '2025').replace('MM', '02').replace('DD', '01');
    const it = String(cfg?.inputType ?? cfg?.InputType ?? '').toLowerCase();
    const isInput = it === 'datetime' || it === 'date';
    if (isInput) {
      if (isAppPrintField) {
        return (
          <div
            className="w-full h-full px-1 flex items-center justify-center overflow-hidden pointer-events-none text-[10px] text-center whitespace-nowrap"
            style={commonStyle}
            title={`Format: ${format}`}
          >
            {format}
          </div>
        );
      }
      return (
        <input
          type="date"
          readOnly
          value="2025-02-01"
          className="w-full h-full min-w-0 border border-gray-300 bg-white rounded px-1 pointer-events-none text-[10px]"
          style={commonStyle}
        />
      );
    }
    return <div className="w-full h-full px-1 overflow-hidden whitespace-nowrap" style={commonStyle}>{example}</div>;
  }

  // (Simplified other types similarly for brevity, ensuring style prop is passed)
  if (type === 'TIME') {
    const format =
      (typeof cfg?.format === 'string' && cfg.format.trim()
        ? cfg.format
        : typeof cfg?.Format === 'string' && cfg.Format.trim()
          ? cfg.Format
          : 'HH:mm') ?? 'HH:mm';
    const example = format.replace('HH', '12').replace('mm', '30');
    return <div className="w-full h-full px-1 overflow-hidden whitespace-nowrap" style={commonStyle}>{example}</div>;
  }

  if (type === 'DURATION') {
    return <div className="w-full h-full px-1 overflow-hidden whitespace-nowrap" style={commonStyle}>保质期 2025-02-04</div>;
  }

  if (type === 'WEIGHT') {
    const rawV = cfg?.value ?? cfg?.Value;
    const numVal =
      rawV == null || rawV === ''
        ? 500
        : typeof rawV === 'number'
          ? rawV
          : Number(rawV);
    const weightNum = Number.isFinite(numVal) ? numVal : 500;
    const weightUnit =
      (typeof cfg?.unit === 'string' && cfg.unit.trim()
        ? cfg.unit
        : typeof cfg?.Unit === 'string' && cfg.Unit.trim()
          ? cfg.Unit
          : 'g') ?? 'g';
    return (
      <div className="w-full h-full px-1 overflow-hidden whitespace-nowrap" style={commonStyle}>
        {weightNum}
        {weightUnit}
      </div>
    );
  }

  if (type === 'WEIGHT_PRICE') {
    const unitPrice = (cfg?.unitPrice as number) ?? 10;
    const weight = (cfg?.weight as number) ?? 0.5;
    const currency = (cfg?.currency as string) ?? '￥';
    return <div className="w-full h-full px-1 overflow-hidden whitespace-nowrap" style={commonStyle}>{currency}{(unitPrice * weight).toFixed(2)}</div>;
  }

  // 营养成分表
  if (type === 'NUTRITION') {
    const calories = (cfg?.calories as number) ?? 120;
    return (
      <div className="text-[8px] p-0.5 w-full h-full overflow-hidden flex flex-col">
        <div className="font-semibold border-b border-black">Nutrition Facts</div>
        <div>Calories {calories}</div>
      </div>
    );
  }

  // 空白占位
  if (type === 'BLANK') {
    return <div className="w-full h-full border border-dashed border-gray-200" />;
  }

  return (
    <div className="text-gray-500 text-[10px] px-1 truncate w-full flex items-center justify-center">
      {el.type.replace(/_/g, ' ')}
    </div>
  );
}

interface LabelCanvasProps {
  template: LabelTemplate;
  selectedId: string | null;
  onSelect: (id: string | null) => void;
  onUpdateElement: (id: string, patch: Partial<LabelElement>) => void;
  onDeleteElement: (id: string) => void;
  onTemplateChange?: (patch: Partial<LabelTemplate>) => void;
  scale?: number;
  onZoomIn?: () => void;
  onZoomOut?: () => void;
  onPreview?: () => void;
}

export function LabelCanvas({
  template,
  selectedId,
  onSelect,
  onUpdateElement,
  onDeleteElement,
  onTemplateChange,
  scale = 1,
  onZoomIn,
  onZoomOut,
  onPreview,
}: LabelCanvasProps) {
  const scrollContainerRef = useRef<HTMLDivElement>(null);
  const canvasRef = useRef<HTMLDivElement>(null);
  const dragRef = useRef<{ id: string; startX: number; startY: number; elX: number; elY: number } | null>(null);
  const resizeRef = useRef<{ id: string; corner: string; startX: number; startY: number; w: number; h: number; elX: number; elY: number } | null>(null);
  const paperResizeRef = useRef<{ edge: 'bottom' | 'right'; startX: number; startY: number; startW: number; startH: number } | null>(null);
  const lastUpdateRef = useRef<{ id: string; x?: number; y?: number; width?: number; height?: number } | null>(null);

  const nextFrameRef = useRef<number | null>(null);
  const [isSpacePressed, setIsSpacePressed] = React.useState(false);
  const [isPanning, setIsPanning] = React.useState(false);
  const panStartRef = useRef<{ x: number; y: number; scrollLeft: number; scrollTop: number } | null>(null);
  const [panOffset, setPanOffset] = React.useState({ x: 0, y: 0 });
  const panOffsetStartRef = useRef<{ x: number; y: number; startX: number; startY: number } | null>(null);

  const baseW = unitToPx(template.width, template.unit);
  const baseH = unitToPx(template.height, template.unit);
  const widthPx = baseW * scale;
  const heightPx = baseH * scale;
  const showGrid = template.showGrid !== false;

  const handlePointerDown = useCallback(
    (e: React.PointerEvent, id: string) => {
      // 如果按住了空格，直接返回，交给外层 panning 处理
      // 允许中键 (button 1) 拖动
      if (isSpacePressed || e.button === 1) return;

      e.stopPropagation();
      onSelect(id);

      // Focus canvas for keyboard events
      canvasRef.current?.focus();

      const el = template.elements.find((x) => x.id === id);
      if (!el) return;

      const domEl = document.getElementById(`element-${id}`);
      if (domEl) {
        domEl.classList.add('z-50', 'opacity-90', 'shadow-xl', 'ring-2', 'ring-blue-400', 'ring-offset-2');
        domEl.style.cursor = 'grabbing';
      }

      dragRef.current = { id, startX: e.clientX, startY: e.clientY, elX: el.x, elY: el.y };
      lastUpdateRef.current = { id, x: el.x, y: el.y }; // 初始化
      (e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);
    },
    [template.elements, onSelect, isSpacePressed]
  );

  const requestUpdate = useCallback((updateFn: () => void) => {
    if (nextFrameRef.current !== null) {
      cancelAnimationFrame(nextFrameRef.current);
    }
    nextFrameRef.current = requestAnimationFrame(() => {
      updateFn();
      nextFrameRef.current = null;
    });
  }, []);

  const handlePointerMove = useCallback(
    (e: React.PointerEvent) => {
      // 画布平移：优先处理（translate 方式，不依赖滚动）
      if (isPanning && panOffsetStartRef.current) {
        const dx = e.clientX - panOffsetStartRef.current.startX;
        const dy = e.clientY - panOffsetStartRef.current.startY;
        setPanOffset({
          x: panOffsetStartRef.current.x + dx,
          y: panOffsetStartRef.current.y + dy,
        });
        return;
      }
      if (isPanning && panStartRef.current && scrollContainerRef.current) {
        const dx = e.clientX - panStartRef.current.x;
        const dy = e.clientY - panStartRef.current.y;
        scrollContainerRef.current.scrollLeft = panStartRef.current.scrollLeft - dx;
        scrollContainerRef.current.scrollTop = panStartRef.current.scrollTop - dy;
        return;
      }
      // Drag Element
      if (dragRef.current) {
        // e.persist(); // React 17+ doesn't strictly need this for properties access in rAF closure if we read them now
        const { id, startX, startY, elX, elY } = dragRef.current;
        const clientX = e.clientX;
        const clientY = e.clientY;

        requestUpdate(() => {
          const dx = (clientX - startX) / scale;
          const dy = (clientY - startY) / scale;
          const rawX = Math.max(0, elX + dx);
          const rawY = Math.max(0, elY + dy);
          const snappedX = snapToGrid(rawX);
          const snappedY = snapToGrid(rawY);

          // 直接操作 DOM 避免频繁重渲染
          const domEl = document.getElementById(`element-${id}`);
          if (domEl) {
            domEl.style.left = `${snappedX}px`;
            domEl.style.top = `${snappedY}px`;
          }
          lastUpdateRef.current = { id, x: snappedX, y: snappedY };
          // 注意：这里不再更新 dragRef.current，因为我们在闭包里计算 dx, dy 也是 Ok 的。
          // 只要我们始终基于 startX/elX 计算，就不会有精度积累误差。
        });
      }

      // Resize Element
      if (resizeRef.current) {
        const { id, corner, startX, startY, w, h, elX, elY } = resizeRef.current;
        const clientX = e.clientX;
        const clientY = e.clientY;

        requestUpdate(() => {
          const dx = (clientX - startX) / scale;
          const dy = (clientY - startY) / scale;
          let nw = w;
          let nh = h;
          let nx = elX;
          let ny = elY;
          if (corner.includes('e')) nw = Math.max(20, w + dx);
          if (corner.includes('w')) {
            nw = Math.max(20, w - dx);
            nx = elX + dx;
          }
          if (corner.includes('s')) nh = Math.max(12, h + dy);
          if (corner.includes('n')) {
            nh = Math.max(12, h - dy);
            ny = elY + dy;
          }
          const snappedW = snapToGrid(nw);
          const snappedH = snapToGrid(nh);
          const snappedX = snapToGrid(nx);
          const snappedY = snapToGrid(ny);

          // 直接操作 DOM
          const domEl = document.getElementById(`element-${id}`);
          if (domEl) {
            domEl.style.width = `${snappedW}px`;
            domEl.style.height = `${snappedH}px`;
            domEl.style.left = `${snappedX}px`;
            domEl.style.top = `${snappedY}px`;
          }

          lastUpdateRef.current = { id, width: snappedW, height: snappedH, x: snappedX, y: snappedY };
        });
      }

      // Resize Paper
      if (paperResizeRef.current && onTemplateChange) {
        const { edge, startX, startY, startW, startH } = paperResizeRef.current;
        const clientX = e.clientX;
        const clientY = e.clientY;

        requestUpdate(() => {
          const deltaPxX = (clientX - startX) / scale;
          const deltaPxY = (clientY - startY) / scale;
          const deltaUnitX = pxToUnit(deltaPxX, template.unit);
          const deltaUnitY = pxToUnit(deltaPxY, template.unit);
          if (edge === 'bottom') {
            const newH = Math.max(1, startH + deltaUnitY);
            onTemplateChange({ height: newH });
          } else {
            const newW = Math.max(1, startW + deltaUnitX);
            onTemplateChange({ width: newW });
          }
        });
      }
    },
    [isPanning, onTemplateChange, scale, template.unit, requestUpdate]
  );

  const handlePointerUp = useCallback(() => {
    // 结束画布平移
    if (isPanning) {
      setIsPanning(false);
      panStartRef.current = null;
      panOffsetStartRef.current = null;
    }
    // Cancel pending animation frame
    if (nextFrameRef.current !== null) {
      cancelAnimationFrame(nextFrameRef.current);
      nextFrameRef.current = null;
    }

    const activeId = dragRef.current?.id || resizeRef.current?.id;
    if (activeId) {
      const domEl = document.getElementById(`element-${activeId}`);
      if (domEl) {
        domEl.classList.remove('z-50', 'opacity-90', 'shadow-xl', 'ring-2', 'ring-blue-400', 'ring-offset-2');
        domEl.style.cursor = '';
      }
    }

    if (lastUpdateRef.current) {
      const { id, ...patch } = lastUpdateRef.current;
      onUpdateElement(id, patch);
      lastUpdateRef.current = null;
    }
    dragRef.current = null;
    resizeRef.current = null;
    paperResizeRef.current = null;
  }, [onUpdateElement]);

  useEffect(() => {
    const onKeyDown = (e: KeyboardEvent) => {
      if (e.code === 'Space' && !e.repeat) {
        setIsSpacePressed(true);
      }
    };
    const onKeyUp = (e: KeyboardEvent) => {
      if (e.code === 'Space') {
        setIsSpacePressed(false);
        setIsPanning(false);
        panStartRef.current = null;
        panOffsetStartRef.current = null;
      }
    };
    window.addEventListener('keydown', onKeyDown);
    window.addEventListener('keyup', onKeyUp);
    return () => {
      window.removeEventListener('keydown', onKeyDown);
      window.removeEventListener('keyup', onKeyUp);
    };
  }, []);

  // 画布初始居中：挂载或尺寸/缩放变化后让内容居中
  useEffect(() => {
    const el = scrollContainerRef.current;
    if (!el) return;
    const center = () => {
      el.scrollLeft = Math.max(0, (el.scrollWidth - el.clientWidth) / 2);
      el.scrollTop = Math.max(0, (el.scrollHeight - el.clientHeight) / 2);
    };
    const raf = requestAnimationFrame(center);
    const t = setTimeout(center, 100);
    return () => {
      cancelAnimationFrame(raf);
      clearTimeout(t);
    };
  }, [scale, baseW, baseH]);

  // Keyboard navigation for elements
  const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
    if (!selectedId) return;
    if (e.key === 'Delete' || e.key === 'Backspace') {
      // ... existing delete logic
      e.preventDefault();
      const idx = template.elements.findIndex((x) => x.id === selectedId);
      if (idx >= 0) {
        const next = template.elements.filter((x) => x.id !== selectedId);
        onDeleteElement(selectedId);
        onSelect(next[idx]?.id ?? next[idx - 1]?.id ?? null);
      }
      return;
    }

    const el = template.elements.find(x => x.id === selectedId);
    if (!el) return;

    // allow typing in inputs without triggering move? 
    // Actually our elements are not inputs (unless we implement inline edit).
    // But preventDefault is good.

    const step = e.shiftKey ? 1 : GRID_SIZE;
    let dx = 0;
    let dy = 0;

    switch (e.key) {
      case 'ArrowLeft': dx = -step; break;
      case 'ArrowRight': dx = step; break;
      case 'ArrowUp': dy = -step; break;
      case 'ArrowDown': dy = -step; break; // Wait, ArrowDown should be +step (y increases downwards)
      default: return;
    }

    // Fix: ArrowDown +step
    if (e.key === 'ArrowDown') dy = step;

    e.preventDefault();
    onUpdateElement(el.id, {
      x: Math.max(0, el.x + dx),
      y: Math.max(0, el.y + dy)
    });

  }, [selectedId, template.elements, onUpdateElement, onDeleteElement, onSelect]);

  const canvasClick = () => onSelect(null);

  // 容器的 Pan 处理
  // 容器的 Pan 处理
  const handleContainerPointerDown = (e: React.PointerEvent) => {
    if (isSpacePressed || e.button === 1) {
      e.preventDefault();
      setIsPanning(true);
      panStartRef.current = {
        x: e.clientX,
        y: e.clientY,
        scrollLeft: scrollContainerRef.current?.scrollLeft || 0,
        scrollTop: scrollContainerRef.current?.scrollTop || 0
      };
      (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
    }
  };

  const handleContainerPointerMove = (e: React.PointerEvent) => {
    if (isPanning && panStartRef.current && scrollContainerRef.current) {
      const dx = e.clientX - panStartRef.current.x;
      const dy = e.clientY - panStartRef.current.y;
      scrollContainerRef.current.scrollLeft = panStartRef.current.scrollLeft - dx;
      scrollContainerRef.current.scrollTop = panStartRef.current.scrollTop - dy;
    }
  };

  const handleContainerPointerUp = (e: React.PointerEvent) => {
    if (isPanning) {
      setIsPanning(false);
      panStartRef.current = null;
    }
  };

  return (
    <div className="flex-1 flex flex-col min-h-0 overflow-hidden bg-gray-100">
      {/* Label Preview 标题 + 网格/预览/缩放 */}
      <div className="shrink-0 px-4 py-2 border-b border-gray-200 bg-white flex items-center justify-between gap-2 flex-wrap z-10">
        <span className="text-sm font-medium text-gray-700">Label Preview</span>
        <div className="flex items-center gap-2 flex-wrap">
          {onPreview && (
            <button
              type="button"
              onClick={onPreview}
              className="h-8 px-3 rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 text-xs font-medium shadow-sm transition-all active:scale-95"
            >
              预览
            </button>
          )}
          {onTemplateChange && (
            <>
              <Select
                value={(() => {
                  const i = PRESET_LABEL_SIZES.findIndex(
                    (p) => p.width === template.width && p.height === template.height && p.unit === template.unit
                  );
                  return i >= 0 ? String(i) : 'custom';
                })()}
                onValueChange={(v: string) => {
                  if (v === 'custom') return;
                  const p = PRESET_LABEL_SIZES[Number(v)];
                  if (p) onTemplateChange({ width: p.width, height: p.height, unit: p.unit });
                }}
              >
                <SelectTrigger className="h-8 w-[130px] text-xs">
                  <SelectValue placeholder="画布大小" />
                </SelectTrigger>
                <SelectContent>
                  {PRESET_LABEL_SIZES.map((p, i) => (
                    <SelectItem key={i} value={String(i)} className="text-xs">
                      {p.name}
                    </SelectItem>
                  ))}
                  <SelectItem value="custom" className="text-xs text-gray-500">
                    自定义
                  </SelectItem>
                </SelectContent>
              </Select>
              <button
                type="button"
                onClick={() => onTemplateChange({ showGrid: !showGrid })}
                className={cn(
                  'h-8 px-3 rounded border text-xs font-medium shadow-sm transition-colors',
                  showGrid ? 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50' : 'border-gray-300 bg-gray-100 text-gray-500'
                )}
              >
                {showGrid ? '隐藏网格' : '显示网格'}
              </button>
            </>
          )}
          <div className="flex items-center gap-1 bg-white rounded border border-gray-300 p-0.5 shadow-sm h-8">
            <button
              type="button"
              onClick={onZoomOut}
              disabled={!onZoomOut}
              className="h-6 w-6 rounded hover:bg-gray-100 text-gray-600 disabled:opacity-50 disabled:pointer-events-none flex items-center justify-center text-sm font-medium active:scale-90 transition-transform"
              title="缩小"
            >
              −
            </button>
            <span className="min-w-[3rem] text-center text-xs text-gray-600 font-medium">
              {Math.round(scale * 100)}%
            </span>
            <button
              type="button"
              onClick={onZoomIn}
              disabled={!onZoomIn}
              className="h-6 w-6 rounded hover:bg-gray-100 text-gray-600 disabled:opacity-50 disabled:pointer-events-none flex items-center justify-center text-sm font-medium active:scale-90 transition-transform"
              title="放大"
            >
              +
            </button>
          </div>
        </div>
      </div>

      {/* Canvas Container */}
      <div
        ref={scrollContainerRef}
        className={cn(
          "flex-1 overflow-auto bg-gray-100 relative",
          isSpacePressed ? "cursor-grab active:cursor-grabbing" : ""
        )}
        onPointerDown={handleContainerPointerDown}
        onPointerMove={handleContainerPointerMove}
        onPointerUp={handleContainerPointerUp}
        onPointerLeave={handleContainerPointerUp}
      >
        <div
          style={{
            minWidth: '100%',
            minHeight: '100%',
            width: 'fit-content',
            height: 'fit-content',
            display: 'flex',
            padding: 50,
            boxSizing: 'border-box',
            transform: `translate(${panOffset.x}px, ${panOffset.y}px)`,
          }}
        >
          <div
            ref={canvasRef}
            tabIndex={0}
            className={cn(
              'relative bg-white shadow-lg border border-dashed border-gray-300 origin-top-left outline-none m-auto',
              isPanning ? 'cursor-grabbing' : 'cursor-grab'
            )}
            style={{
              width: baseW,
              height: baseH,
              transform: `scale(${scale})`,
              backgroundImage: showGrid
                ? `linear-gradient(to right, rgba(0,0,0,0.06) 1px, transparent 1px),
                   linear-gradient(to bottom, rgba(0,0,0,0.06) 1px, transparent 1px)`
                : undefined,
              backgroundSize: showGrid ? `${GRID_SIZE}px ${GRID_SIZE}px` : undefined,
              // 如果按住空格，禁用 canvas 内部的 pointer-events 以便拖动容器
              pointerEvents: isSpacePressed ? 'none' : 'auto'
            }}
            onClick={(e) => {
              // 点击画布空白处取消选中
              const target = e.target as HTMLElement;
              const isOnElement = target.closest('[id^="element-"]');
              const isOnPaperResize = target.closest('[title*="拖拽拉高"]') || target.closest('[title*="拖拽拉宽"]');
              if (!isOnElement && !isOnPaperResize) {
                onSelect(null);
              }
            }}
            onPointerDown={(e) => {
              // 空白处或标尺等非控件区域按下即开始平移（放宽判定：在画布内且未点到元素/纸张拖拽条）
              const target = e.target as HTMLElement;
              const isOnElement = target.closest('[id^="element-"]');
              const isOnPaperResize = target.closest('[title*="拖拽拉高"]') || target.closest('[title*="拖拽拉宽"]');
              const isOnCanvasArea = canvasRef.current?.contains(target);
              if (isOnCanvasArea && !isOnElement && !isOnPaperResize && !dragRef.current && !resizeRef.current) {
                // 如果按住空格或中键，开始平移
                if (isSpacePressed || e.button === 1) {
                  e.preventDefault();
                  e.stopPropagation();
                  setIsPanning(true);
                  panOffsetStartRef.current = {
                    x: panOffset.x,
                    y: panOffset.y,
                    startX: e.clientX,
                    startY: e.clientY,
                  };
                  panStartRef.current = {
                    x: e.clientX,
                    y: e.clientY,
                    scrollLeft: scrollContainerRef.current?.scrollLeft ?? 0,
                    scrollTop: scrollContainerRef.current?.scrollTop ?? 0,
                  };
                  (e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);
                }
              }
            }}
            onPointerMove={handlePointerMove}
            onPointerUp={handlePointerUp}
            onKeyDown={handleKeyDown}
          >
            {template.showRuler && (
              <div className="absolute top-0 left-0 right-0 h-5 border-b border-gray-300 bg-gray-50 text-[10px] text-gray-500 flex items-center px-1 pointer-events-none select-none">
                {template.unit} {template.width} × {template.height}
              </div>
            )}
            {/* 纸张尺寸拖拽：底部拉高 */}
            {onTemplateChange && (
              <div
                className="absolute bottom-0 left-0 right-0 h-3 cursor-ns-resize flex items-center justify-center bg-gray-200/80 hover:bg-blue-400/30 border-t border-gray-300 text-[10px] text-gray-500 transition-colors"
                title="拖拽拉高纸张"
                onPointerDown={(e) => {
                  e.stopPropagation();
                  paperResizeRef.current = {
                    edge: 'bottom',
                    startX: e.clientX,
                    startY: e.clientY,
                    startW: template.width,
                    startH: template.height,
                  };
                  (e.target as HTMLElement).setPointerCapture?.(e.pointerId);
                }}
              >
                ⋮
              </div>
            )}
            {/* 纸张尺寸拖拽：右侧拉宽 */}
            {onTemplateChange && (
              <div
                className="absolute top-0 right-0 bottom-0 w-3 cursor-ew-resize flex items-center justify-center bg-gray-200/80 hover:bg-blue-400/30 border-l border-gray-300 text-[10px] text-gray-500 transition-colors"
                title="拖拽拉宽纸张"
                onPointerDown={(e) => {
                  e.stopPropagation();
                  paperResizeRef.current = {
                    edge: 'right',
                    startX: e.clientX,
                    startY: e.clientY,
                    startW: template.width,
                    startH: template.height,
                  };
                  (e.target as HTMLElement).setPointerCapture?.(e.pointerId);
                }}
              >
                ⋮
              </div>
            )}
            {template.elements.map((el) => {
              const isPrintField = isPrintInputElement(el);
              return (
              <div
                key={el.id}
                id={`element-${el.id}`}
                className={cn(
                  'absolute box-border cursor-move overflow-hidden transition-shadow',
                  el.border === 'line' && 'border border-gray-400',
                  el.border === 'dotted' && 'border border-dotted border-gray-400',
                  selectedId === el.id && 'ring-2 ring-blue-500 ring-offset-1 z-10'
                )}
                style={{
                  left: el.x,
                  top: el.y,
                  width: el.width,
                  height: el.height,
                }}
                onClick={(e) => {
                  e.stopPropagation();
                  onSelect(el.id);
                }}
                onPointerDown={(e) => handlePointerDown(e, el.id)}
              >
                <div
                  className={cn(
                    'w-full h-full min-h-0 relative',
                    isPrintField &&
                      'rounded-sm border-2 border-dashed border-amber-500/85 bg-amber-50/35',
                  )}
                >
                  <ElementContent el={el} isAppPrintField={isPrintField} />
                </div>
                {selectedId === el.id && (
                  <>
                    {/* 4 Corners */}
                    {(['nw', 'ne', 'sw', 'se'] as const).map((corner) => (
                      <div
                        key={corner}
                        className="absolute w-4 h-4 bg-white border-2 border-blue-500 rounded-full z-20 shadow-md hover:scale-110 transition-transform"
                        style={{
                          cursor: 'nwse-resize',
                          top: corner.startsWith('n') ? -6 : undefined,
                          bottom: corner.startsWith('s') ? -6 : undefined,
                          left: corner.endsWith('w') ? -6 : undefined,
                          right: corner.endsWith('e') ? -6 : undefined,
                        }}
                        onPointerDown={(e) => {
                          e.stopPropagation();
                          const el0 = template.elements.find((x) => x.id === el.id)!;
                          resizeRef.current = {
                            id: el.id,
                            corner,
                            startX: e.clientX,
                            startY: e.clientY,
                            w: el0.width,
                            h: el0.height,
                            elX: el0.x,
                            elY: el0.y,
                          };
                          (e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);
                        }}
                      />
                    ))}
                    {/* 4 Edges */}
                    {(['n', 's', 'w', 'e'] as const).map((edge) => (
                      <div
                        key={edge}
                        className="absolute bg-blue-500/50 border border-white/50 rounded-sm z-10 shadow-sm hover:bg-blue-600"
                        style={{
                          cursor: edge === 'n' || edge === 's' ? 'ns-resize' : 'ew-resize',
                          width: edge === 'n' || edge === 's' ? '20px' : '6px',
                          height: edge === 'n' || edge === 's' ? '6px' : '20px',
                          top: edge === 'n' ? -3 : edge === 's' ? undefined : '50%',
                          bottom: edge === 's' ? -3 : undefined,
                          left: edge === 'w' ? -3 : edge === 'e' ? undefined : '50%',
                          right: edge === 'e' ? -3 : undefined,
                          transform: edge === 'n' || edge === 's' ? 'translateX(-50%)' : 'translateY(-50%)',
                        }}
                        onPointerDown={(e) => {
                          e.stopPropagation();
                          const el0 = template.elements.find((x) => x.id === el.id)!;

                          const domEl = document.getElementById(`element-${el.id}`);
                          if (domEl) {
                            domEl.classList.add('z-50', 'opacity-90');
                          }

                          resizeRef.current = {
                            id: el.id,
                            corner: edge,
                            startX: e.clientX,
                            startY: e.clientY,
                            w: el0.width,
                            h: el0.height,
                            elX: el0.x,
                            elY: el0.y,
                          };
                          (e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId);
                        }}
                      />
                    ))}
                  </>
                )}
              </div>
            );
            })}
          </div>
        </div>
      </div>
    </div>
  );
}

/** Preview only: no grid, no rulers, no drag; scale to fit. */
export function LabelPreviewOnly({
  template,
  maxWidth = 480,
}: {
  template: LabelTemplate;
  maxWidth?: number;
}) {
  const baseW = unitToPx(template.width, template.unit);
  const baseH = unitToPx(template.height, template.unit);
  const scaleToFit = maxWidth ? Math.min(maxWidth / baseW, maxWidth / baseH, 2) : 1;
  const displayW = baseW * scaleToFit;
  const displayH = baseH * scaleToFit;
  // 与编辑区一致：内层 baseW×baseH，transformOrigin 0 0 缩放，保证位置/样式一致
  return (
    <div className="flex items-center justify-center p-4 bg-gray-100 rounded">
      <div style={{ width: displayW, height: displayH }} className="relative bg-white shadow-lg overflow-hidden">
        <div
          className="origin-top-left"
          style={{
            position: 'absolute',
            left: 0,
            top: 0,
            width: baseW,
            height: baseH,
            transform: `scale(${scaleToFit})`,
            transformOrigin: '0 0',
          }}
        >
          {template.elements.map((el) => {
            const isPrintField = isPrintInputElement(el);
            return (
            <div
              key={el.id}
              className="absolute box-border overflow-hidden pointer-events-none flex items-center justify-center text-xs"
              style={{
                left: el.x,
                top: el.y,
                width: el.width,
                height: el.height,
                border: el.border === 'line' ? '1px solid #999' : el.border === 'dotted' ? '1px dotted #999' : undefined,
              }}
            >
              <div
                className={cn(
                  'w-full h-full min-h-0 relative',
                  isPrintField &&
                    'rounded-sm border-2 border-dashed border-amber-500/85 bg-amber-50/35',
                )}
              >
                <ElementContent el={el} isAppPrintField={isPrintField} />
              </div>
            </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}
