import React, { useMemo, useState, useEffect, useRef } from 'react';
import { createPortal } from 'react-dom';
import { PaperConfig, LabelTemplate, RecordRow, PaperType, CutLineConfig, ExportRangeConfig } from '../types';
import {
  computeAutoLayoutPaper,
  DEFAULT_EXPORT_RANGE,
  DEFAULT_CUT_LINE_CONFIG,
  buildPrintablePages,
  collectGridCutLinePositions,
  getCutLineChannel,
  formatCutLineSvgDashArray,
  getCutLineStrokeWidthMm,
  loadAutoLayoutMinPageMarginMm,
  saveAutoLayoutMinPageMarginMm,
  type PrintableLabel,
} from '../utils';
import { CanvasLabelImage } from './CanvasLabelImage';
import { CommitInput } from './CommitInput';
import { useAppDialog } from './AppDialog';
import { FileDown, RefreshCcw, X } from 'lucide-react';

interface PageInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
  value: string | number;
  onCommit: (val: string) => void;
  inputClassName?: string;
}

export const PageInput: React.FC<PageInputProps> = ({
  value,
  onCommit,
  inputClassName = '',
  ...props
}) => (
  <CommitInput value={value} onCommit={onCommit} className={inputClassName} {...props} />
);

interface LayoutStats {
  gridCols: number;
  gridRows: number;
  labelsPerPage: number;
  totalPages: number;
  selectedCount: number;
}

interface PaperSettingsPanelProps {
  paper: PaperConfig;
  onChangePaper: (updated: PaperConfig) => void;
  template: LabelTemplate;
  rows?: RecordRow[];
  layoutStats?: LayoutStats;
  totalRowCount?: number;
  theme?: 'light' | 'ps' | 'mobile';
  exportRange?: ExportRangeConfig;
}

const paperEquals = (a: PaperConfig, b: PaperConfig) =>
  a.type === b.type &&
  a.width === b.width &&
  a.height === b.height &&
  a.orientation === b.orientation &&
  a.marginTop === b.marginTop &&
  a.marginRight === b.marginRight &&
  a.marginBottom === b.marginBottom &&
  a.marginLeft === b.marginLeft &&
  a.columnGap === b.columnGap &&
  a.rowGap === b.rowGap &&
  a.flow === b.flow;

