import React, { useCallback, useEffect, useState } from 'react';
import { Building2, Check, Mail, Map, MapPin, Mailbox, RotateCcw, RotateCw, Trash2 } from 'lucide-react';
import { Input } from '../../ui/input';
import { Button } from '../../ui/button';
import { Label } from '../../ui/label';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '../../ui/select';
import { Switch } from '../../ui/switch';
import type {
  LabelTemplate,
  LabelElement,
  Unit,
  Rotation,
  Border,
  NutritionExtraItem,
  PrintOrientation,
} from '../../../types/labelTemplate';
import {
  canonicalElementType,
  isAutoGeneratedElement,
  isBlankSpaceElement,
  isCompanyAutoElement,
  isTemplateSectionPersistedType,
  isLabelSectionPersistedType,
  isPrintInputSectionPersistedType,
  elementEditorDisplayName,
  readElementPositionLocked,
  NUTRITION_FIXED_ITEMS,
  LABEL_EDITOR_FONT_OPTIONS,
  readLabelEditorFontFamilyChoice,
} from '../../../types/labelTemplate';
import {
  buildCompanyPrintFieldsConfigPatch,
  COMPANY_PRINT_FIELD_OPTIONS,
  readCompanyPrintFields,
  type CompanyPrintFieldKey,
} from '../../../utils/companyPrintFields';
import { cn } from '../../ui/utils';
import { readInvertColors, isTextLikeElementForInvertColors } from '../../../utils/invertColorsConfig';
import {
  elementRotationDegrees,
  readVerticalAlign,
  rotateElementLeftValue,
  rotateElementRightValue,
  readFontWeight,
  readFontStyle,
  readTextDecoration,
} from '../../../utils/textElementLayout';
import {
  type PreviewRulerDisplayUnit,
  displayLengthToElementPx,
  elementPxToDisplayLength,
  formatPreviewRulerDisplayValue,
  previewRulerUnitLabel,
} from '../../../utils/previewRulerUnits';
import { unitToPx } from '../../../utils/labelTemplateUnits';
import { ImageUrlUpload } from '../../ui/image-url-upload';
import { readImageScaleMode } from '../../../utils/imageScaleMode';
import {
  formatWeightDisplay,
  readWeightInputMode,
  WEIGHT_INPUT_MODE_OPTIONS,
} from '../../../utils/weightElement';
import type { LabelMultipleOptionDto } from '../../../types/labelMultipleOption';
import { getLabelMultipleOptions } from '../../../services/labelMultipleOptionService';
import { Checkbox } from '../../ui/checkbox';
import {
  NutritionManualEntryForm,
  applyNutritionValuesToElementConfig,
  nutritionValuesFromElementConfig,
} from '../NutritionManualEntryForm';
import {
  BARCODE_FORMAT_OPTIONS,
  DEFAULT_BARCODE_FORMAT,
  normalizeBarcodeType,
} from '../../../lib/barcodeFormat';

const DATE_FORMAT_OPTIONS = [
  'DD/MM/YYYY',
  'MM/DD/YYYY',
  'DD/MM/YY',
  'MM/DD/YY',
  'MM/YY',
  'MM/DD',
  'MM',
  'DD',
  'YY',
  'FULLY DAY(WEDNESDAY)',
  'DAY (WED)',
  'MONTH (DECEMBER)',
  'YEAR (2025)',
  'DD MONTH YEAR (25 DECEMBER 2025)',
] as const;
const DATETIME_DEFAULT_FORMAT = 'YYYY-MM-DD HH:mm';

const DURATION_FORMAT_OPTIONS = [
  'Minutes',
  'Hours',
  'Days',
  'Weeks',
  'Months (30 Day)',
  'Years',
] as const;

interface PropertiesPanelProps {
  template: LabelTemplate;
  selectedElement: LabelElement | null;
  onTemplateChange: (patch: Partial<LabelTemplate>) => void;
  onElementChange: (id: string, patch: Partial<LabelElement>) => void;
  onDeleteElement?: (id: string) => void;
  /** 编辑已有模板时禁止修改 Template Code */
  readOnlyTemplateCode?: boolean;
  /** 与预览区标尺单位一致，用于 W/H 读数后缀 */
  previewRulerUnit?: PreviewRulerDisplayUnit;
  /** 竖打预览时 W/H 沿纸张坐标轴，不随预览旋转 */
  printOrientation?: PrintOrientation;
}

