import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Button } from '../../ui/button';
import { ArrowLeft, Save, Download } from 'lucide-react';
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from '../../ui/dialog';
import { Input } from '../../ui/input';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '../../ui/select';
import type { ElementLibraryCategory, LabelTemplate, LabelElement, PrintOrientation } from '../../../types/labelTemplate';
import {
  buildLabelTemplateScopePayload,
  allocateElementName,
  canonicalElementType,
  composeElementTypeForPersist,
  composeLibraryCategoryForPersist,
  createDefaultTemplate,
  createDefaultElement,
  applyPaletteLabelDefaultText,
  labelElementsToApiPayload,
  normalizePrintOrientation,
  PRESET_LABEL_SIZES,
  resolvedLibraryCategoryForPersist,
  resolvedTypeAddForPersist,
  resolvedValueSourceTypeForSave,
  stripLabelConfigPrefixes,
  valueSourceTypeForLibraryCategory,
} from '../../../types/labelTemplate';
import { ElementsPanel } from './ElementsPanel';
import { LabelCanvas, LabelPreviewOnly, clampLabelElementBox, mergeLabelElementLivePatch, mergeTemplateElementsLivePatch, type LabelElementLivePatch } from './LabelCanvas';
import type { PreviewRulerDisplayUnit } from '@/utils/previewRulerUnits';
import { PropertiesPanel } from './PropertiesPanel';
import { createLabelTemplate, getLabelTemplate, getLabelTemplates, updateLabelTemplate } from '../../../services/labelTemplateService';
import { getLocations } from '../../../services/locationService';
import { getGroups } from '../../../services/groupService';
import { getPartners } from '../../../services/partnerService';
import { skipCountForPage } from '../../../lib/paginationQuery';
import {
  hydrateLabelTemplateScopeFromDto,
  locationsScopedForTemplateScope,
  regionOptionsForPartners,
} from '../../../lib/categoryScopeForm';
import { CategoryScopeFields } from '../../shared/category-scope-fields';
import { useCategoryScopeAuth } from '../../../hooks/useCategoryScopeAuth';
import {
  TemplateFieldGroup,
  templateFieldInputClass,
  templateFieldSelectTriggerClass,
  templateUnitShortLabel,
} from './template-editor-field-group';
import type { LocationDto } from '../../../types/location';
import type { GroupListItem } from '../../../types/group';
import type { PartnerListItem } from '../../../types/partner';
import { toast } from 'sonner';

const MIN_SCALE = 0.5;
const MAX_SCALE = 2;
const SCALE_STEP = 0.25;
const DEFAULT_SCALE = 1.0;

function buildCopiedTemplateId(sourceId: string): string {
  const seed = Math.random().toString(36).slice(2, 8);
  return `tpl_${seed}_${Date.now().toString(36)}`;
}

function cloneStarterTemplate(source: LabelTemplate): LabelTemplate {
  return {
    ...source,
    id: buildCopiedTemplateId(source.id),
    name: `${(source.name || "Unnamed template").trim()} Copy`,
    elements: source.elements.map((el) => ({
      ...el,
      config: { ...(el.config ?? {}) },
    })),
  };
}

interface LabelTemplateEditorProps {
  /** null = 新建，string = 编辑该 id */
  templateId: string | null;
  initialTemplate: LabelTemplate | null;
  onClose: () => void;
  onSaved: () => void;
}