export const PaperSettingsPanel: React.FC<PaperSettingsPanelProps> = ({
  paper,
  onChangePaper,
  template,
  rows = [],
  layoutStats,
  totalRowCount,
  theme = 'light',
  exportRange,
}) => {
  const { showAlert } = useAppDialog();
  const [draftPaper, setDraftPaper] = useState(paper);
  const [autoLayoutOpen, setAutoLayoutOpen] = useState(false);
  const [lastAutoLayoutMinMargin, setLastAutoLayoutMinMargin] = useState(
    () => loadAutoLayoutMinPageMarginMm()
  );
  const [autoLayoutMinMargin, setAutoLayoutMinMargin] = useState(
    () => String(loadAutoLayoutMinPageMarginMm())
  );
  const autoLayoutInputRef = useRef<HTMLInputElement>(null);
  const panelRef = React.useRef<HTMLDivElement>(null);

  useEffect(() => {
    setDraftPaper(paper);
  }, [paper]);

  const commitDraftIfChanged = () => {
    if (!paperEquals(draftPaper, paper)) {
      onChangePaper(draftPaper);
    }
  };

  const handlePanelBlur = (e: React.FocusEvent<HTMLDivElement>) => {
    const next = e.relatedTarget as Node | null;
    if (next && panelRef.current?.contains(next)) return;
    commitDraftIfChanged();
  };

  const isPs = theme === 'ps';
  const isMobile = theme === 'mobile';
  const fieldClass = isMobile
    ? 'mobile-field font-mono'
    : isPs
      ? 'ps-field font-mono'
      : 'w-full px-2.5 py-1.5 bg-gray-50 border border-gray-100 rounded-lg text-xs text-center font-mono';
  const selectClass = isMobile
    ? 'mobile-field'
    : isPs
      ? 'ps-field'
      : 'w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg font-medium text-gray-700 text-xs';
  const labelClass = isMobile
    ? 'mobile-field-label'
    : isPs
      ? 'ps-field-label'
      : 'block text-[10px] uppercase font-bold text-gray-400 mb-1';
  const sectionTitleClass = isMobile
    ? 'font-bold text-indigo-500 text-sm'
    : isPs
      ? 'ps-section-title'
      : 'font-bold text-[#31a8ff] text-xs';

  const rangeForStats = exportRange ?? DEFAULT_EXPORT_RANGE;
  const draftLayoutStats = useMemo(
    () => buildPrintablePages(rows, draftPaper, template, rangeForStats),
    [rows, draftPaper, template, rangeForStats]
  );

  const gridInfo = useMemo(() => {
    if (layoutStats && !exportRange) {
      return { cols: layoutStats.gridCols, rows: layoutStats.gridRows };
    }
    return { cols: draftLayoutStats.gridCols, rows: draftLayoutStats.gridRows };
  }, [layoutStats, exportRange, draftLayoutStats]);

  const labelsPerPage = layoutStats?.labelsPerPage ?? draftLayoutStats.labelsPerPage;
  const totalPages = layoutStats?.totalPages ?? draftLayoutStats.totalPages;
  const selectedCount = layoutStats?.selectedCount ?? draftLayoutStats.selectedCount;
  const allRowCount = totalRowCount ?? rows.length;

  const applyAutoLayoutWithMinMargin = (minMargin: number) => {
    const next = computeAutoLayoutPaper(draftPaper, template, minMargin);
    setDraftPaper(next);
    onChangePaper(next);
  };

  const closeAutoLayoutDialog = () => setAutoLayoutOpen(false);

  const openAutoLayoutDialog = () => {
    setAutoLayoutMinMargin(String(lastAutoLayoutMinMargin));
    setAutoLayoutOpen(true);
  };

  useEffect(() => {
    if (!autoLayoutOpen) return;
    const timer = window.setTimeout(() => autoLayoutInputRef.current?.focus(), 0);
    return () => window.clearTimeout(timer);
  }, [autoLayoutOpen]);

  useEffect(() => {
    if (!autoLayoutOpen) return;
    const onKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        e.preventDefault();
        closeAutoLayoutDialog();
      }
    };
    window.addEventListener('keydown', onKeyDown);
    return () => window.removeEventListener('keydown', onKeyDown);
  }, [autoLayoutOpen]);

  const confirmAutoLayout = async () => {
    const minMargin = Number(autoLayoutMinMargin);
    if (!Number.isFinite(minMargin) || minMargin < 0) {
      await showAlert('请输入不小于 0 的最小页边距（mm）。', { variant: 'warning' });
      return;
    }

    const paperW =
      draftPaper.orientation === 'portrait' ? draftPaper.width : draftPaper.height;
    const paperH =
      draftPaper.orientation === 'portrait' ? draftPaper.height : draftPaper.width;
    if (minMargin * 2 >= paperW || minMargin * 2 >= paperH) {
      await showAlert('最小页边距过大，当前纸张尺寸无法排版。', { variant: 'warning' });
      return;
    }

    applyAutoLayoutWithMinMargin(minMargin);
    saveAutoLayoutMinPageMarginMm(minMargin);
    setLastAutoLayoutMinMargin(minMargin);
    closeAutoLayoutDialog();
  };

  const applyPaper = (next: PaperConfig) => {
    setDraftPaper(next);
    onChangePaper(next);
  };

  const handlePaperTypeChange = (type: PaperType) => {
    let w = 210;
    let h = 297;
    if (type === 'A5') {
      w = 148;
      h = 210;
    } else if (type === 'A6') {
      w = 105;
      h = 148;
    } else if (type === 'custom') {
      w = draftPaper.width;
      h = draftPaper.height;
    }
    applyPaper({ ...draftPaper, type, width: w, height: h });
  };

  const patchDraftPaper = (patch: Partial<PaperConfig>) => {
    applyPaper({ ...draftPaper, ...patch });
  };

  const autoLayoutDialog =
    autoLayoutOpen && typeof document !== 'undefined'
      ? createPortal(
          <div
            className="ps-modal-overlay"
            onClick={closeAutoLayoutDialog}
            role="presentation"
          >
            <div
              className="ps-modal"
              onClick={(e) => e.stopPropagation()}
              role="dialog"
              aria-modal="true"
              aria-labelledby="auto-layout-dialog-title"
            >
              <div className="ps-modal-header">
                <h3 id="auto-layout-dialog-title" className="ps-modal-title">
                  自动排版
                </h3>
                <button
                  type="button"
                  onClick={closeAutoLayoutDialog}
                  className="ps-modal-close"
                  aria-label="关闭"
                >
                  <X className="w-3.5 h-3.5" />
                </button>
              </div>
              <form
                onSubmit={(e) => {
                  e.preventDefault();
                  void confirmAutoLayout();
                }}
              >
                <div className="ps-modal-body space-y-3">
                  <p className="text-[11px] text-[#888] leading-relaxed">
                    将根据当前纸张与标签尺寸，在不低于最小页边距的前提下，自动计算左右、上下对称边距并居中排版。
                  </p>
                  <div>
                    <label htmlFor="auto-layout-min-margin" className="ps-field-label">
                      最小页边距 (mm)
                    </label>
                    <input
                      id="auto-layout-min-margin"
                      ref={autoLayoutInputRef}
                      type="number"
                      min="0.1"
                      max="100"
                      step="0.1"
                      value={autoLayoutMinMargin}
                      onChange={(e) => setAutoLayoutMinMargin(e.target.value)}
                      className="ps-field font-mono"
                    />
                  </div>
                </div>
                <div className="ps-modal-footer">
                  <button type="button" onClick={closeAutoLayoutDialog} className="ps-btn text-[11px]">
                    取消
                  </button>
                  <button type="submit" className="ps-btn ps-btn-primary text-[11px]">
                    确定排版
                  </button>
                </div>
              </form>
            </div>
          </div>,
          document.body
        )
      : null;

  return (
    <>
    <div
      ref={panelRef}
      onBlur={isMobile ? undefined : handlePanelBlur}
      className={`space-y-4 text-xs ${isPs || isMobile ? '' : 'bg-white border border-gray-200 rounded-2xl p-6 shadow-sm'
        }`}
    >
      {!isPs && !isMobile && (
        <div className="flex items-center gap-2 mb-1">
          <h3 className="text-sm font-bold text-gray-900 leading-none">排版</h3>
        </div>
      )}
      {isPs && (
        <p className="text-[10px] text-[#666] leading-relaxed">
          排版参数修改后将自动更新预览。
        </p>
      )}

      <div className={isPs || isMobile ? 'space-y-4' : 'grid grid-cols-1 md:grid-cols-4 gap-6'}>
        <div className="space-y-2">
          <h4 className={sectionTitleClass}>纸张</h4>
          <div>
            <label className={labelClass}>纸张类型</label>
            <select
              value={draftPaper.type}
              onChange={(e) => handlePaperTypeChange(e.target.value as PaperType)}
              className={selectClass}
            >
              <option value="A4">A4 (210×297 mm)</option>
              <option value="A5">A5 (148×210 mm)</option>
              <option value="A6">A6 (105×148 mm)</option>
              <option value="custom">自定义尺寸</option>
            </select>
          </div>

          {draftPaper.type === 'custom' && (
            <div className="grid grid-cols-2 gap-2">
              <div>
                <label className={labelClass}>宽度 (mm)</label>
                <PageInput
                  type="number"
                  value={draftPaper.width}
                  onCommit={(val) => patchDraftPaper({ width: Math.max(10, Number(val) || 10) })}
                  inputClassName={fieldClass}
                />
              </div>
              <div>
                <label className={labelClass}>高度 (mm)</label>
                <PageInput
                  type="number"
                  value={draftPaper.height}
                  onCommit={(val) => patchDraftPaper({ height: Math.max(10, Number(val) || 10) })}
                  inputClassName={fieldClass}
                />
              </div>
            </div>
          )}

          <div>
            <label className={labelClass}>进纸方向</label>
            {isPs ? (
              <div className="ps-toggle-group">
                <button
                  type="button"
                  onClick={() => patchDraftPaper({ orientation: 'portrait' })}
                  className={`ps-toggle-btn flex-1 text-[10px] ${draftPaper.orientation === 'portrait' ? 'active' : ''}`}
                >
                  纵向
                </button>
                <button
                  type="button"
                  onClick={() => patchDraftPaper({ orientation: 'landscape' })}
                  className={`ps-toggle-btn flex-1 text-[10px] ${draftPaper.orientation === 'landscape' ? 'active' : ''}`}
                >
                  横向
                </button>
              </div>
            ) : isMobile ? (
              <div className="flex bg-slate-100 p-1 rounded-xl">
                <button
                  type="button"
                  onClick={() => patchDraftPaper({ orientation: 'portrait' })}
                  className={`flex-1 py-2.5 text-sm font-semibold rounded-lg transition ${draftPaper.orientation === 'portrait'
                    ? 'bg-white text-slate-800 shadow-sm'
                    : 'text-slate-500'
                    } cursor-pointer`}
                >
                  纵向
                </button>
                <button
                  type="button"
                  onClick={() => patchDraftPaper({ orientation: 'landscape' })}
                  className={`flex-1 py-2.5 text-sm font-semibold rounded-lg transition ${draftPaper.orientation === 'landscape'
                    ? 'bg-white text-slate-800 shadow-sm'
                    : 'text-slate-500'
                    } cursor-pointer`}
                >
                  横向
                </button>
              </div>
            ) : (
              <div className="flex bg-gray-100 p-0.5 rounded-lg">
                <button
                  type="button"
                  onClick={() => patchDraftPaper({ orientation: 'portrait' })}
                  className={`flex-1 py-1.5 text-[11px] font-semibold rounded ${draftPaper.orientation === 'portrait' ? 'bg-white text-gray-800 shadow-xs' : 'text-gray-500'
                    } cursor-pointer`}
                >
                  纵向
                </button>
                <button
                  type="button"
                  onClick={() => patchDraftPaper({ orientation: 'landscape' })}
                  className={`flex-1 py-1.5 text-[11px] font-semibold rounded ${draftPaper.orientation === 'landscape' ? 'bg-white text-gray-800 shadow-xs' : 'text-gray-500'
                    } cursor-pointer`}
                >
                  横向
                </button>
              </div>
            )}
          </div>
        </div>

        <div className="space-y-2">
          <h4 className={sectionTitleClass}>页边距 (mm)</h4>
          <div className="grid grid-cols-2 gap-2">
            {(
              [
                ['上边距', 'marginTop', draftPaper.marginTop],
                ['下边距', 'marginBottom', draftPaper.marginBottom],
                ['左边距', 'marginLeft', draftPaper.marginLeft],
                ['右边距', 'marginRight', draftPaper.marginRight],
              ] as const
            ).map(([label, key, val]) => (
              <div key={key}>
                <label className={labelClass}>{label}</label>
                <PageInput
                  type="number"
                  min="0"
                  max="100"
                  value={val}
                  onCommit={(v) => patchDraftPaper({ [key]: Number(v) || 0 })}
                  inputClassName={fieldClass}
                />
              </div>
            ))}
            <div className="col-span-2 flex flex-col items-end gap-0.5 -mt-0.5">
              <button
                type="button"
                onClick={openAutoLayoutDialog}
                title="设置最小页边距并自动居中排版"
                className={
                  isPs
                    ? 'text-[10px] text-[#777] hover:text-[#31a8ff] transition cursor-pointer'
                    : isMobile
                      ? 'text-xs text-slate-500 hover:text-indigo-600 font-normal py-1 cursor-pointer'
                      : 'text-[10px] text-gray-500 hover:text-indigo-600 font-normal cursor-pointer'
                }
              >
                自动排版
              </button>
              <span
                className={
                  isPs
                    ? 'text-[9px] text-[#666] leading-tight'
                    : isMobile
                      ? 'text-[10px] text-slate-400 leading-tight'
                      : 'text-[9px] text-gray-400 leading-tight'
                }
              >
                上次最小页边距 {lastAutoLayoutMinMargin} mm
              </span>
            </div>
          </div>
        </div>

        <div className="space-y-2">
          <h4 className={sectionTitleClass}>标签间距 (mm)</h4>
          <div className="grid grid-cols-2 gap-2">
            <div>
              <label className={labelClass}>横向</label>
              <PageInput
                type="number"
                step="0.5"
                min="0"
                max="50"
                value={draftPaper.columnGap}
                onCommit={(val) => patchDraftPaper({ columnGap: Number(val) || 0 })}
                inputClassName={fieldClass}
              />
            </div>
            <div>
              <label className={labelClass}>纵向</label>
              <PageInput
                type="number"
                step="0.5"
                min="0"
                max="50"
                value={draftPaper.rowGap}
                onCommit={(val) => patchDraftPaper({ rowGap: Number(val) || 0 })}
                inputClassName={fieldClass}
              />
            </div>
          </div>
          <div>
            <label className={labelClass}>填充顺序</label>
            <select
              value={draftPaper.flow}
              onChange={(e) => patchDraftPaper({ flow: e.target.value as PaperConfig['flow'] })}
              className={selectClass}
            >
              <option value="left-right-top-bottom">左右 → 上下（行优先）</option>
              <option value="top-bottom-left-right">上下 → 左右（列优先）</option>
            </select>
          </div>
        </div>

      </div>
    </div>
    {autoLayoutDialog}
    </>
  );
};