export function PropertiesPanel({
  template,
  selectedElement,
  onTemplateChange,
  onElementChange,
  onDeleteElement,
  readOnlyTemplateCode = false,
  previewRulerUnit = 'cm',
  printOrientation = 'vertical',
}: PropertiesPanelProps) {
  void onTemplateChange;
  void readOnlyTemplateCode;
  if (selectedElement) {
    const isBlankElement = isBlankSpaceElement(selectedElement);
    const elementDisplayName = elementEditorDisplayName(
      selectedElement,
      template.elements ?? [],
    );
    const positionLocked = readElementPositionLocked(selectedElement);
    return (
      <div className="flex h-full min-h-0 w-full min-w-0 flex-col border-l border-gray-200 bg-white">
        <div className="shrink-0 border-b border-gray-200 px-3 py-2 font-semibold text-gray-800">
          Properties (Element)
        </div>
        <div className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-contain">
          <div className="space-y-3 p-3">
            <div className="grid grid-cols-2 gap-2">
              <div>
                <Label className="text-xs">X</Label>
                <Input
                  type="number"
                  value={selectedElement.x}
                  disabled={positionLocked}
                  onChange={(e) =>
                    onElementChange(selectedElement.id, {
                      x: Number(e.target.value) || 0,
                    })
                  }
                  className="h-8 text-sm"
                />
              </div>
              <div>
                <Label className="text-xs">Y</Label>
                <Input
                  type="number"
                  value={selectedElement.y}
                  disabled={positionLocked}
                  onChange={(e) =>
                    onElementChange(selectedElement.id, {
                      y: Number(e.target.value) || 0,
                    })
                  }
                  className="h-8 text-sm"
                />
              </div>
            </div>
            {positionLocked ? (
              <p className="text-[10px] leading-snug text-amber-700">
                Position is locked. Unlock in Available Elements to move or resize.
              </p>
            ) : null}
            <div className="grid grid-cols-2 gap-2">
              <ElementDimensionField
                label="Width"
                pxValue={selectedElement.width}
                paperSizeTemplate={template.width}
                templateUnit={template.unit}
                displayUnit={previewRulerUnit}
                disabled={positionLocked}
                onPxChange={(width) =>
                  onElementChange(selectedElement.id, {
                    width: Math.max(1, width),
                  })
                }
              />
              <ElementDimensionField
                label="Height"
                pxValue={selectedElement.height}
                paperSizeTemplate={template.height}
                templateUnit={template.unit}
                displayUnit={previewRulerUnit}
                disabled={positionLocked}
                onPxChange={(height) =>
                  onElementChange(selectedElement.id, {
                    height: Math.max(1, height),
                  })
                }
              />
            </div>
            {printOrientation === 'horizontal' ? (
              <p className="text-[10px] leading-snug text-gray-500">
                Width / Height follow paper axes (W×H), not the rotated preview. Switch to vertical to align with what you see on screen.
              </p>
            ) : null}
            {!isBlankElement ? (
              <div>
                <Label className="text-xs">Rotate</Label>
                <div className="mt-1 flex items-center gap-2">
                  <Button
                    type="button"
                    variant="outline"
                    size="sm"
                    className="h-8 gap-1 px-2 text-xs"
                    disabled={positionLocked}
                    onClick={() =>
                      onElementChange(selectedElement.id, {
                        rotation: rotateElementLeftValue(selectedElement) as Rotation,
                      })
                    }
                  >
                    <RotateCcw className="h-4 w-4" />
                    Left
                  </Button>
                  <Button
                    type="button"
                    variant="outline"
                    size="sm"
                    className="h-8 gap-1 px-2 text-xs"
                    disabled={positionLocked}
                    onClick={() =>
                      onElementChange(selectedElement.id, {
                        rotation: rotateElementRightValue(selectedElement) as Rotation,
                      })
                    }
                  >
                    <RotateCw className="h-4 w-4" />
                    Right
                  </Button>
                  <span className="text-xs text-gray-500">
                    {elementRotationDegrees(selectedElement)}&deg;
                  </span>
                </div>
              </div>
            ) : null}
            {!isBlankElement ? (
              <div>
                <Label className="text-xs">Border</Label>
                <Select
                  value={selectedElement.border}
                  onValueChange={(v: Border) =>
                    onElementChange(selectedElement.id, { border: v })
                  }
                >
                  <SelectTrigger className="h-8 text-sm">
                    <SelectValue />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="none">none</SelectItem>
                    <SelectItem value="line">line</SelectItem>
                    <SelectItem value="dotted">dotted</SelectItem>
                  </SelectContent>
                </Select>
              </div>
            ) : null}
            {!isBlankElement &&
            isTextLikeElementForInvertColors(canonicalElementType(selectedElement.type)) ? (
              <InvertColorsField
                checked={readInvertColors(selectedElement.config as Record<string, unknown>)}
                onChange={(v) =>
                  onElementChange(selectedElement.id, {
                    config: { ...selectedElement.config, invertColors: v },
                  })
                }
              />
            ) : null}
            <div>
              <Label className="text-xs">Element name</Label>
              <Input
                value={elementDisplayName}
                readOnly
                className="h-8 text-sm mt-1 bg-gray-50 text-gray-700 cursor-default"
                placeholder="e.g. text"
              />
            </div>
            <ElementConfigFields
              element={selectedElement}
              onChange={(config) =>
                onElementChange(selectedElement.id, { config: { ...selectedElement.config, ...config } })
              }
            />
            {isCompanyAutoElement(selectedElement) ? (
              <CompanyPrintFieldsEditor
                cfg={selectedElement.config as Record<string, unknown>}
                onPatch={(patch) =>
                  onElementChange(selectedElement.id, {
                    config: { ...selectedElement.config, ...patch },
                  })
                }
              />
            ) : null}
            {onDeleteElement && (
              <div className="pt-4 border-t border-gray-100">
                <Button
                  variant="destructive"
                  className="w-full gap-2"
                  onClick={() => onDeleteElement(selectedElement.id)}
                >
                  <Trash2 className="h-4 w-4 shrink-0" />
                  Delete Element
                </Button>
              </div>
            )}
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="flex h-full min-h-0 w-full min-w-0 flex-col border-l border-gray-200 bg-white">
      <div className="shrink-0 border-b border-gray-200 px-3 py-2 font-semibold text-gray-800">
        Properties (Element)
      </div>
      <div className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-contain">
        <div className="p-3">
          <div className="rounded-md border border-blue-100 bg-blue-50/50 p-3 text-xs text-blue-900">
            Select an element on the canvas to edit its properties.
          </div>
        </div>
      </div>
    </div>
  );
}

const MULTIPLE_OPTION_NONE = '__none__';

/** 绑定「Multiple Options」页维护的字典：先选字典，再在该字典的值列表中多选 */
function MultipleOptionsDictionaryFields({
  cfg,
  onPatch,
}: {
  cfg: Record<string, unknown>;
  onPatch: (patch: Record<string, unknown>) => void;
}) {
  const [rows, setRows] = useState<LabelMultipleOptionDto[]>([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    getLabelMultipleOptions({ skipCount: 1, maxResultCount: 500 })
      .then((res) => {
        if (!cancelled) setRows(res.items ?? []);
      })
      .catch(() => {
        if (!cancelled) setRows([]);
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, []);

  const selectedId = ((cfg.multipleOptionId as string) ?? '').trim();
  const selectedVals = Array.isArray(cfg.selectedOptionValues)
    ? (cfg.selectedOptionValues as string[])
    : [];
  const active = rows.find((r) => r.id === selectedId);
  const valueList = active?.optionValuesJson ?? [];

  /** 从服务端拉到的字典列表就绪后，为已绑定 id 的旧模板补上 multipleOptionName，画布才能显示「名称:」前缀 */
  useEffect(() => {
    if (!selectedId || rows.length === 0) return;
    const row = rows.find((r) => r.id === selectedId);
    const name = String(row?.optionName ?? '').trim();
    if (!row || !name) return;
    const current = String(cfg.multipleOptionName ?? '').trim();
    if (name !== current) {
      onPatch({ multipleOptionName: name });
    }
  }, [selectedId, rows, cfg.multipleOptionName, onPatch]);

  const selectValue = selectedId ? selectedId : MULTIPLE_OPTION_NONE;

  return (
    <>
      <div>
        <Label className="text-xs">Option dictionary</Label>
        <Select
          value={selectValue}
          onValueChange={(id) => {
            if (id === MULTIPLE_OPTION_NONE) {
              onPatch({ multipleOptionId: '', multipleOptionName: '', selectedOptionValues: [] });
              return;
            }
            const next = rows.find((r) => r.id === id);
            const allowed = new Set(next?.optionValuesJson ?? []);
            const filtered = selectedVals.filter((v) => allowed.has(v));
            const optName = String(next?.optionName ?? next?.optionCode ?? '').trim();
            onPatch({
              multipleOptionId: id,
              multipleOptionName: optName,
              selectedOptionValues: filtered,
            });
          }}
          disabled={loading}
        >
          <SelectTrigger className="h-8 text-sm mt-1">
            <SelectValue placeholder={loading ? 'Loading…' : 'Select from Multiple Options'} />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value={MULTIPLE_OPTION_NONE}>— None —</SelectItem>
            {rows.map((o) => (
              <SelectItem key={o.id} value={o.id}>
                {(o.optionName ?? o.optionCode ?? o.id) as string}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
        <p className="text-[10px] text-gray-400 mt-1">
          Data comes from the Multiple Options tab (label-multiple-option list).
        </p>
      </div>
      {active && valueList.length > 0 ? (
        <div>
          <Label className="text-xs">Values (multi-select)</Label>
          <div className="mt-1 max-h-44 overflow-y-auto border border-gray-200 rounded-md p-2 space-y-2 bg-gray-50/50">
            {valueList.map((val) => (
              <div key={val} className="flex items-center gap-2 min-w-0">
                <Checkbox
                  className="shrink-0"
                  checked={selectedVals.includes(val)}
                  onCheckedChange={(checked) => {
                    const set = new Set(selectedVals);
                    if (checked) set.add(val);
                    else set.delete(val);
                    onPatch({ selectedOptionValues: Array.from(set) });
                  }}
                />
                <span className="text-xs truncate" title={val}>
                  {val}
                </span>
              </div>
            ))}
          </div>
        </div>
      ) : selectedId ? (
        <p className="text-[10px] text-amber-600">No values in this dictionary or still loading.</p>
      ) : null}
    </>
  );
}

const TEMPLATE_IMAGE_UPLOAD_SIZE_PX = 100;

const COMPANY_PRINT_FIELD_ICONS: Record<
  CompanyPrintFieldKey,
  React.ComponentType<{ className?: string; strokeWidth?: number }>
> = {
  address: MapPin,
  city: Building2,
  state: Map,
  zip: Mailbox,
  email: Mail,
};

function CompanyPrintFieldsEditor({
  cfg,
  onPatch,
}: {
  cfg: Record<string, unknown>;
  onPatch: (patch: Record<string, unknown>) => void;
}) {
  const fields = readCompanyPrintFields(cfg);

  const toggleField = (key: CompanyPrintFieldKey) => {
    const nextFields = {
      ...fields,
      [key]: !fields[key],
    };
    onPatch(buildCompanyPrintFieldsConfigPatch(nextFields));
  };

  return (
    <div className="rounded-md border border-slate-200 bg-slate-50/80 p-2.5">
      <Label className="text-xs font-medium text-gray-800">
        Company info on label
      </Label>
      <p className="mt-0.5 text-[10px] leading-snug text-gray-500">
        Company name is always printed. Select which fields to include on the label.
      </p>
      <div className="mt-2 grid grid-cols-2 gap-2">
        {COMPANY_PRINT_FIELD_OPTIONS.map((item) => {
          const Icon = COMPANY_PRINT_FIELD_ICONS[item.key];
          const selected = fields[item.key];
          return (
            <button
              key={item.key}
              type="button"
              onClick={() => toggleField(item.key)}
              className={cn(
                'relative flex min-h-[4.5rem] flex-col items-center justify-center gap-1 rounded-lg border p-2 text-center shadow-sm transition-colors',
                selected
                  ? 'border-blue-600 bg-blue-600 text-white'
                  : 'border-slate-200 bg-white hover:border-slate-300 hover:bg-slate-50',
              )}
              aria-pressed={selected}
            >
              {selected ? (
                <span className="absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-white/25 text-white">
                  <Check className="h-2.5 w-2.5" strokeWidth={3} />
                </span>
              ) : null}
              <Icon
                className={cn('h-5 w-5 shrink-0', selected ? 'text-white' : 'text-rose-900')}
                strokeWidth={1.5}
              />
              <span
                className={cn(
                  'text-[10px] leading-tight',
                  selected ? 'text-white' : 'text-gray-800',
                )}
              >
                {item.label}
              </span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

function ElementDimensionField({
  label,
  pxValue,
  paperSizeTemplate,
  templateUnit,
  displayUnit,
  onPxChange,
  disabled = false,
}: {
  label: string;
  pxValue: number;
  paperSizeTemplate: number;
  templateUnit: Unit;
  displayUnit: PreviewRulerDisplayUnit;
  onPxChange: (px: number) => void;
  disabled?: boolean;
}) {
  const basePaperPx = unitToPx(Number(paperSizeTemplate) || 0, templateUnit);
  const paperSize = Number(paperSizeTemplate) || 0;
  const displayValue = elementPxToDisplayLength(
    pxValue,
    basePaperPx,
    paperSize,
    templateUnit,
    displayUnit,
  );
  const formatted = formatPreviewRulerDisplayValue(displayValue, displayUnit);
  const unitLabel = previewRulerUnitLabel(displayUnit);
  const step = displayUnit === 'mm' ? 1 : displayUnit === 'inch' ? 0.0001 : 0.001;

  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState('');

  const commitDisplay = useCallback(
    (raw: string): boolean => {
      const trimmed = raw.trim();
      if (trimmed === '' || trimmed === '-' || trimmed === '.') return false;
      const nextDisplay = Number(trimmed);
      if (!Number.isFinite(nextDisplay)) return false;
      const nextPx = Math.round(
        displayLengthToElementPx(
          nextDisplay,
          basePaperPx,
          paperSize,
          templateUnit,
          displayUnit,
        ),
      );
      onPxChange(Math.max(1, nextPx));
      return true;
    },
    [basePaperPx, displayUnit, onPxChange, paperSize, templateUnit],
  );

  return (
    <div>
      <Label className="text-xs">{label}</Label>
      <div className="mt-1 flex items-center gap-1.5">
        <Input
          type="number"
          step={step}
          value={editing ? draft : formatted}
          disabled={disabled}
          onFocus={() => {
            setEditing(true);
            setDraft(formatted);
          }}
          onBlur={() => {
            commitDisplay(draft);
            setEditing(false);
          }}
          onChange={(e) => {
            const raw = e.target.value;
            setDraft(raw);
            const trimmed = raw.trim();
            if (
              trimmed !== '' &&
              trimmed !== '-' &&
              !trimmed.endsWith('.') &&
              !trimmed.endsWith('-')
            ) {
              commitDisplay(raw);
            }
          }}
          onKeyDown={(e) => {
            if (e.key === 'Enter') {
              commitDisplay(draft);
              setEditing(false);
              e.currentTarget.blur();
            }
            if (e.key === 'Escape') {
              setEditing(false);
              e.currentTarget.blur();
            }
          }}
          className="h-8 min-w-0 flex-1 text-sm"
        />
        <span className="w-9 shrink-0 text-xs text-gray-500">{unitLabel}</span>
      </div>
    </div>
  );
}

function InvertColorsField({
  checked,
  onChange,
}: {
  checked: boolean;
  onChange: (value: boolean) => void;
}) {
  return (
    <div className="flex items-center gap-2">
      <Switch checked={checked} onCheckedChange={onChange} />
      <Label className="text-xs">Invert colors</Label>
    </div>
  );
}

function VerticalAlignField({
  cfg,
  update,
}: {
  cfg: Record<string, unknown>;
  update: (key: string, value: unknown) => void;
}) {
  return (
    <div>
      <Label className="text-xs">Vertical Alignment</Label>
      <Select
        value={readVerticalAlign(cfg)}
        onValueChange={(v) => update('verticalAlign', v)}
      >
        <SelectTrigger className="h-8 text-sm mt-1">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="top">Top</SelectItem>
          <SelectItem value="center">Center</SelectItem>
          <SelectItem value="bottom">Bottom</SelectItem>
        </SelectContent>
      </Select>
    </div>
  );
}

function FontFamilyField({
  cfg,
  update,
}: {
  cfg: Record<string, unknown>;
  update: (key: string, value: unknown) => void;
}) {
  const current = readLabelEditorFontFamilyChoice(cfg);
  return (
    <div>
      <Label className="text-xs">Font</Label>
      <Select value={current} onValueChange={(v) => update('fontFamily', v)}>
        <SelectTrigger
          className="h-8 text-sm mt-1"
          style={{ fontFamily: `'${current}', sans-serif` }}
        >
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          {LABEL_EDITOR_FONT_OPTIONS.map((opt) => (
            <SelectItem
              key={opt.value}
              value={opt.value}
              style={{ fontFamily: `'${opt.value}', sans-serif` }}
            >
              {opt.label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}

function BiuStyleFields({
  cfg,
  update,
}: {
  cfg: Record<string, unknown>;
  update: (key: string, value: unknown) => void;
}) {
  const bold = readFontWeight(cfg) === 'bold';
  const italic = readFontStyle(cfg) === 'italic';
  const underline = readTextDecoration(cfg) === 'underline';
  const btnBase =
    'h-8 w-8 p-0 text-xs font-semibold shrink-0';
  return (
    <div>
      <Label className="text-xs">B.I.U</Label>
      <div className="mt-1 flex items-center gap-1">
        <Button
          type="button"
          variant={bold ? 'default' : 'outline'}
          size="sm"
          className={cn(btnBase, bold && 'bg-blue-600 hover:bg-blue-700')}
          onClick={() => update('fontWeight', bold ? 'normal' : 'bold')}
          title="Bold"
        >
          B
        </Button>
        <Button
          type="button"
          variant={italic ? 'default' : 'outline'}
          size="sm"
          className={cn(btnBase, italic && 'bg-blue-600 hover:bg-blue-700 italic')}
          onClick={() => update('fontStyle', italic ? 'normal' : 'italic')}
          title="Italic"
        >
          I
        </Button>
        <Button
          type="button"
          variant={underline ? 'default' : 'outline'}
          size="sm"
          className={cn(btnBase, underline && 'bg-blue-600 hover:bg-blue-700 underline')}
          onClick={() => update('textDecoration', underline ? 'none' : 'underline')}
          title="Underline"
        >
          U
        </Button>
      </div>
    </div>
  );
}

const DEMO_VALUE_HINT = 'Value entered here for demo only.';
const PRINT_PRETEXT_HINT =
  'Shown on the app print screen as a prompt only; not printed on the label.';

function TextStaticStyleFields({
  cfg,
  update,
  textAlignDefault,
  primaryTextLabel,
  primaryTextHint,
  hidePrimaryText = false,
}: {
  cfg: Record<string, unknown>;
  update: (key: string, value: unknown) => void;
  textAlignDefault: string;
  /** Template 面板静态文案在属性里称 Value；Entered When Printing 称 Pre-text */
  primaryTextLabel?: 'Text' | 'Value' | 'Pre-text';
  /** Value/Text 输入框下方提示（如 Price 演示值说明） */
  primaryTextHint?: string;
  /** Auto-generated 控件：文案由系统自动填充，隐藏 Text/Value 输入 */
  hidePrimaryText?: boolean;
}) {
  const textLabel = primaryTextLabel ?? 'Text';
  return (
    <>
      {!hidePrimaryText ? (
        <div>
          <Label className="text-xs">{textLabel}</Label>
          <Input
            value={(cfg.text as string) ?? '0.00'}
            onChange={(e) => update('text', e.target.value)}
            className="h-8 text-sm mt-1"
          />
          {primaryTextHint ? (
            <p className="text-[10px] text-gray-400 mt-1">{primaryTextHint}</p>
          ) : null}
        </div>
      ) : null}
      <FontFamilyField cfg={cfg} update={update} />
      <div>
        <Label className="text-xs">Font Size</Label>
        <Input
          type="number"
          value={(cfg.fontSize as number) ?? 14}
          onChange={(e) => update('fontSize', Number(e.target.value) || 14)}
          className="h-8 text-sm mt-1"
        />
      </div>
      <div>
        <Label className="text-xs">Text Align</Label>
        <Select
          value={(cfg.textAlign as string) ?? textAlignDefault}
          onValueChange={(v) => update('textAlign', v)}
        >
          <SelectTrigger className="h-8 text-sm mt-1">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="left">Left</SelectItem>
            <SelectItem value="center">Center</SelectItem>
            <SelectItem value="right">Right</SelectItem>
          </SelectContent>
        </Select>
      </div>
      <VerticalAlignField cfg={cfg} update={update} />
      <BiuStyleFields cfg={cfg} update={update} />
    </>
  );
}

function PreTextField({
  cfg,
  update,
}: {
  cfg: Record<string, unknown>;
  update: (key: string, value: unknown) => void;
}) {
  return (
    <div>
      <Label className="text-xs">Pre-text</Label>
      <Input
        value={(cfg.text as string) ?? ''}
        onChange={(e) => update('text', e.target.value)}
        className="h-8 text-sm mt-1"
        placeholder="e.g. Ingredients:"
      />
      <p className="text-[10px] text-gray-400 mt-1">{PRINT_PRETEXT_HINT}</p>
    </div>
  );
}

/** 读 config（兼容后端 PascalCase、数字以字符串下发） */
function cfgPickStr(cfg: Record<string, unknown>, keys: string[], fallback: string): string {
  for (const k of keys) {
    const v = cfg[k];
    if (v != null && String(v).trim() !== '') return String(v).trim();
  }
  return fallback;
}

function cfgPickNum(cfg: Record<string, unknown>, keys: string[], fallback: number): number {
  for (const k of keys) {
    const v = cfg[k];
    if (v == null || v === '') continue;
    const n = typeof v === 'number' ? v : Number(v);
    if (Number.isFinite(n)) return n;
  }
  return fallback;
}

const WEIGHT_UNIT_OPTIONS: Array<{ value: string; label: string }> = [
  { value: 'lb', label: 'Lb' },
  { value: 'kg', label: 'Kg' },
  { value: 'mg', label: 'Milligrams' },
  { value: 'g', label: 'Grams' },
  { value: 'oz', label: 'Ounces' },
];

function normalizeWeightUnit(raw: unknown): string {
  const unit = String(raw ?? '').trim().toLowerCase();
  if (unit === 'milligrams') return 'mg';
  if (unit === 'grams') return 'g';
  if (unit === 'ounces') return 'oz';
  if (unit === 'pounds') return 'lb';
  if (unit === 'kilograms') return 'kg';
  if (WEIGHT_UNIT_OPTIONS.some((item) => item.value === unit)) return unit;
  return 'g';
}

function nutritionExtraRows(cfg: Record<string, unknown>): NutritionExtraItem[] {
  const raw = cfg.extraNutrients;
  if (!Array.isArray(raw)) return [];
  return raw.map((item, idx) => {
    const row = item as Record<string, unknown>;
    return {
      id: String(row.id ?? `extra-${idx}`),
      name: String(row.name ?? ''),
      value: String(row.value ?? ''),
      unit: String(row.unit ?? ''),
    };
  });
}

function nutritionFixedField(
  cfg: Record<string, unknown>,
  key: string,
  field: 'value' | 'unit',
): string {
  const directKey = field === 'value' ? key : `${key}Unit`;
  const direct = cfg[directKey];
  if (direct != null && String(direct).trim() !== '') return String(direct).trim();
  const fixedRows = Array.isArray(cfg.fixedNutrients)
    ? (cfg.fixedNutrients as Record<string, unknown>[])
    : [];
  const row = fixedRows.find((item) => String(item.key ?? '').trim() === key);
  return String(row?.[field] ?? '').trim();
}

function ElementConfigFields({
  element,
  onChange,
}: {
  element: LabelElement;
  onChange: (config: Record<string, unknown>) => void;
}) {
  const cfg = element.config as Record<string, unknown>;
  const elementType = canonicalElementType(element.type);
  const update = (key: string, value: unknown) =>
    onChange({ [key]: value });
  const fromTemplatePalette = isTemplateSectionPersistedType(element);
  const fromLabelPalette = isLabelSectionPersistedType(element);
  const fromPrintPalette = isPrintInputSectionPersistedType(element);
  const staticTextLabel = fromTemplatePalette
    ? ('Value' as const)
    : fromPrintPalette
      ? ('Pre-text' as const)
      : ('Text' as const);
  const hidePrimaryText = isAutoGeneratedElement(element);
  const demoValueHint = !hidePrimaryText
    ? fromPrintPalette
      ? PRINT_PRETEXT_HINT
      : fromLabelPalette
        ? DEMO_VALUE_HINT
        : undefined
    : undefined;

  switch (elementType) {
    case 'TEXT_STATIC':
      if (cfg.inputType === 'options') {
        return (
          <>
            <MultipleOptionsDictionaryFields cfg={cfg} onPatch={onChange} />
            <TextStaticStyleFields
              cfg={cfg}
              update={update}
              textAlignDefault="left"
              primaryTextLabel={staticTextLabel}
              primaryTextHint={demoValueHint}
              hidePrimaryText={hidePrimaryText}
            />
          </>
        );
      }
      return (
        <TextStaticStyleFields
          cfg={cfg}
          update={update}
          textAlignDefault="right"
          primaryTextLabel={staticTextLabel}
          primaryTextHint={demoValueHint}
          hidePrimaryText={hidePrimaryText}
        />
      );
    case 'TEXT_PRODUCT':
      return (
        <TextStaticStyleFields
          cfg={cfg}
          update={update}
          textAlignDefault="right"
          primaryTextHint={demoValueHint}
          hidePrimaryText={hidePrimaryText}
        />
      );
    case 'TEXT_PRICE':
      return (
        <TextStaticStyleFields
          cfg={cfg}
          update={update}
          textAlignDefault="right"
          primaryTextLabel="Value"
          primaryTextHint={demoValueHint ?? DEMO_VALUE_HINT}
          hidePrimaryText={hidePrimaryText}
        />
      );
    case 'BARCODE':
      return (
        <>
          <div>
            <Label className="text-xs">Barcode Format</Label>
            <Select
              value={normalizeBarcodeType(cfg.barcodeType ?? DEFAULT_BARCODE_FORMAT)}
              onValueChange={(v) => update('barcodeType', v)}
            >
              <SelectTrigger className="h-8 text-sm mt-1">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {BARCODE_FORMAT_OPTIONS.map((opt) => (
                  <SelectItem key={opt.value} value={opt.value}>
                    {opt.label}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          <div>
            <Label className="text-xs">Data</Label>
            <Input
              value={(cfg.data as string) ?? '123456789'}
              onChange={(e) => update('data', e.target.value)}
              className="h-8 text-sm mt-1"
            />
          </div>
          <div className="grid grid-cols-2 gap-2">
            <div>
              <Label className="text-xs">Font size</Label>
              <Input
                type="number"
                min={8}
                max={72}
                value={(cfg.fontSize as number) ?? 14}
                onChange={(e) => update('fontSize', Number(e.target.value) || 14)}
                className="h-8 text-sm mt-1"
              />
            </div>
            <div>
              <Label className="text-xs">Text align</Label>
              <Select
                value={(cfg.textAlign as string) ?? 'center'}
                onValueChange={(v) => update('textAlign', v)}
              >
                <SelectTrigger className="h-8 text-sm mt-1">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="left">Left</SelectItem>
                  <SelectItem value="center">Center</SelectItem>
                  <SelectItem value="right">Right</SelectItem>
                </SelectContent>
              </Select>
            </div>
          </div>
          <p className="text-[10px] text-gray-400 -mt-1">
            Rotation and border use the common fields above.
          </p>
          <div className="flex items-center gap-2">
            <Switch
              checked={(cfg.showText as boolean) !== false}
              onCheckedChange={(v) => update('showText', v)}
            />
            <Label className="text-xs">Show Text</Label>
          </div>
        </>
      );
    case 'QRCODE':
      return (
        <div>
          <Label className="text-xs">Data (URL)</Label>
          <Input
            value={(cfg.data as string) ?? 'https://example.com'}
            onChange={(e) => update('data', e.target.value)}
            className="h-8 text-sm mt-1"
          />
        </div>
      );
    case 'IMAGE': {
      if (fromTemplatePalette) {
        const src = String(cfg.src ?? '').trim();
        return (
          <>
            <div>
              <Label className="text-xs">Image</Label>
              <ImageUrlUpload
                value={src}
                onChange={(url) => update('src', url)}
                uploadSubDir="label-template-editor"
                boxSizePx={TEMPLATE_IMAGE_UPLOAD_SIZE_PX}
              />
            </div>
            <div>
              <Label className="text-xs">Scale Mode</Label>
              <Select
                value={readImageScaleMode(cfg)}
                onValueChange={(v) => update('scaleMode', v)}
              >
                <SelectTrigger className="h-8 text-sm mt-1">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="contain">Contain</SelectItem>
                  <SelectItem value="cover">Cover</SelectItem>
                  <SelectItem value="fill">Fill</SelectItem>
                </SelectContent>
              </Select>
            </div>
          </>
        );
      }
      return (
        <>
          <div>
            <Label className="text-xs">Image URL / path</Label>
            <Input
              value={(cfg.src as string) ?? ''}
              onChange={(e) => update('src', e.target.value)}
              className="h-8 text-sm mt-1"
              placeholder="https://... or /picture/..."
            />
          </div>
          <div>
            <Label className="text-xs">Scale Mode</Label>
            <Select
              value={readImageScaleMode(cfg)}
              onValueChange={(v) => update('scaleMode', v)}
            >
              <SelectTrigger className="h-8 text-sm mt-1">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="contain">Contain</SelectItem>
                <SelectItem value="cover">Cover</SelectItem>
                <SelectItem value="fill">Fill</SelectItem>
              </SelectContent>
            </Select>
          </div>
        </>
      );
    }
    case 'DATE': {
      const inputTypeNorm = String(cfg.inputType ?? cfg.InputType ?? '').toLowerCase();
      const isPrintDate = inputTypeNorm === 'datetime' || inputTypeNorm === 'date';
      const dateFormat = cfgPickStr(
        cfg,
        ['format', 'Format'],
        inputTypeNorm === 'datetime' ? DATETIME_DEFAULT_FORMAT : 'DD/MM/YYYY',
      );
      const formatOptions =
        inputTypeNorm === 'datetime'
          ? [DATETIME_DEFAULT_FORMAT, ...DATE_FORMAT_OPTIONS]
          : [...DATE_FORMAT_OPTIONS];
      return (
        <>
          <div>
            <Label className="text-xs">Format</Label>
            <Select value={dateFormat} onValueChange={(v) => update('format', v)}>
              <SelectTrigger className="h-8 text-sm mt-1">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {formatOptions.map((fmt) => (
                  <SelectItem key={fmt} value={fmt}>
                    {fmt}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
            {isPrintDate ? (
              <p className="text-[10px] text-gray-400 mt-1">
                Shown as placeholder on the label until the app fills the date at print time.
              </p>
            ) : null}
          </div>
          {isPrintDate && fromPrintPalette ? <PreTextField cfg={cfg} update={update} /> : null}
          <FontFamilyField cfg={cfg} update={update} />
          <div>
            <Label className="text-xs">Font Size</Label>
            <Input
              type="number"
              value={cfgPickNum(cfg, ['fontSize', 'FontSize'], 14)}
              onChange={(e) => update('fontSize', Number(e.target.value) || 14)}
              className="h-8 text-sm mt-1"
            />
          </div>
          <div>
            <Label className="text-xs">Text Align</Label>
            <Select
              value={cfgPickStr(cfg, ['textAlign', 'TextAlign'], 'left')}
              onValueChange={(v) => update('textAlign', v)}
            >
              <SelectTrigger className="h-8 text-sm mt-1">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="left">Left</SelectItem>
                <SelectItem value="center">Center</SelectItem>
                <SelectItem value="right">Right</SelectItem>
              </SelectContent>
            </Select>
          </div>
          <VerticalAlignField cfg={cfg} update={update} />
          <BiuStyleFields cfg={cfg} update={update} />
        </>
      );
    }
    case 'TIME':
      const timeFormat = cfgPickStr(cfg, ['format', 'Format'], '24 hr');
      return (
        <>
          <div>
            <Label className="text-xs">Time Format</Label>
            <Select value={timeFormat === '12 hr' ? '12 hr' : '24 hr'} onValueChange={(v) => update('format', v)}>
              <SelectTrigger className="h-8 text-sm mt-1">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="12 hr">12 hr - 2:00pm</SelectItem>
                <SelectItem value="24 hr">24 hr - 14:00</SelectItem>
              </SelectContent>
            </Select>
          </div>
          <FontFamilyField cfg={cfg} update={update} />
          <div>
            <Label className="text-xs">Font Size</Label>
            <Input
              type="number"
              value={cfgPickNum(cfg, ['fontSize', 'FontSize'], 14)}
              onChange={(e) => update('fontSize', Number(e.target.value) || 14)}
              className="h-8 text-sm mt-1"
            />
          </div>
          <div>
            <Label className="text-xs">Text Align</Label>
            <Select
              value={cfgPickStr(cfg, ['textAlign', 'TextAlign'], 'left')}
              onValueChange={(v) => update('textAlign', v)}
            >
              <SelectTrigger className="h-8 text-sm mt-1">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="left">Left</SelectItem>
                <SelectItem value="center">Center</SelectItem>
                <SelectItem value="right">Right</SelectItem>
              </SelectContent>
            </Select>
          </div>
          <VerticalAlignField cfg={cfg} update={update} />
          <BiuStyleFields cfg={cfg} update={update} />
        </>
      );
    case 'DURATION':
      return (
        <>
          <div>
            <Label className="text-xs">Format</Label>
            <Select
              value={cfgPickStr(cfg, ['format', 'Format'], 'Days')}
              onValueChange={(v) => update('format', v)}
            >
              <SelectTrigger className="h-8 text-sm mt-1">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {DURATION_FORMAT_OPTIONS.map((fmt) => (
                  <SelectItem key={fmt} value={fmt}>
                    {fmt}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          <FontFamilyField cfg={cfg} update={update} />
          <div>
            <Label className="text-xs">Font Size</Label>
            <Input
              type="number"
              value={cfgPickNum(cfg, ['fontSize', 'FontSize'], 14)}
              onChange={(e) => update('fontSize', Number(e.target.value) || 14)}
              className="h-8 text-sm mt-1"
            />
          </div>
          <div>
            <Label className="text-xs">Text Align</Label>
            <Select
              value={cfgPickStr(cfg, ['textAlign', 'TextAlign'], 'left')}
              onValueChange={(v) => update('textAlign', v)}
            >
              <SelectTrigger className="h-8 text-sm mt-1">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="left">Left</SelectItem>
                <SelectItem value="center">Center</SelectItem>
                <SelectItem value="right">Right</SelectItem>
              </SelectContent>
            </Select>
          </div>
          <VerticalAlignField cfg={cfg} update={update} />
          <BiuStyleFields cfg={cfg} update={update} />
        </>
      );
    case 'WEIGHT':
      {
        const weightUnit = normalizeWeightUnit(cfgPickStr(cfg, ['unit', 'Unit'], 'g'));
        const textAlign = cfgPickStr(cfg, ['textAlign', 'TextAlign'], 'left');
        const fontSize = cfgPickNum(cfg, ['fontSize', 'FontSize'], 14);
        const weightInputMode = readWeightInputMode(cfg);
        const editorWeightInputMode = weightInputMode === 'tare' ? 'net' : weightInputMode;
        return (
          <>
            {fromPrintPalette ? <PreTextField cfg={cfg} update={update} /> : null}
            <div>
              <Label className="text-xs">Weight input mode</Label>
              <Select
                value={editorWeightInputMode}
                onValueChange={(v) => update('weightInputMode', v)}
              >
                <SelectTrigger className="h-8 text-sm mt-1">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {WEIGHT_INPUT_MODE_OPTIONS.map((item) => (
                    <SelectItem key={item.value} value={item.value} disabled={item.disabled}>
                      {item.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
            <div>
              <Label className="text-xs">Value</Label>
              <Input
                type="number"
                value={cfgPickNum(cfg, ['value', 'Value'], 500)}
                onChange={(e) => update('value', Number(e.target.value) || 0)}
                className="h-8 text-sm mt-1"
              />
              <p className="text-[10px] text-gray-400 mt-1">
                {fromPrintPalette ? DEMO_VALUE_HINT : 'Demo value for editor preview only. App users enter weight at print time.'}
              </p>
            </div>
            <div>
              <Label className="text-xs">Unit</Label>
              <Select
                value={weightUnit}
                onValueChange={(v) => update('unit', v)}
              >
                <SelectTrigger className="h-8 text-sm mt-1">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {WEIGHT_UNIT_OPTIONS.map((item) => (
                    <SelectItem key={item.value} value={item.value}>
                      {item.label}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
            <FontFamilyField cfg={cfg} update={update} />
            <div>
              <Label className="text-xs">Font Size</Label>
              <Input
                type="number"
                value={fontSize}
                onChange={(e) => update('fontSize', Math.max(1, Number(e.target.value) || 14))}
                className="h-8 text-sm mt-1"
              />
            </div>
            <div>
              <Label className="text-xs">Text Align</Label>
              <Select
                value={textAlign}
                onValueChange={(v) => update('textAlign', v)}
              >
                <SelectTrigger className="h-8 text-sm mt-1">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value="left">Left</SelectItem>
                  <SelectItem value="center">Center</SelectItem>
                  <SelectItem value="right">Right</SelectItem>
                </SelectContent>
              </Select>
            </div>
            <VerticalAlignField cfg={cfg} update={update} />
            <BiuStyleFields cfg={cfg} update={update} />
          </>
        );
      }
    case 'WEIGHT_PRICE':
      return (
        <>
          <div>
            <Label className="text-xs">Unit Price</Label>
            <Input
              type="number"
              value={(cfg.unitPrice as number) ?? 10}
              onChange={(e) => update('unitPrice', Number(e.target.value) || 0)}
              className="h-8 text-sm mt-1"
            />
          </div>
          <div>
            <Label className="text-xs">Weight</Label>
            <Input
              type="number"
              step="0.1"
              value={(cfg.weight as number) ?? 0.5}
              onChange={(e) => update('weight', Number(e.target.value) || 0)}
              className="h-8 text-sm mt-1"
            />
          </div>
          <div>
            <Label className="text-xs">Currency</Label>
            <Input
              value={(cfg.currency as string) ?? '$'}
              onChange={(e) => update('currency', e.target.value)}
              className="h-8 text-sm mt-1"
            />
          </div>
        </>
      );
    case 'NUTRITION':
      {
        const extraRows = nutritionExtraRows(cfg);
        const readLessThan = (key: string): boolean => {
          if (key === 'calories') return Boolean(cfg.caloriesLessThan);
          const baseFixed = Array.isArray(cfg.fixedNutrients)
            ? (cfg.fixedNutrients as Record<string, unknown>[])
            : [];
          const row = baseFixed.find((r) => String(r.key ?? '').trim() === key);
          return Boolean(row?.lessThan ?? cfg[`${key}LessThan`]);
        };
        const setLessThan = (key: string, lessThan: boolean) => {
          if (key === 'calories') {
            onChange({ ...cfg, caloriesLessThan: lessThan });
            return;
          }
          const baseFixed = Array.isArray(cfg.fixedNutrients)
            ? (cfg.fixedNutrients as Record<string, unknown>[])
            : [];
          const fixedRows = NUTRITION_FIXED_ITEMS.map((item) => {
            const baseRow = baseFixed.find((r) => String(r.key ?? '').trim() === item.key);
            const unit =
              nutritionFixedField(cfg, item.key, 'unit') || (item.defaultUnit ?? '');
            const nextLessThan = item.key === key ? lessThan : Boolean(baseRow?.lessThan ?? cfg[`${item.key}LessThan`]);
            return {
              key: item.key,
              label: item.label,
              value: String(baseRow?.value ?? cfg[item.key] ?? ''),
              unit,
              dailyValuePercent: String(
                baseRow?.dailyValuePercent ?? baseRow?.percent ?? cfg[`${item.key}Percent`] ?? '',
              ),
              lessThan: nextLessThan,
            };
          });
          const patch: Record<string, unknown> = { fixedNutrients: fixedRows, [`${key}LessThan`]: lessThan };
          for (const item of NUTRITION_FIXED_ITEMS) {
            const row = fixedRows.find((r) => r.key === item.key);
            patch[`${item.key}LessThan`] = Boolean(row?.lessThan);
          }
          onChange({ ...cfg, ...patch });
        };
        const applyFixedNutrientUnit = (key: string, nextUnit: string) => {
          const baseFixed = Array.isArray(cfg.fixedNutrients)
            ? (cfg.fixedNutrients as Record<string, unknown>[])
            : [];
          const previewValues = nutritionValuesFromElementConfig(cfg);
          const fixedRows = NUTRITION_FIXED_ITEMS.map((item) => {
            const baseRow = baseFixed.find((r) => String(r.key ?? '').trim() === item.key);
            const unit =
              nutritionFixedField(cfg, item.key, 'unit') || (item.defaultUnit ?? '');
            return {
              key: item.key,
              label: item.label,
              value: String(baseRow?.value ?? cfg[item.key] ?? ''),
              unit: item.key === key ? nextUnit : unit,
              dailyValuePercent: String(
                baseRow?.dailyValuePercent ?? baseRow?.percent ?? cfg[`${item.key}Percent`] ?? '',
              ),
              lessThan: Boolean(baseRow?.lessThan ?? cfg[`${item.key}LessThan`]),
            };
          });
          const keyPatch: Record<string, unknown> = { fixedNutrients: fixedRows };
          for (const item of NUTRITION_FIXED_ITEMS) {
            const row = fixedRows.find((r) => r.key === item.key);
            if (row?.value) keyPatch[item.key] = row.value;
            else keyPatch[item.key] = '';
            if (row?.unit) keyPatch[`${item.key}Unit`] = row.unit;
          }
          onChange(applyNutritionValuesToElementConfig({ ...cfg, ...keyPatch }, previewValues));
        };

        const addExtraNutrient = () => {
          const next: NutritionExtraItem[] = [
            ...extraRows,
            {
              id: `extra-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
              name: '',
              value: '',
              unit: '',
            },
          ];
          update('extraNutrients', next);
        };

        const updateExtraNutrient = (
          id: string,
          field: 'name' | 'unit',
          nextValue: string,
        ) => {
          const next = extraRows.map((item) =>
            item.id === id ? { ...item, [field]: nextValue, value: '' } : item,
          );
          update('extraNutrients', next);
        };

        const removeExtraNutrient = (id: string) => {
          update(
            'extraNutrients',
            extraRows.filter((item) => item.id !== id),
          );
        };

        return (
          <>
            <div>
              <Label className="text-xs">Nutrition Facts layout</Label>
              <div className="space-y-2 mt-1">
                <div className="grid grid-cols-[1fr_90px] gap-2 items-center">
                  <span className="text-xs text-gray-600">Nutrition Facts title (px)</span>
                  <Input
                    type="number"
                    value={cfgPickNum(cfg, ['nutritionTitleFontSize', 'NutritionTitleFontSize'], 16)}
                    onChange={(e) =>
                      update('nutritionTitleFontSize', Math.max(10, Number(e.target.value) || 16))
                    }
                    className="h-8 text-sm"
                  />
                </div>
              </div>
              {element.height < 240 ? (
                <p className="text-[10px] text-amber-600 mt-1">
                  Tip: increase element height to at least 280px to show the full Nutrition Facts panel.
                </p>
              ) : null}
            </div>
            <div className="rounded-md border border-gray-200 bg-gray-50/80 p-2.5">
              <Label className="text-xs font-semibold">Preview / sample data</Label>
              <NutritionManualEntryForm
                element={element}
                values={nutritionValuesFromElementConfig(cfg)}
                onFieldChange={(subKey, next) => {
                  const merged = applyNutritionValuesToElementConfig(cfg, {
                    ...nutritionValuesFromElementConfig(cfg),
                    [subKey]: next,
                  });
                  onChange(merged);
                }}
                className="space-y-3 mt-2"
              />
            </div>
            <div>
              <Label className="text-xs">Nutrition table structure</Label>
              <div className="space-y-1.5 mt-1">
                <div className="grid grid-cols-[1fr_58px_26px_26px] gap-1.5 items-center text-[10px] text-gray-500 px-0.5">
                  <span>Name</span>
                  <span>Unit</span>
                  <span className="text-center">&lt;</span>
                  <span />
                </div>
                <div className="grid grid-cols-[1fr_58px_26px_26px] gap-1.5 items-center">
                  <span className="text-xs text-gray-600">Calories</span>
                  <span />
                  <div className="flex justify-center">
                    <Checkbox
                      checked={readLessThan('calories')}
                      onCheckedChange={(v) => setLessThan('calories', v === true)}
                      aria-label="Calories less-than prefix"
                    />
                  </div>
                  <span />
                </div>
                {NUTRITION_FIXED_ITEMS.map((item) => (
                  <div key={item.key} className="grid grid-cols-[1fr_58px_26px_26px] gap-1.5 items-center">
                    <span className="text-xs text-gray-600">{item.label}</span>
                    <Input
                      value={nutritionFixedField(cfg, item.key, 'unit') || (item.defaultUnit ?? '')}
                      onChange={(e) => applyFixedNutrientUnit(item.key, e.target.value)}
                      className="h-8 text-sm"
                      placeholder="Unit"
                    />
                    <div className="flex justify-center">
                      <Checkbox
                        checked={readLessThan(item.key)}
                        onCheckedChange={(v) => setLessThan(item.key, v === true)}
                        aria-label={`${item.label} less-than prefix`}
                      />
                    </div>
                    <span />
                  </div>
                ))}
                {extraRows.map((row) => (
                  <div key={row.id} className="grid grid-cols-[1fr_58px_26px_26px] gap-1.5 items-center">
                    <Input
                      value={row.name}
                      onChange={(e) => updateExtraNutrient(row.id, 'name', e.target.value)}
                      className="h-8 text-sm"
                      placeholder="Nutrient name"
                    />
                    <Input
                      value={row.unit}
                      onChange={(e) => updateExtraNutrient(row.id, 'unit', e.target.value)}
                      className="h-8 text-sm"
                      placeholder="Unit"
                    />
                    <div className="flex justify-center">
                      <Checkbox
                        checked={Boolean(cfg[`extra:${row.id}:lessThan`])}
                        onCheckedChange={(v) =>
                          onChange({ ...cfg, [`extra:${row.id}:lessThan`]: v === true })
                        }
                        aria-label={`${row.name || 'Extra nutrient'} less-than prefix`}
                      />
                    </div>
                    <Button
                      type="button"
                      variant="ghost"
                      className="h-8 w-8 p-0 text-gray-500 hover:text-red-600"
                      onClick={() => removeExtraNutrient(row.id)}
                      aria-label="Delete nutrient"
                    >
                      <Trash2 className="h-3.5 w-3.5" />
                    </Button>
                  </div>
                ))}
              </div>
              <Button
                type="button"
                variant="outline"
                className="h-7 px-2 text-xs mt-2"
                onClick={addExtraNutrient}
              >
                Add Nutrient
              </Button>
              <div className="text-[10px] text-gray-400 mt-2">
                Reference unit only (not auto-appended). &lt; prefix applies when enabled here; label data only enters amount and %DV.
              </div>
            </div>
          </>
        );
      }
    case 'BLANK':
      return (
        <div className="text-xs text-gray-500">
          Blank spacer; no configuration needed.
        </div>
      );
    default:
      return (
        <div className="text-xs text-gray-500">
          Config for {elementType} (edit in code if needed)
        </div>
      );
  }
}