export function LabelTemplateEditor({
  templateId,
  initialTemplate,
  onClose,
  onSaved,
}: LabelTemplateEditorProps) {
  const scopeAuth = useCategoryScopeAuth();
  const [template, setTemplate] = useState<LabelTemplate>(() => {
    if (initialTemplate) return { ...initialTemplate };
    const next = createDefaultTemplate(templateId ?? undefined);
    return {
      ...next,
      id: buildCopiedTemplateId(next.id || "template"),
    };
  });
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const [liveElementPatch, setLiveElementPatch] = useState<LabelElementLivePatch | null>(null);
  const liveElementPatchRef = useRef<LabelElementLivePatch | null>(null);
  liveElementPatchRef.current = liveElementPatch;
  const [scale, setScale] = useState(DEFAULT_SCALE);
  const [previewOpen, setPreviewOpen] = useState(false);
  const [previewRulerUnit, setPreviewRulerUnit] = useState<PreviewRulerDisplayUnit>('cm');
  const [starterOptions, setStarterOptions] = useState<Array<{ code: string; name: string }>>([]);
  const [selectedStarterCode, setSelectedStarterCode] = useState<string>('');
  const [loadingStarter, setLoadingStarter] = useState(false);

  const [locationCatalog, setLocationCatalog] = useState<LocationDto[]>([]);
  const [filterGroups, setFilterGroups] = useState<GroupListItem[]>([]);
  const [filterPartners, setFilterPartners] = useState<PartnerListItem[]>([]);
  const [scopeCatalogReady, setScopeCatalogReady] = useState(false);
  const [scopePartnerIds, setScopePartnerIds] = useState<string[]>([]);
  const [scopeRegionIds, setScopeRegionIds] = useState<string[]>([]);
  const [scopeLocationIds, setScopeLocationIds] = useState<string[]>([]);
  const scopeHydrateKeyRef = useRef('');

  const selectedElement = useMemo(() => {
    const el = template.elements.find((item) => item.id === selectedId) ?? null;
    if (!el) return null;
    return mergeLabelElementLivePatch(el, liveElementPatch);
  }, [template.elements, selectedId, liveElementPatch]);

  const previewTemplate = useMemo(
    () => ({
      ...template,
      elements: mergeTemplateElementsLivePatch(template.elements, liveElementPatch),
    }),
    [template, liveElementPatch],
  );

  const printOrientation = template.printOrientation ?? 'horizontal';

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const out: LocationDto[] = [];
        let locPage = 1;
        const locSize = 500;
        for (;;) {
          const res = await getLocations({
            skipCount: skipCountForPage(locPage),
            maxResultCount: locSize,
          });
          out.push(...(res.items ?? []));
          if (!res.items || res.items.length < locSize) break;
          locPage += 1;
          if (locPage > 200) break;
        }
        const [grpRes, partnerRes] = await Promise.all([
          getGroups({ skipCount: 1, maxResultCount: 500 }),
          getPartners({ skipCount: 1, maxResultCount: 500, state: true }),
        ]);
        if (cancelled) return;
        setLocationCatalog(out);
        setFilterGroups(grpRes.items ?? []);
        setFilterPartners(partnerRes.items ?? []);
        setScopeCatalogReady(true);
      } catch {
        if (!cancelled) {
          setLocationCatalog([]);
          setFilterGroups([]);
          setFilterPartners([]);
          setScopeCatalogReady(false);
        }
      }
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  useEffect(() => {
    if (!scopeCatalogReady) return;
    const partnerKey = (template.partnerIds ?? []).join(',');
    const regionKey = (template.regionIds ?? []).join(',');
    const locKey = (template.appliedLocationIds ?? []).join(',');
    const key = [
      template.id,
      template.appliedPartnerType ?? '',
      template.appliedRegionType ?? '',
      template.appliedLocation,
      partnerKey,
      regionKey,
      locKey,
    ].join(':');
    if (scopeHydrateKeyRef.current === key) return;
    scopeHydrateKeyRef.current = key;

    const scope = hydrateLabelTemplateScopeFromDto(
      {
        appliedPartnerType: template.appliedPartnerType,
        appliedRegionType: template.appliedRegionType,
        appliedLocation: template.appliedLocation,
        partnerIds: template.partnerIds,
        companyIds: template.companyIds,
        regionIds: template.regionIds,
        groupIds: template.groupIds,
        locationIds: template.appliedLocationIds,
        appliedLocationIds: template.appliedLocationIds,
      },
      locationCatalog,
      filterPartners,
      filterGroups,
    );
    setScopePartnerIds(scope.partnerIds);
    setScopeRegionIds(scope.regionIds);
    setScopeLocationIds(scope.locationIds);
  }, [
    scopeCatalogReady,
    template.id,
    template.appliedPartnerType,
    template.appliedRegionType,
    template.appliedLocation,
    template.partnerIds,
    template.companyIds,
    template.regionIds,
    template.groupIds,
    template.appliedLocationIds,
    locationCatalog,
    filterPartners,
    filterGroups,
  ]);

  useEffect(() => {
    if (templateId) return;
    let cancelled = false;
    (async () => {
      try {
        const res = await getLabelTemplates({ skipCount: 1, maxResultCount: 200 });
        if (cancelled) return;
        const options = (res.items ?? [])
          .map((x) => {
            const code = (x.templateCode ?? x.id ?? '').trim();
            const name = (x.templateName ?? x.name ?? code).trim();
            return code ? { code, name } : null;
          })
          .filter((x): x is { code: string; name: string } => !!x);
        setStarterOptions(options);
        if (!selectedStarterCode && options.length > 0) {
          setSelectedStarterCode(options[0].code);
        }
      } catch {
        if (!cancelled) setStarterOptions([]);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [templateId, selectedStarterCode]);

  useEffect(() => {
    if (templateId || !selectedStarterCode) return;
    let cancelled = false;
    (async () => {
      setLoadingStarter(true);
      try {
        const apiTemplate = await getLabelTemplate(selectedStarterCode);
        if (cancelled) return;
        const copied = cloneStarterTemplate({
          id: apiTemplate.id,
          name: (apiTemplate.name ?? apiTemplate.templateName ?? '').trim() || 'Unnamed template',
          labelType: (apiTemplate.labelType as any) ?? 'PRICE',
          unit: (apiTemplate.unit as any) ?? 'cm',
          width: Number(apiTemplate.width ?? 6),
          height: Number(apiTemplate.height ?? 4),
          appliedLocation: apiTemplate.appliedLocation === 'SPECIFIED' ? 'SPECIFIED' : 'ALL',
          appliedLocationIds: [...(apiTemplate.appliedLocationIds ?? apiTemplate.locationIds ?? [])],
          appliedPartnerType: apiTemplate.appliedPartnerType as any,
          appliedRegionType: apiTemplate.appliedRegionType as any,
          partnerIds: [...(apiTemplate.partnerIds ?? apiTemplate.companyIds ?? [])],
          companyIds: [...(apiTemplate.companyIds ?? apiTemplate.partnerIds ?? [])],
          regionIds: [
            ...new Set(
              [...(apiTemplate.regionIds ?? []), ...(apiTemplate.groupIds ?? [])]
                .map((x) => String(x).trim())
                .filter(Boolean),
            ),
          ],
          groupIds: [
            ...new Set(
              [...(apiTemplate.regionIds ?? []), ...(apiTemplate.groupIds ?? [])]
                .map((x) => String(x).trim())
                .filter(Boolean),
            ),
          ],
          showRuler: apiTemplate.showRuler ?? true,
          showGrid: apiTemplate.showGrid ?? true,
          border: apiTemplate.border ?? 'none',
          printOrientation: normalizePrintOrientation(
            apiTemplate.printOrientation ?? (apiTemplate as Record<string, unknown>).PrintOrientation,
          ),
          elements: (apiTemplate.elements ?? []).map((raw, idx) => {
            const el = raw as LabelElement;
            const en = (el.elementName ?? '').trim();
            return { ...el, elementName: en || `element${idx + 1}` };
          }),
        });
        setTemplate(copied);
        setLiveElementPatch(null);
        setSelectedId(null);
      } catch (e: any) {
        if (cancelled) return;
        toast.error("Failed to copy starter template.", {
          description: e?.message ? String(e.message) : "Please try again.",
        });
      } finally {
        if (!cancelled) setLoadingStarter(false);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [templateId, selectedStarterCode]);

  /** 纸张尺寸或单位变化时，将已有控件限制在安全区内 */
  useEffect(() => {
    setTemplate((prev) => {
      const unitToPx = (value: number, unit: "cm" | "inch"): number =>
        unit === "cm" ? value * 37.8 : value * 96;
      const baseW = unitToPx(prev.width, prev.unit);
      const baseH = unitToPx(prev.height, prev.unit);
      let changed = false;
      const elements = prev.elements.map((el) => {
        const c = clampLabelElementBox(el.x, el.y, el.width, el.height, baseW, baseH);
        if (el.x !== c.x || el.y !== c.y || el.width !== c.w || el.height !== c.h) {
          changed = true;
          return { ...el, x: c.x, y: c.y, width: c.w, height: c.h };
        }
        return el;
      });
      if (!changed) return prev;
      return { ...prev, elements };
    });
  }, [template.width, template.height, template.unit, template.id]);

  const templateBorderValue = template.border ?? 'none';

  /** 配置区单行 flex */
  const configRowClass = "flex w-full min-w-0 flex-wrap items-center gap-2";

  const updateElement = useCallback((id: string, patch: Partial<LabelElement>) => {
    setTemplate((prev) => {
      const unitToPx = (value: number, unit: "cm" | "inch"): number =>
        unit === "cm" ? value * 37.8 : value * 96;
      const baseW = unitToPx(prev.width, prev.unit);
      const baseH = unitToPx(prev.height, prev.unit);
      const orientation = prev.printOrientation ?? 'horizontal';
      return {
        ...prev,
        elements: prev.elements.map((el) => {
          if (el.id !== id) return el;
          const merged = { ...el, ...patch };
          const geomTouched =
            patch.x !== undefined ||
            patch.y !== undefined ||
            patch.width !== undefined ||
            patch.height !== undefined;
          if (!geomTouched) return merged;
          const c = clampLabelElementBox(
            merged.x,
            merged.y,
            merged.width,
            merged.height,
            baseW,
            baseH,
            undefined,
            orientation,
          );
          return { ...merged, x: c.x, y: c.y, width: c.w, height: c.h };
        }),
      };
    });
  }, []);

  const flushLiveElementPatch = useCallback(() => {
    const patch = liveElementPatchRef.current;
    if (!patch?.id) return;
    const { id, x, y, width, height } = patch;
    const geom: Partial<LabelElement> = {};
    if (x !== undefined) geom.x = x;
    if (y !== undefined) geom.y = y;
    if (width !== undefined) geom.width = width;
    if (height !== undefined) geom.height = height;
    if (Object.keys(geom).length > 0) {
      updateElement(id, geom);
    }
    setLiveElementPatch(null);
  }, [updateElement]);

  const handleSelectElement = useCallback((id: string | null) => {
    flushLiveElementPatch();
    setSelectedId(id);
  }, [flushLiveElementPatch]);

  const addElement = useCallback((
    type: Parameters<typeof createDefaultElement>[0],
    configOverride: Partial<Record<string, unknown>> | undefined,
    libraryCategory: ElementLibraryCategory,
    paletteItemLabel: string,
  ) => {
    let addedId = "";
    setTemplate((prev) => {
      const unitToPx = (value: number, unit: "cm" | "inch"): number =>
        unit === "cm" ? value * 37.8 : value * 96;

      const canvasWidthPx = unitToPx(prev.width, prev.unit);
      const canvasHeightPx = unitToPx(prev.height, prev.unit);

      let el = createDefaultElement(type, 0, 0);
      const GRID_SIZE = 8;
      const snapToGrid = (value: number): number =>
        Math.round(value / GRID_SIZE) * GRID_SIZE;

      let centerX = (canvasWidthPx - el.width) / 2;
      let centerY = (canvasHeightPx - el.height) / 2;

      const checkOverlap = (x: number, y: number, width: number, height: number): boolean =>
        prev.elements.some((o) => {
          const elRight = o.x + o.width;
          const elBottom = o.y + o.height;
          const newRight = x + width;
          const newBottom = y + height;
          return !(x >= elRight || newRight <= o.x || y >= elBottom || newBottom <= o.y);
        });

      if (checkOverlap(centerX, centerY, el.width, el.height)) {
        const offset = GRID_SIZE * 2;
        let found = false;
        for (let tryY = centerY; tryY < canvasHeightPx - el.height && !found; tryY += offset) {
          for (let tryX = centerX; tryX < canvasWidthPx - el.width && !found; tryX += offset) {
            if (!checkOverlap(tryX, tryY, el.width, el.height)) {
              centerX = tryX;
              centerY = tryY;
              found = true;
            }
          }
        }
        if (!found) {
          for (let tryY = centerY; tryY >= 0 && !found; tryY -= offset) {
            for (let tryX = centerX; tryX >= 0 && !found; tryX -= offset) {
              if (!checkOverlap(tryX, tryY, el.width, el.height)) {
                centerX = tryX;
                centerY = tryY;
                found = true;
              }
            }
          }
        }
      }

      el = {
        ...el,
        x: Math.max(0, snapToGrid(centerX)),
        y: Math.max(0, snapToGrid(centerY)),
      };
      if (paletteItemLabel.trim().toLowerCase() === "label id") {
        const bottomY = Math.max(0, canvasHeightPx - el.height - GRID_SIZE);
        el = { ...el, y: snapToGrid(bottomY) };
      }
      const box = clampLabelElementBox(el.x, el.y, el.width, el.height, canvasWidthPx, canvasHeightPx);
      el = { ...el, x: box.x, y: box.y, width: box.w, height: box.h };
      if (configOverride && Object.keys(configOverride).length > 0) {
        el.config = { ...el.config, ...configOverride };
      }
      el.config = applyPaletteLabelDefaultText(
        type,
        el.config as Record<string, unknown>,
        paletteItemLabel,
        configOverride,
      );
      const elementName = allocateElementName(paletteItemLabel, prev.elements);
      const vst = valueSourceTypeForLibraryCategory(libraryCategory);
      el = {
        ...el,
        type: el.type,
        typeAdd: composeElementTypeForPersist(libraryCategory, paletteItemLabel),
        libraryCategory: composeLibraryCategoryForPersist(libraryCategory, paletteItemLabel),
        valueSourceType: vst,
        elementName,
      };
      addedId = el.id;
      return { ...prev, elements: [...prev.elements, el] };
    });
    handleSelectElement(addedId);
  }, [template.width, template.height, template.unit, handleSelectElement]);

  const deleteElement = useCallback((id: string) => {
    setTemplate((prev) => ({
      ...prev,
      elements: prev.elements.filter((el) => el.id !== id),
    }));
    handleSelectElement(null);
  }, [handleSelectElement]);

  const handleTemplateChange = useCallback((patch: Partial<LabelTemplate>) => {
    setTemplate((prev) => ({ ...prev, ...patch }));
  }, []);

  const handlePrintOrientationChange = useCallback((orientation: PrintOrientation) => {
    flushLiveElementPatch();
    handleTemplateChange({ printOrientation: orientation });
  }, [flushLiveElementPatch, handleTemplateChange]);

  const handleSave = useCallback(async () => {
    try {
      const code = (template.id ?? "").trim();
      if (!code) {
        toast.error("Template code is required.", {
          description: "Please enter a template code (e.g. TPL_TEST_001).",
        });
        return;
      }
      const emptyName = template.elements.find(
        (el) => !(el.elementName ?? "").trim(),
      );
      if (emptyName) {
        toast.error("Component name required.", {
          description: "Each element must have a non-empty element name.",
        });
        return;
      }

      const optionsWithoutDictionary = template.elements.find((el) => {
        if (el.type !== "TEXT_STATIC") return false;
        const cfg = el.config as Record<string, unknown>;
        if (String(cfg?.inputType ?? "").toLowerCase() !== "options") return false;
        const mid = String(cfg?.multipleOptionId ?? cfg?.MultipleOptionId ?? "").trim();
        return !mid;
      });
      if (optionsWithoutDictionary) {
        toast.error("Option dictionary required.", {
          description:
            "Each Multiple Options element must have an Option dictionary selected in the properties panel.",
        });
        return;
      }

      const effectivePartnerIds = scopeAuth.requireCompanySelection
        ? scopePartnerIds
        : [scopeAuth.fixedPartnerId.trim()].filter(Boolean);
      const availablePartnerIds = scopeAuth.requireCompanySelection
        ? filterPartners.map((p) => p.id).filter(Boolean)
        : effectivePartnerIds;
      const availableRegionIds = regionOptionsForPartners(
        filterGroups,
        effectivePartnerIds.length > 0 ? effectivePartnerIds : availablePartnerIds,
      ).map((o) => o.value);
      const scopedLocations = locationsScopedForTemplateScope(
        locationCatalog,
        filterPartners,
        filterGroups,
        effectivePartnerIds,
        scopeRegionIds,
      );
      const availableLocationIds = scopedLocations.map((l) => l.id);

      const scopePayload = buildLabelTemplateScopePayload({
        selectedPartnerIds: effectivePartnerIds,
        selectedRegionIds: scopeRegionIds,
        selectedLocationIds: scopeLocationIds,
        availablePartnerIds,
        availableRegionIds,
        availableLocationIds,
      });
      if (!scopePayload.ok) {
        toast.error("Validation failed", { description: scopePayload.message });
        return;
      }

      const apiInput = {
        id: code,
        name: template.name,
        labelType: template.labelType,
        unit: template.unit,
        width: template.width,
        height: template.height,
        showRuler: true,
        showGrid: template.showGrid ?? true,
        border: template.border ?? 'none',
        printOrientation: template.printOrientation ?? 'horizontal',
        state: true,
        elements: labelElementsToApiPayload(template.elements),
        ...scopePayload.body,
      };

      if (templateId) {
        // 编辑模式：使用 TemplateCode 作为 id
        await updateLabelTemplate(code, apiInput);
        toast.success("Template updated.", {
          description: "The template has been updated successfully.",
        });
      } else {
        // 新建模式
        await createLabelTemplate(apiInput);
        toast.success("Template created.", {
          description: "The template has been created successfully.",
        });
      }
      onSaved();
      onClose();
    } catch (e: any) {
      toast.error("Failed to save template.", {
        description: e?.message ? String(e.message) : "Please try again.",
      });
    }
  }, [
    template,
    templateId,
    onSaved,
    onClose,
    locationCatalog,
    filterPartners,
    filterGroups,
    scopeAuth.requireCompanySelection,
    scopeAuth.fixedPartnerId,
    scopePartnerIds,
    scopeRegionIds,
    scopeLocationIds,
  ]);

  const handleExport = useCallback(() => {
    const payload: LabelTemplate = {
      ...template,
      elements: template.elements.map((el) => ({
        ...el,
        type: canonicalElementType(el.type),
        typeAdd: resolvedTypeAddForPersist(el),
        elementName: (el.elementName ?? "").trim(),
        valueSourceType: resolvedValueSourceTypeForSave(el),
        libraryCategory: resolvedLibraryCategoryForPersist(el),
        config: stripLabelConfigPrefixes((el.config ?? {}) as Record<string, unknown>),
      })),
    };
    const blob = new Blob([JSON.stringify(payload, null, 2)], {
      type: 'application/json',
    });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `label-template-${template.id}.json`;
    a.click();
    URL.revokeObjectURL(url);
  }, [template]);

  return (
    <div
      className="flex flex-col overflow-hidden"
      style={{
        flex: "1 1 0%",
        minHeight: 0,
        display: "flex",
        flexDirection: "column",
        overflow: "hidden",
      }}
    >
      {/* 顶栏：仅导航与保存 */}
      <div className="flex shrink-0 items-center gap-2 border-b border-gray-200 bg-white px-4 py-2">
        <Button
          size="sm"
          className="h-8 shrink-0 bg-blue-600 px-3 text-xs text-white hover:bg-blue-700"
          onClick={onClose}
        >
          <ArrowLeft className="mr-1 h-3.5 w-3.5" />
          Back
        </Button>
        <span className="min-w-0 flex-1 truncate text-sm font-medium text-gray-700">{template.name}</span>
        <Button size="sm" variant="outline" className="h-8 shrink-0 text-xs" onClick={handleExport}>
          <Download className="mr-1 h-3.5 w-3.5" />
          Export JSON
        </Button>
        <Button
          size="sm"
          className="h-8 shrink-0 bg-blue-600 px-3 text-xs text-white hover:bg-blue-700"
          onClick={handleSave}
        >
          <Save className="mr-1 h-3.5 w-3.5" />
          Save
        </Button>
      </div>

      {/* 模板配置区：统一 input-group 样式 */}
      <div className="shrink-0 border-b border-[#cfd9ea] bg-[#dde7f5] px-4 py-2">
        <div className="rounded-lg border border-[#c2d1e8] bg-[#e4ecf8] p-2">
          <div className="flex flex-col gap-2">
            <div className={configRowClass}>
              <TemplateFieldGroup label="Name:" className="min-w-[10rem] flex-[1_1_12rem] max-w-sm">
                <Input
                  value={template.name}
                  onChange={(e) => handleTemplateChange({ name: e.target.value })}
                  className={templateFieldInputClass}
                />
              </TemplateFieldGroup>

              <TemplateFieldGroup label="Unit:" className="w-[6.75rem] shrink-0">
                <Select
                  value={template.unit}
                  onValueChange={(v: "cm" | "inch") => handleTemplateChange({ unit: v, showRuler: true })}
                >
                  <SelectTrigger className={templateFieldSelectTriggerClass}>
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="inch" className="text-xs">
                      Inch
                    </SelectItem>
                    <SelectItem value="cm" className="text-xs">
                      cm
                    </SelectItem>
                  </SelectContent>
                </Select>
              </TemplateFieldGroup>

              <TemplateFieldGroup
                label="W:"
                suffix={templateUnitShortLabel(template.unit)}
                className="w-[5.25rem] shrink-0"
              >
                <Input
                  type="number"
                  value={template.width}
                  onChange={(e) =>
                    handleTemplateChange({ width: Math.max(0.1, Number(e.target.value) || 0), showRuler: true })
                  }
                  className={`${templateFieldInputClass} px-1 text-center`}
                />
              </TemplateFieldGroup>

              <TemplateFieldGroup
                label="H:"
                suffix={templateUnitShortLabel(template.unit)}
                className="w-[5.25rem] shrink-0"
              >
                <Input
                  type="number"
                  value={template.height}
                  onChange={(e) =>
                    handleTemplateChange({ height: Math.max(0.1, Number(e.target.value) || 0), showRuler: true })
                  }
                  className={`${templateFieldInputClass} px-1 text-center`}
                />
              </TemplateFieldGroup>

              <TemplateFieldGroup label="Size Preset:" className="w-[8.5rem] shrink-0">
                <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) handleTemplateChange({ width: p.width, height: p.height, unit: p.unit, showRuler: true });
                  }}
                >
                  <SelectTrigger className={templateFieldSelectTriggerClass}>
                    <SelectValue placeholder="Canvas size" />
                  </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">
                      Custom
                    </SelectItem>
                  </SelectContent>
                </Select>
              </TemplateFieldGroup>

              <TemplateFieldGroup label="Starter Template:" className="min-w-[9rem] flex-[1_1_10rem]">
                {!templateId ? (
                  <Select value={selectedStarterCode} onValueChange={setSelectedStarterCode}>
                    <SelectTrigger className={templateFieldSelectTriggerClass}>
                      <SelectValue placeholder="Select starter template" />
                    </SelectTrigger>
                    <SelectContent>
                      {starterOptions.map((op) => (
                        <SelectItem key={op.code} value={op.code} className="text-xs">
                          {op.name}
                        </SelectItem>
                      ))}
                    </SelectContent>
                  </Select>
                ) : (
                  <Input value="Current template" disabled className={templateFieldInputClass} />
                )}
              </TemplateFieldGroup>

              <TemplateFieldGroup label="Border:" className="w-[7.5rem] shrink-0">
                <Select
                  value={templateBorderValue}
                  onValueChange={(v: "none" | "line" | "dotted") => handleTemplateChange({ border: v })}
                >
                  <SelectTrigger className={templateFieldSelectTriggerClass}>
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="none" className="text-xs">
                      none
                    </SelectItem>
                    <SelectItem value="line" className="text-xs">
                      line
                    </SelectItem>
                    <SelectItem value="dotted" className="text-xs">
                      dotted
                    </SelectItem>
                  </SelectContent>
                </Select>
              </TemplateFieldGroup>
            </div>

            <div className={`${configRowClass} flex-nowrap`}>
              <TemplateFieldGroup label="Rotation:" className="w-[8.5rem] shrink-0">
                <Select
                  value={printOrientation}
                  onValueChange={(v: PrintOrientation) => handlePrintOrientationChange(v)}
                >
                  <SelectTrigger className={templateFieldSelectTriggerClass}>
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="horizontal" className="text-xs">
                      horizontal
                    </SelectItem>
                    <SelectItem value="vertical" className="text-xs">
                      vertical
                    </SelectItem>
                  </SelectContent>
                </Select>
              </TemplateFieldGroup>

              {!scopeCatalogReady ? (
                <span className="min-w-0 flex-1 text-[11px] leading-tight text-[#1d3c8f]">
                  Loading company, region and location…
                </span>
              ) : (
                <CategoryScopeFields
                  layout="templateEditorInputGroup"
                  templateScopeMode
                  partners={filterPartners}
                  groups={filterGroups}
                  locations={locationCatalog}
                  selectedPartnerId=""
                  onPartnerChange={() => {}}
                  selectedPartnerIds={scopePartnerIds}
                  onPartnerIdsChange={setScopePartnerIds}
                  selectedRegionNames={[]}
                  onRegionChange={() => {}}
                  selectedRegionIds={scopeRegionIds}
                  onRegionIdsChange={setScopeRegionIds}
                  selectedLocationIds={scopeLocationIds}
                  onLocationChange={setScopeLocationIds}
                  requireCompanySelection={scopeAuth.requireCompanySelection}
                  fixedPartnerId={scopeAuth.fixedPartnerId}
                />
              )}
            </div>

            {loadingStarter ? (
              <div className="text-[11px] leading-tight text-[#1d3c8f]">Copying starter template…</div>
            ) : null}
          </div>
        </div>
      </div>

      {/* 三列：行布局内联；左侧整列 overflow-y:auto，保证橙色 Print input 可滚到 */}
      <div
        className="gap-2 bg-[#dde7f5] p-2"
        style={{
          flex: "1 1 0%",
          minHeight: 0,
          display: "flex",
          flexDirection: "row",
          alignItems: "stretch",
          gap: 8,
        }}
      >
        <div
          className="shrink-0 rounded-lg border border-[#c2d1e8] bg-[#e4ecf8] p-1"
          style={{
            width: "15rem",
            flexShrink: 0,
            alignSelf: "stretch",
            minHeight: 0,
            overflowX: "hidden",
            overflowY: "auto",
            WebkitOverflowScrolling: "touch",
            boxSizing: "border-box",
          }}
        >
          <ElementsPanel onAddElement={addElement} />
        </div>
        <div
          className="rounded-lg border border-[#c2d1e8] bg-[#e4ecf8] p-1"
          style={{
            flex: "1 1 0%",
            minHeight: 0,
            minWidth: 0,
            overflow: "hidden",
            display: "flex",
            flexDirection: "column",
          }}
        >
          <LabelCanvas
            template={template}
            selectedId={selectedId}
            onSelect={handleSelectElement}
            onUpdateElement={updateElement}
            onDeleteElement={deleteElement}
            onTemplateChange={handleTemplateChange}
            scale={scale}
            onZoomIn={() => setScale((s) => Math.min(MAX_SCALE, s + SCALE_STEP))}
            onZoomOut={() => setScale((s) => Math.max(MIN_SCALE, s - SCALE_STEP))}
            onResetZoom={() => setScale(DEFAULT_SCALE)}
            onPreview={() => setPreviewOpen(true)}
            hideToolbarPresetSize
            previewRulerUnit={previewRulerUnit}
            onPreviewRulerUnitChange={setPreviewRulerUnit}
            liveElementPatch={liveElementPatch}
            onLiveElementPatchChange={setLiveElementPatch}
            printOrientation={printOrientation}
            onPrintOrientationChange={handlePrintOrientationChange}
          />
        </div>
        <div
          className="rounded-lg border border-[#c2d1e8] bg-[#e4ecf8] p-1"
          style={{
            width: "18rem",
            flexShrink: 0,
            alignSelf: "stretch",
            minHeight: 0,
            overflow: "hidden",
            display: "flex",
            flexDirection: "column",
          }}
        >
          <div className="shrink-0 border-b border-[#c2d1e8] bg-white/80 px-2 py-2">
            <p className="mb-2 text-xs font-medium text-gray-700">Print preview</p>
            <div className="flex justify-center overflow-hidden">
              <LabelPreviewOnly
                template={previewTemplate}
                maxWidth={248}
                highlightElementId={selectedId}
                previewRulerUnit={previewRulerUnit}
                printOrientation={printOrientation}
              />
            </div>
          </div>
          <div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden">
            <PropertiesPanel
              template={template}
              selectedElement={selectedElement}
              onTemplateChange={handleTemplateChange}
              onElementChange={updateElement}
              onDeleteElement={deleteElement}
              readOnlyTemplateCode={!!templateId}
              previewRulerUnit={previewRulerUnit}
              printOrientation={printOrientation}
            />
          </div>
        </div>
      </div>
      <Dialog open={previewOpen} onOpenChange={setPreviewOpen}>
        <DialogContent className="max-w-[90vw] max-h-[90vh] p-0 overflow-hidden flex flex-col">
          <DialogHeader className="shrink-0 px-6 py-4 border-b bg-white">
            <DialogTitle>Label preview</DialogTitle>
          </DialogHeader>
          <div className="flex-1 min-h-0 overflow-x-auto overflow-y-auto p-4 bg-gray-50">
            <div className="min-w-max">
              <LabelPreviewOnly
                template={previewTemplate}
                highlightElementId={selectedId}
                previewRulerUnit={previewRulerUnit}
                printOrientation={printOrientation}
              />
            </div>
          </div>
        </DialogContent>
      </Dialog>
    </div>
  );
}