export interface LayoutPreviewProps {
  paper: PaperConfig;
  template: LabelTemplate;
  printablePages: (PrintableLabel | null)[][];
  gridCols: number;
  labelsPerPage: number;
  totalPages: number;
  selectedCount: number;
  totalRowCount: number;
  /** 未刷新前根据当前配置计算的页数/枚数（用于工具栏提示） */
  draftTotalPages: number;
  draftSelectedCount: number;
  previewReady: boolean;
  previewLayoutStale: boolean;
  cutLine: CutLineConfig;
  activeRowIndex: number;
  onSelectRowIndex: (idx: number) => void;
  compact?: boolean;
  collapsed?: boolean;
  onToggleCollapsed?: () => void;
  variant?: 'desktop' | 'mobile';
}

export const LayoutPreview: React.FC<LayoutPreviewProps> = ({
  paper,
  template,
  printablePages,
  gridCols,
  labelsPerPage,
  totalPages,
  selectedCount,
  totalRowCount,
  draftTotalPages,
  draftSelectedCount,
  previewReady,
  previewLayoutStale,
  cutLine,
  activeRowIndex,
  onSelectRowIndex,
  compact = false,
  collapsed = false,
  onToggleCollapsed,
  variant = 'desktop',
}) => {
  const isMobileVariant = variant === 'mobile';
  const [previewScale, setPreviewScale] = useState<number>(
    isMobileVariant ? 1.15 : compact ? 1.4 : 3.3
  );
  const [showAllPages, setShowAllPages] = useState(false);
  const MAX_PREVIEW_PAGES = 3;

  const gridInfo = useMemo(
    () => ({ cols: gridCols, rows: Math.ceil(labelsPerPage / gridCols) || 1 }),
    [gridCols, labelsPerPage]
  );

  const currentPaperW = paper.orientation === 'portrait' ? paper.width : paper.height;
  const currentPaperH = paper.orientation === 'portrait' ? paper.height : paper.width;
  const colGapPx = Math.max(0, paper.columnGap * previewScale);
  const rowGapPx = Math.max(0, paper.rowGap * previewScale);
  const labelWPx = template.width * previewScale;
  const labelHPx = template.height * previewScale;
  const cutStrokeWidth = getCutLineStrokeWidthMm(cutLine) * previewScale;
  const cutHalfStroke = cutStrokeWidth / 2;
  const cutChannel = getCutLineChannel(paper.columnGap, paper.rowGap, cutLine);
  const cutChannelXPx = cutChannel.x * previewScale;
  const cutChannelYPx = cutChannel.y * previewScale;
  const cutDash = formatCutLineSvgDashArray(cutLine, previewScale);
  const pageCutLines = useMemo(
    () =>
      collectGridCutLinePositions(
        0,
        0,
        labelWPx,
        labelHPx,
        gridInfo.cols,
        gridInfo.rows,
        colGapPx,
        rowGapPx
      ),
    [labelWPx, labelHPx, gridInfo.cols, gridInfo.rows, colGapPx, rowGapPx]
  );
  const visiblePages = showAllPages ? printablePages : printablePages.slice(0, MAX_PREVIEW_PAGES);
  const hasMorePages = printablePages.length > MAX_PREVIEW_PAGES;

  return (
    <div
      className={`flex-1 flex flex-col min-h-0 min-w-0 layout-preview-panel ${compact && collapsed ? 'layout-preview-collapsed' : ''
        }`}
    >
      <div className={`ps-preview-toolbar ${isMobileVariant ? 'mobile-preview-toolbar' : ''}`}>
        <div className="flex items-center gap-2 text-[11px] text-[#aaa] min-w-0 flex-1">
          <FileDown className="w-3.5 h-3.5 text-[#31a8ff] shrink-0" />
          <span className="font-semibold text-[#e8e8e8]">排版预览</span>
          <span className="text-[#666]">|</span>
          <span className="truncate">
            {previewReady ? totalPages : draftTotalPages} 页
          </span>
          {!isMobileVariant && (
            <>
              <span className="text-[#666]">|</span>
              <span>
                {(previewReady ? selectedCount : draftSelectedCount) < totalRowCount
                  ? `${previewReady ? selectedCount : draftSelectedCount}/${totalRowCount} 行`
                  : `${totalRowCount} 行`}
              </span>
            </>
          )}
          {previewLayoutStale && previewReady && (
            <span className="text-amber-500/90 shrink-0">待更新</span>
          )}
          {previewReady && hasMorePages && !showAllPages && !isMobileVariant && (
            <span className="text-amber-500/90">预览前 {MAX_PREVIEW_PAGES} 页</span>
          )}
        </div>

        <div className={`flex items-center gap-3 shrink-0 ${isMobileVariant ? 'hidden' : ''}`}>
          {compact && onToggleCollapsed && (
            <button
              type="button"
              onClick={onToggleCollapsed}
              className="ps-btn text-[10px] px-2 py-1"
            >
              {collapsed ? '展开' : '收起'}
            </button>
          )}
          <div className={`flex items-center gap-2 mobile-preview-zoom ${compact ? 'hidden' : ''}`}>
            <span className="text-[10px] text-[#777]">预览比例</span>
            <button
              type="button"
              onClick={() => setPreviewScale(Math.max(1.0, Math.round((previewScale - 0.4) * 10) / 10))}
              disabled={previewScale <= 1.0}
              className="ps-preview-zoom-btn"
            >
              -
            </button>
            <span className="text-[10px] font-mono text-[#31a8ff] min-w-[40px] text-center">
              {Math.round((previewScale / 3.3) * 100)}%
            </span>
            <button
              type="button"
              onClick={() => setPreviewScale(Math.min(5.0, Math.round((previewScale + 0.4) * 10) / 10))}
              disabled={previewScale >= 5.0}
              className="ps-preview-zoom-btn"
            >
              +
            </button>
            <button
              type="button"
              onClick={() => setPreviewScale(2.0)}
              className={`ps-preview-preset-btn ${previewScale === 2.0 ? 'active' : ''}`}
            >
              适中
            </button>
            <button
              type="button"
              onClick={() => setPreviewScale(3.3)}
              className={`ps-preview-preset-btn ${previewScale === 3.3 ? 'active' : ''}`}
            >
              100%
            </button>
          </div>
          {hasMorePages && (
            <button
              type="button"
              onClick={() => setShowAllPages((v) => !v)}
              className="ps-btn text-[10px]"
            >
              {showAllPages ? '收起预览' : `展开全部 ${totalPages} 页`}
            </button>
          )}
        </div>
      </div>

      <div
        className={`flex-1 overflow-auto ps-workspace-bg min-h-0 ps-preview-workspace ${
          isMobileVariant ? 'p-3 mobile-preview-workspace' : 'p-6'
        }`}
      >
        {!previewReady ? (
          <div className="h-full flex flex-col items-center justify-center text-center px-6 gap-2">
            <p className="text-sm text-[#aaa]">上传数据源并完成变量映射后</p>
            <p className="text-xs text-[#666] max-w-[280px] leading-relaxed">
              {isMobileVariant
                ? '在下方「数据源」中上传 Excel，预览将自动更新'
                : '在右侧「数据源」中点击「应用数据」生成排版预览；纸张排版变更会自动更新'}
            </p>
          </div>
        ) : (
          <div
            className={`flex flex-col items-center ${
              isMobileVariant ? 'space-y-4 pb-4' : 'space-y-8 pb-8'
            }`}
          >
            {visiblePages.map((pageRows, pageIdx) => (
              <div
                key={pageIdx}
                className="bg-white rounded shadow-2xl overflow-hidden relative border border-[#222] flex flex-col shrink-0"
                style={{
                  width: `${currentPaperW * previewScale}px`,
                }}
              >
                <div className="bg-[#f0f0f0] border-b border-gray-200 py-1.5 px-3 flex justify-between items-center text-[10px] font-semibold text-gray-500 select-none shrink-0">
                  <span>第 {pageIdx + 1} 页 / 共 {totalPages} 页</span>
                  <span>
                    {currentPaperW} × {currentPaperH} mm · {paper.orientation === 'portrait' ? '纵向' : '横向'}
                  </span>
                </div>
                <div
                  className="bg-white flex items-start justify-start relative select-none overflow-hidden shrink-0"
                  style={{
                    width: `${currentPaperW * previewScale}px`,
                    height: `${currentPaperH * previewScale}px`,
                    padding: `${paper.marginTop * previewScale}px ${paper.marginRight * previewScale}px ${paper.marginBottom * previewScale}px ${paper.marginLeft * previewScale}px`,
                    boxSizing: 'border-box',
                  }}
                >
                  <div
                    className="grid bg-white relative"
                    style={{
                      gridTemplateColumns: `repeat(${gridInfo.cols}, ${labelWPx}px)`,
                      gridAutoRows: `${labelHPx}px`,
                      columnGap: `${colGapPx}px`,
                      rowGap: `${rowGapPx}px`,
                      width: 'max-content',
                    }}
                  >
                    {cutLine.enabled && (
                      <svg
                        className="absolute pointer-events-none z-20"
                        style={{
                          left: -cutHalfStroke,
                          top: -cutHalfStroke,
                          width: pageCutLines.gridW + cutStrokeWidth,
                          height: pageCutLines.gridH + cutStrokeWidth,
                          overflow: 'visible',
                        }}
                        viewBox={`${-cutHalfStroke} ${-cutHalfStroke} ${pageCutLines.gridW + cutStrokeWidth} ${pageCutLines.gridH + cutStrokeWidth}`}
                        aria-hidden
                      >
                        {pageCutLines.ys.map((y) => (
                          <line
                            key={`h-${y}`}
                            x1={0}
                            y1={y}
                            x2={pageCutLines.gridW}
                            y2={y}
                            stroke={cutLine.color}
                            strokeWidth={cutStrokeWidth}
                            strokeDasharray={cutDash}
                            strokeLinecap="butt"
                          />
                        ))}
                        {pageCutLines.xs.map((x) => (
                          <line
                            key={`v-${x}`}
                            x1={x}
                            y1={0}
                            x2={x}
                            y2={pageCutLines.gridH}
                            stroke={cutLine.color}
                            strokeWidth={cutStrokeWidth}
                            strokeDasharray={cutDash}
                            strokeLinecap="butt"
                          />
                        ))}
                      </svg>
                    )}
                    {pageRows.map((item, cellIdx) => {
                      if (!item) {
                        return (
                          <div
                            key={cellIdx}
                            className="bg-white flex items-center justify-center text-gray-300 font-mono text-[9px]"
                            style={{
                              width: `${template.width * previewScale}px`,
                              height: `${template.height * previewScale}px`,
                            }}
                          >
                            空白
                          </div>
                        );
                      }
                      const isActive = item.sourceIndex === activeRowIndex;
                      return (
                        <div
                          key={cellIdx}
                          onClick={() => onSelectRowIndex(item.sourceIndex)}
                          className={`bg-white text-center relative flex flex-col justify-center cursor-pointer box-border ${isActive ? 'ring-2 ring-inset ring-[#31a8ff] z-10' : ''
                            }`}
                          style={{
                            width: `${labelWPx}px`,
                            height: `${labelHPx}px`,
                            padding: `${cutChannelYPx}px ${cutChannelXPx}px`,
                            boxSizing: 'border-box',
                          }}
                        >
                          <div className="w-full h-full overflow-hidden relative">
                            <CanvasLabelImage
                              widthMm={template.width}
                              heightMm={template.height}
                              elements={template.elements}
                              rowData={item.row}
                              rowIndex={item.sourceIndex}
                              showBorder={false}
                              mode="output"
                            />
                          </div>
                        </div>
                      );
                    })}
                  </div>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

      <div className={`ps-status-bar ${isMobileVariant ? 'mobile-preview-status' : ''}`}>
        <span>
          {previewReady
            ? `${gridInfo.cols}×${gridInfo.rows} · 每页 ${labelsPerPage} 枚`
            : `预计 ${draftTotalPages} 页`}
        </span>
        {!isMobileVariant && (
          <span>
            {previewReady ? '点击标签可高亮对应数据行' : '数据变更后请在右侧面板手动刷新绘制'}
          </span>
        )}
      </div>
    </div>
  );
};

/** @deprecated 使用 ExportSidebar + LayoutPreview 组合 */
export interface PreviewGridProps extends LayoutPreviewProps {
  paper: PaperConfig;
  onChangePaper: (updated: PaperConfig) => void;
}

export const PreviewGrid: React.FC<PreviewGridProps> = ({
  paper,
  onChangePaper,
  template,
  rows,
  activeRowIndex,
  onSelectRowIndex,
}) => {
  const layout = buildPrintablePages(rows, paper, template, DEFAULT_EXPORT_RANGE);
  return (
    <div className="space-y-6">
      <PaperSettingsPanel
        paper={paper}
        onChangePaper={onChangePaper}
        template={template}
        layoutStats={layout}
        totalRowCount={rows.length}
      />
      <LayoutPreview
        paper={paper}
        template={template}
        printablePages={layout.pages}
        gridCols={layout.gridCols}
        labelsPerPage={layout.labelsPerPage}
        totalPages={layout.totalPages}
        selectedCount={layout.selectedCount}
        totalRowCount={rows.length}
        draftTotalPages={layout.totalPages}
        draftSelectedCount={layout.selectedCount}
        previewReady
        previewLayoutStale={false}
        cutLine={template.cutLine ?? DEFAULT_CUT_LINE_CONFIG}
        activeRowIndex={activeRowIndex}
        onSelectRowIndex={onSelectRowIndex}
      />
    </div>
  );
};
