@extends('layouts.app')

@section('title', 'SAM E-Sign - 템플릿 필드 편집')

@section('content')
<meta name="csrf-token" content="{{ csrf_token() }}">
<div id="esign-template-fields-root" data-template-id="{{ $templateId }}"></div>
@endsection

@push('scripts')
@include('partials.react-cdn')
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script>pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';</script>
<script src="https://cdn.jsdelivr.net/npm/interactjs/dist/interact.min.js"></script>
@verbatim
<script type="text/babel">
const { useState, useEffect, useCallback, useRef, useMemo } = React;

const TEMPLATE_ID = document.getElementById('esign-template-fields-root')?.dataset.templateId;
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || '';

const getHeaders = () => ({
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content || csrfToken,
});

const FIELD_TYPES = [
    { value: 'signature', label: '서명', icon: '✍' },
    { value: 'stamp', label: '도장', icon: '📌' },
    { value: 'text', label: '텍스트', icon: 'T' },
    { value: 'date', label: '날짜', icon: '📅' },
    { value: 'checkbox', label: '체크박스', icon: '☑' },
];

const SIGNER_COLORS = ['#3B82F6', '#EF4444', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899'];
const SIGNER_LABELS = ['서명자 1', '서명자 2', '서명자 3', '서명자 4', '서명자 5', '서명자 6'];

const SYSTEM_VARIABLES = [
    { key: 'signer1_name', label: '갑(1) 이름' },
    { key: 'signer2_name', label: '을(2) 이름' },
    { key: 'signer1_email', label: '갑(1) 이메일' },
    { key: 'signer2_email', label: '을(2) 이메일' },
    { key: 'contract_title', label: '계약 제목' },
    { key: 'current_date', label: '계약일 (오늘)' },
    { key: 'expires_at', label: '만료일' },
];

const ZOOM_LEVELS = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0];
const MAX_HISTORY = 50;

// ─── Toolbar ───
const Toolbar = ({ zoom, setZoom, gridEnabled, setGridEnabled, undo, redo, canUndo, canRedo, saving, saveFields, goBack }) => (
    <div className="flex items-center justify-between px-4 py-2 bg-white border-b shadow-sm">
        <div className="flex items-center gap-3">
            <button onClick={goBack} className="text-gray-500 hover:text-gray-800 text-lg px-2" title="뒤로가기">&larr;</button>
            <span className="font-semibold text-gray-800 text-sm">템플릿 필드 편집</span>
        </div>
        <div className="flex items-center gap-1">
            <button onClick={() => { const i = ZOOM_LEVELS.indexOf(zoom); if (i > 0) setZoom(ZOOM_LEVELS[i-1]); }}
                    disabled={zoom <= ZOOM_LEVELS[0]}
                    className="px-2 py-1 text-sm border rounded hover:bg-gray-50 disabled:opacity-30" title="축소">−</button>
            <span className="px-2 py-1 text-xs font-mono min-w-[50px] text-center">{Math.round(zoom * 100)}%</span>
            <button onClick={() => { const i = ZOOM_LEVELS.indexOf(zoom); if (i < ZOOM_LEVELS.length - 1) setZoom(ZOOM_LEVELS[i+1]); }}
                    disabled={zoom >= ZOOM_LEVELS[ZOOM_LEVELS.length - 1]}
                    className="px-2 py-1 text-sm border rounded hover:bg-gray-50 disabled:opacity-30" title="확대">+</button>
            <div className="w-px h-5 bg-gray-200 mx-1"></div>
            <button onClick={() => setGridEnabled(g => !g)}
                    className={`px-2 py-1 text-sm border rounded ${gridEnabled ? 'bg-blue-50 border-blue-300 text-blue-600' : 'hover:bg-gray-50'}`}
                    title="그리드 스냅">▦</button>
            <div className="w-px h-5 bg-gray-200 mx-1"></div>
            <button onClick={undo} disabled={!canUndo} className="px-2 py-1 text-sm border rounded hover:bg-gray-50 disabled:opacity-30" title="되돌리기 (Ctrl+Z)">↩</button>
            <button onClick={redo} disabled={!canRedo} className="px-2 py-1 text-sm border rounded hover:bg-gray-50 disabled:opacity-30" title="다시실행 (Ctrl+Shift+Z)">↪</button>
        </div>
        <div className="flex items-center gap-2">
            <button onClick={saveFields} disabled={saving}
                    className="px-5 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 text-sm font-medium disabled:opacity-50 transition-colors">
                {saving ? '저장 중...' : '저장'}
            </button>
        </div>
    </div>
);

// ─── ThumbnailSidebar ───
const ThumbnailSidebar = ({ pdfDoc, totalPages, currentPage, setCurrentPage, fields }) => {
    const canvasRefs = useRef({});
    const [rendered, setRendered] = useState({});

    useEffect(() => {
        if (!pdfDoc) return;
        const renderThumbs = async () => {
            for (let i = 1; i <= totalPages; i++) {
                const canvas = canvasRefs.current[i];
                if (!canvas || rendered[i]) continue;
                const page = await pdfDoc.getPage(i);
                const vp = page.getViewport({ scale: 0.15 });
                canvas.width = vp.width;
                canvas.height = vp.height;
                await page.render({ canvasContext: canvas.getContext('2d'), viewport: vp }).promise;
                setRendered(prev => ({ ...prev, [i]: true }));
            }
        };
        renderThumbs();
    }, [pdfDoc, totalPages]);

    const fieldCountByPage = useMemo(() => {
        const counts = {};
        fields.forEach(f => { counts[f.page_number] = (counts[f.page_number] || 0) + 1; });
        return counts;
    }, [fields]);

    return (
        <div className="w-20 bg-gray-50 border-r overflow-y-auto flex-shrink-0" style={{ height: 'calc(100vh - 105px)' }}>
            <div className="p-2 space-y-2">
                {Array.from({ length: totalPages }, (_, i) => i + 1).map(p => (
                    <button key={p} onClick={() => setCurrentPage(p)}
                            className={`w-full relative rounded border-2 transition-all ${p === currentPage ? 'border-blue-500 shadow-md' : 'border-transparent hover:border-gray-300'}`}>
                        <canvas ref={el => canvasRefs.current[p] = el} className="w-full rounded" />
                        <span className="absolute bottom-0 left-0 right-0 bg-black/50 text-white text-[10px] text-center py-0.5 rounded-b">{p}</span>
                        {fieldCountByPage[p] > 0 && (
                            <span className="absolute -top-1 -right-1 bg-blue-500 text-white text-[9px] w-4 h-4 rounded-full flex items-center justify-center font-bold">
                                {fieldCountByPage[p]}
                            </span>
                        )}
                    </button>
                ))}
            </div>
        </div>
    );
};

// ─── FieldOverlay (interact.js) ───
// 기본: 점선 + 드래그=이동 / 더블클릭: 실선 + 핸들=크기조절 / ESC: 모드해제
const FieldOverlay = ({ field, index, selected, signerCount, containerRef, onSelect, onUpdate, onDragEnd, gridEnabled, selectedIndices, onGroupMove }) => {
    const fieldRef = useRef(null);
    const [resizeMode, setResizeMode] = useState(false);
    const draggedRef = useRef(false);
    const latestRef = useRef({ field, index, onUpdate, onDragEnd, selectedIndices, onGroupMove });
    latestRef.current = { field, index, onUpdate, onDragEnd, selectedIndices, onGroupMove };

    const signerIdx = field.signer_order - 1;
    const color = SIGNER_COLORS[signerIdx % SIGNER_COLORS.length] || '#888';
    const signerName = SIGNER_LABELS[signerIdx] || `서명자 ${field.signer_order}`;
    const typeInfo = FIELD_TYPES.find(t => t.value === field.field_type) || FIELD_TYPES[0];

    // 선택 해제 시 리사이즈 모드 자동 해제
    useEffect(() => { if (!selected) setResizeMode(false); }, [selected]);

    // ESC 키로 리사이즈 모드 해제
    useEffect(() => {
        if (!resizeMode) return;
        const h = (e) => { if (e.key === 'Escape') { e.stopPropagation(); setResizeMode(false); } };
        window.addEventListener('keydown', h);
        return () => window.removeEventListener('keydown', h);
    }, [resizeMode]);

    // interact.js: resizeMode에 따라 드래그 또는 리사이즈만 활성화
    useEffect(() => {
        const el = fieldRef.current;
        if (!el) return;

        const modifiers = [
            interact.modifiers.restrictRect({ restriction: 'parent', endOnly: false }),
        ];
        if (gridEnabled) {
            modifiers.push(interact.modifiers.snap({
                targets: [interact.snappers.grid({ x: 1, y: 1 })],
                range: 10, relativePoints: [{ x: 0, y: 0 }],
            }));
        }

        const interactable = interact(el);

        if (resizeMode) {
            interactable
                .draggable({ enabled: false })
                .resizable({
                    edges: { left: true, right: true, top: true, bottom: true },
                    modifiers: [
                        interact.modifiers.restrictEdges({ outer: 'parent' }),
                        interact.modifiers.restrictSize({ min: { width: 30, height: 20 } }),
                    ],
                    listeners: {
                        move(event) {
                            const { field: f, index: idx, onUpdate: update } = latestRef.current;
                            const rect = containerRef.current.getBoundingClientRect();
                            const newX = f.position_x + (event.deltaRect.left / rect.width) * 100;
                            const newY = f.position_y + (event.deltaRect.top / rect.height) * 100;
                            const newW = (event.rect.width / rect.width) * 100;
                            const newH = (event.rect.height / rect.height) * 100;
                            update(idx, {
                                position_x: round2(Math.max(0, newX)),
                                position_y: round2(Math.max(0, newY)),
                                width: round2(Math.max(2, newW)),
                                height: round2(Math.max(1, newH)),
                            });
                        },
                        end() { latestRef.current.onDragEnd(); },
                    },
                });
        } else {
            interactable
                .resizable({ enabled: false })
                .draggable({
                    inertia: false,
                    modifiers,
                    listeners: {
                        move(event) {
                            draggedRef.current = true;
                            const { field: f, index: idx, onUpdate: update, selectedIndices: si, onGroupMove: groupMove } = latestRef.current;
                            const rect = containerRef.current.getBoundingClientRect();
                            const dx = (event.dx / rect.width) * 100;
                            const dy = (event.dy / rect.height) * 100;
                            if (si && si.size > 1 && si.has(idx) && groupMove) {
                                groupMove(dx, dy);
                            } else {
                                const newX = Math.max(0, Math.min(100 - f.width, f.position_x + dx));
                                const newY = Math.max(0, Math.min(100 - f.height, f.position_y + dy));
                                update(idx, { position_x: round2(newX), position_y: round2(newY) });
                            }
                        },
                        end() {
                            latestRef.current.onDragEnd();
                            setTimeout(() => { draggedRef.current = false; }, 0);
                        },
                    },
                });
        }

        return () => interactable.unset();
    }, [gridEnabled, resizeMode]);

    return (
        <div ref={fieldRef}
             onClick={(e) => { e.stopPropagation(); if (draggedRef.current) return; onSelect(index, e); }}
             onDoubleClick={(e) => { e.stopPropagation(); onSelect(index, e); setResizeMode(prev => !prev); }}
             style={{
                 position: 'absolute',
                 left: `${field.position_x}%`, top: `${field.position_y}%`,
                 width: `${field.width}%`, height: `${field.height}%`,
                 border: resizeMode ? `2px solid ${color}` : `2px dashed ${color}`,
                 backgroundColor: resizeMode ? `${color}30` : (selected ? `${color}20` : `${color}10`),
                 cursor: resizeMode ? 'default' : 'move',
                 borderRadius: '4px',
                 zIndex: selected ? 20 : 10,
                 touchAction: 'none',
                 boxShadow: resizeMode ? `0 0 0 2px ${color}, 0 2px 12px rgba(0,0,0,0.2)` : (selected ? `0 0 0 1px ${color}, 0 2px 8px rgba(0,0,0,0.15)` : 'none'),
             }}
             className={`flex items-center select-none group transition-shadow ${{'L':'justify-start','C':'justify-center','R':'justify-end'}[field.text_align] || 'justify-start'}`}>
            <div className="flex items-center gap-0.5 text-[10px] font-medium truncate px-1" style={{ color }}>
                <span>{typeInfo.icon}</span>
                <span className="truncate">{field.field_variable ? `{{${field.field_variable}}}` : (field.field_label || signerName)}</span>
            </div>
            {/* 리사이즈 모드 안내 툴팁 */}
            {resizeMode && (
                <div style={{ position: 'absolute', top: -20, left: 0, fontSize: 9, backgroundColor: '#374151', color: 'white', padding: '1px 6px', borderRadius: 3, whiteSpace: 'nowrap', zIndex: 40, lineHeight: '16px' }}>
                    크기 조절 · ESC 해제
                </div>
            )}
            {/* 리사이즈 핸들 (리사이즈 모드에서만 표시) */}
            {resizeMode && <>
                {['nw','n','ne','w','e','sw','s','se'].map(dir => {
                    const styles = {
                        nw: { top: -5, left: -5, cursor: 'nw-resize' },
                        n:  { top: -5, left: '50%', transform: 'translateX(-50%)', cursor: 'n-resize' },
                        ne: { top: -5, right: -5, cursor: 'ne-resize' },
                        w:  { top: '50%', left: -5, transform: 'translateY(-50%)', cursor: 'w-resize' },
                        e:  { top: '50%', right: -5, transform: 'translateY(-50%)', cursor: 'e-resize' },
                        sw: { bottom: -5, left: -5, cursor: 'sw-resize' },
                        s:  { bottom: -5, left: '50%', transform: 'translateX(-50%)', cursor: 's-resize' },
                        se: { bottom: -5, right: -5, cursor: 'se-resize' },
                    };
                    return (
                        <div key={dir} style={{
                            ...styles[dir], position: 'absolute',
                            width: 10, height: 10, backgroundColor: 'white',
                            border: `2px solid ${color}`, borderRadius: 2,
                            zIndex: 30,
                        }} />
                    );
                })}
            </>}
        </div>
    );
};

// ─── PropertiesPanel (템플릿용: signer_order 기반) ───
const PropertiesPanel = ({ signerCount, fields, selectedFieldIndex, currentPage, onAddField, onUpdateField, onRemoveField, onSelectField, onSetSignerCount, templateVariables, onSetTemplateVariables }) => {
    const selectedField = selectedFieldIndex !== null ? fields[selectedFieldIndex] : null;
    const signers = Array.from({ length: signerCount }, (_, i) => ({ order: i + 1, label: SIGNER_LABELS[i] || `서명자 ${i + 1}` }));
    const [newVarKey, setNewVarKey] = React.useState('');
    const [newVarLabel, setNewVarLabel] = React.useState('');

    const addVariable = () => {
        const key = newVarKey.trim();
        const label = newVarLabel.trim();
        if (!key || !label) return;
        if (templateVariables.some(v => v.key === key)) { alert('이미 존재하는 변수 키입니다.'); return; }
        onSetTemplateVariables([...templateVariables, { key, label, type: 'text', default: '' }]);
        setNewVarKey('');
        setNewVarLabel('');
    };

    const removeVariable = (key) => {
        onSetTemplateVariables(templateVariables.filter(v => v.key !== key));
    };

    const allVariables = [
        ...SYSTEM_VARIABLES.map(v => ({ ...v, group: '시스템' })),
        ...templateVariables.map(v => ({ ...v, group: '커스텀' })),
    ];

    return (
        <div className="w-72 bg-white border-l overflow-y-auto flex-shrink-0" style={{ height: 'calc(100vh - 105px)' }}>
            <div className="p-3 space-y-4">
                {/* 서명자 수 설정 */}
                <div>
                    <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">서명자 수</h3>
                    <div className="flex items-center gap-2">
                        <select value={signerCount} onChange={e => onSetSignerCount(parseInt(e.target.value))}
                                className="border rounded px-2 py-1 text-xs flex-1">
                            {[1, 2, 3, 4, 5, 6].map(n => (
                                <option key={n} value={n}>{n}명</option>
                            ))}
                        </select>
                    </div>
                </div>

                {/* 커스텀 변수 관리 */}
                <div className="border-t pt-3">
                    <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">커스텀 변수</h3>
                    <div className="space-y-1 mb-2">
                        {templateVariables.length === 0 && <p className="text-[10px] text-gray-400">정의된 변수 없음</p>}
                        {templateVariables.map(v => (
                            <div key={v.key} className="flex items-center justify-between bg-amber-50 rounded px-2 py-1">
                                <div className="min-w-0">
                                    <span className="text-[10px] font-mono text-amber-700 block truncate">{v.key}</span>
                                    <span className="text-[10px] text-gray-500 block truncate">{v.label}</span>
                                </div>
                                <button onClick={() => removeVariable(v.key)} className="text-gray-300 hover:text-red-500 flex-shrink-0 ml-1 text-xs">&times;</button>
                            </div>
                        ))}
                    </div>
                    <div className="flex gap-1">
                        <input type="text" value={newVarKey} onChange={e => setNewVarKey(e.target.value.replace(/[^a-z0-9_]/gi, ''))}
                               placeholder="키 (영문)" className="border rounded px-1.5 py-1 text-[10px] w-20" />
                        <input type="text" value={newVarLabel} onChange={e => setNewVarLabel(e.target.value)}
                               placeholder="표시명" className="border rounded px-1.5 py-1 text-[10px] flex-1"
                               onKeyDown={e => e.key === 'Enter' && addVariable()} />
                        <button onClick={addVariable} className="px-2 py-1 bg-amber-500 text-white rounded text-[10px] hover:bg-amber-600 flex-shrink-0">+</button>
                    </div>
                </div>

                {/* 필드 도구상자 */}
                <div>
                    <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">필드 추가</h3>
                    {signers.map((s, i) => {
                        const color = SIGNER_COLORS[i % SIGNER_COLORS.length];
                        return (
                            <div key={s.order} className="mb-3">
                                <div className="flex items-center gap-1.5 mb-1.5">
                                    <span className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: color }}></span>
                                    <span className="text-xs font-medium text-gray-700 truncate">{s.label}</span>
                                </div>
                                <div className="grid grid-cols-3 gap-1">
                                    {FIELD_TYPES.map(ft => (
                                        <button key={ft.value}
                                                onClick={() => onAddField(s.order, ft.value)}
                                                className="px-1.5 py-1.5 border rounded text-[10px] hover:bg-gray-50 transition-colors flex flex-col items-center gap-0.5"
                                                style={{ borderColor: `${color}40` }}>
                                            <span className="text-sm">{ft.icon}</span>
                                            <span className="text-gray-600">{ft.label}</span>
                                        </button>
                                    ))}
                                </div>
                            </div>
                        );
                    })}
                </div>

                <div className="border-t pt-3">
                    <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
                        {selectedField ? '필드 속성' : '필드 속성 (선택 없음)'}
                    </h3>
                    {selectedField ? (
                        <div className="space-y-2">
                            <div>
                                <label className="text-[10px] text-gray-500">서명자</label>
                                <select value={selectedField.signer_order}
                                        onChange={e => onUpdateField(selectedFieldIndex, { signer_order: parseInt(e.target.value) })}
                                        className="w-full border rounded px-2 py-1 text-xs mt-0.5">
                                    {signers.map(s => <option key={s.order} value={s.order}>{s.label}</option>)}
                                </select>
                            </div>
                            <div>
                                <label className="text-[10px] text-gray-500">유형</label>
                                <select value={selectedField.field_type}
                                        onChange={e => onUpdateField(selectedFieldIndex, { field_type: e.target.value })}
                                        className="w-full border rounded px-2 py-1 text-xs mt-0.5">
                                    {FIELD_TYPES.map(t => <option key={t.value} value={t.value}>{t.icon} {t.label}</option>)}
                                </select>
                            </div>
                            <div>
                                <label className="text-[10px] text-gray-500">라벨</label>
                                <input type="text" value={selectedField.field_label || ''}
                                       onChange={e => onUpdateField(selectedFieldIndex, { field_label: e.target.value })}
                                       placeholder="선택사항"
                                       className="w-full border rounded px-2 py-1 text-xs mt-0.5" />
                            </div>
                            {['text', 'date'].includes(selectedField.field_type) && (
                            <div>
                                <label className="text-[10px] text-gray-500">변수 연결</label>
                                <select value={selectedField.field_variable || ''}
                                        onChange={e => onUpdateField(selectedFieldIndex, { field_variable: e.target.value || null })}
                                        className={`w-full border rounded px-2 py-1 text-xs mt-0.5 ${selectedField.field_variable ? 'border-amber-400 bg-amber-50' : ''}`}>
                                    <option value="">(없음 - 수동 입력)</option>
                                    <optgroup label="시스템 변수">
                                        {SYSTEM_VARIABLES.map(v => <option key={v.key} value={v.key}>{v.label}</option>)}
                                    </optgroup>
                                    {templateVariables.length > 0 && (
                                        <optgroup label="커스텀 변수">
                                            {templateVariables.map(v => <option key={v.key} value={v.key}>{v.label}</option>)}
                                        </optgroup>
                                    )}
                                </select>
                                {selectedField.field_variable && (
                                    <div className="mt-1 px-2 py-1 bg-amber-50 border border-amber-200 rounded">
                                        <span className="text-[10px] text-amber-700 font-mono">{`{{${selectedField.field_variable}}}`}</span>
                                    </div>
                                )}
                            </div>
                            )}
                            <div className="grid grid-cols-2 gap-1.5">
                                <div>
                                    <label className="text-[10px] text-gray-500">X (%)</label>
                                    <input type="number" value={selectedField.position_x} step="0.5" min="0" max="100"
                                           onChange={e => onUpdateField(selectedFieldIndex, { position_x: parseFloat(e.target.value) || 0 })}
                                           className="w-full border rounded px-2 py-1 text-xs mt-0.5" />
                                </div>
                                <div>
                                    <label className="text-[10px] text-gray-500">Y (%)</label>
                                    <input type="number" value={selectedField.position_y} step="0.5" min="0" max="100"
                                           onChange={e => onUpdateField(selectedFieldIndex, { position_y: parseFloat(e.target.value) || 0 })}
                                           className="w-full border rounded px-2 py-1 text-xs mt-0.5" />
                                </div>
                                <div>
                                    <label className="text-[10px] text-gray-500">W (%)</label>
                                    <input type="number" value={selectedField.width} step="0.5" min="2" max="100"
                                           onChange={e => onUpdateField(selectedFieldIndex, { width: parseFloat(e.target.value) || 2 })}
                                           className="w-full border rounded px-2 py-1 text-xs mt-0.5" />
                                </div>
                                <div>
                                    <label className="text-[10px] text-gray-500">H (%)</label>
                                    <input type="number" value={selectedField.height} step="0.5" min="1" max="100"
                                           onChange={e => onUpdateField(selectedFieldIndex, { height: parseFloat(e.target.value) || 1 })}
                                           className="w-full border rounded px-2 py-1 text-xs mt-0.5" />
                                </div>
                            </div>
                            <div className="grid grid-cols-2 gap-1.5">
                                <div>
                                    <label className="text-[10px] text-gray-500">페이지</label>
                                    <input type="number" value={selectedField.page_number} min="1"
                                           onChange={e => onUpdateField(selectedFieldIndex, { page_number: parseInt(e.target.value) || 1 })}
                                           className="w-full border rounded px-2 py-1 text-xs mt-0.5" />
                                </div>
                                <div>
                                    <label className="text-[10px] text-gray-500">폰트 크기 (pt)</label>
                                    <input type="number" value={selectedField.font_size || ''} min="6" max="72" placeholder="자동"
                                           onChange={e => onUpdateField(selectedFieldIndex, { font_size: e.target.value ? parseInt(e.target.value) : null })}
                                           className="w-full border rounded px-2 py-1 text-xs mt-0.5" />
                                </div>
                            </div>
                            {['text', 'date'].includes(selectedField.field_type) && (
                            <div>
                                <label className="text-[10px] text-gray-500">가로 정렬</label>
                                <div className="flex gap-1 mt-0.5">
                                    {[{ value: 'L', label: '왼쪽', icon: '⫷' }, { value: 'C', label: '가운데', icon: '⫿' }, { value: 'R', label: '오른쪽', icon: '⫸' }].map(a => (
                                        <button key={a.value}
                                                onClick={() => onUpdateField(selectedFieldIndex, { text_align: a.value })}
                                                className={`flex-1 px-2 py-1.5 border rounded text-[10px] font-medium transition-colors ${(selectedField.text_align || 'L') === a.value ? 'bg-blue-600 text-white border-blue-600' : 'text-gray-600 hover:bg-gray-50'}`}
                                                title={a.label}>
                                            {a.label}
                                        </button>
                                    ))}
                                </div>
                            </div>
                            )}
                            <div className="flex items-center gap-2 pt-1">
                                <label className="flex items-center gap-1.5 text-xs cursor-pointer">
                                    <input type="checkbox" checked={selectedField.is_required !== false}
                                           onChange={e => onUpdateField(selectedFieldIndex, { is_required: e.target.checked })}
                                           className="rounded border-gray-300" />
                                    필수 항목
                                </label>
                            </div>
                            <button onClick={() => onRemoveField(selectedFieldIndex)}
                                    className="w-full mt-2 px-3 py-1.5 border border-red-200 text-red-600 rounded text-xs hover:bg-red-50 transition-colors">
                                필드 삭제
                            </button>
                        </div>
                    ) : (
                        <p className="text-xs text-gray-400">PDF에서 필드를 클릭하여 선택하세요</p>
                    )}
                </div>

                {/* 필드 목록 */}
                <div className="border-t pt-3">
                    <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
                        필드 목록 ({fields.length}개)
                    </h3>
                    <div className="space-y-1 max-h-60 overflow-y-auto">
                        {fields.length === 0 && <p className="text-xs text-gray-400">아직 추가된 필드가 없습니다</p>}
                        {fields.map((f, idx) => {
                            const si = f.signer_order - 1;
                            const color = SIGNER_COLORS[si % SIGNER_COLORS.length] || '#888';
                            const typeInfo = FIELD_TYPES.find(t => t.value === f.field_type) || FIELD_TYPES[0];
                            const isSelected = idx === selectedFieldIndex;
                            return (
                                <div key={idx}
                                     onClick={() => onSelectField(idx)}
                                     className={`flex items-center justify-between px-2 py-1.5 rounded cursor-pointer text-xs transition-colors ${isSelected ? 'bg-blue-50 ring-1 ring-blue-200' : 'hover:bg-gray-50'}`}>
                                    <div className="flex items-center gap-1.5 min-w-0">
                                        <span className="w-2 h-2 rounded-full flex-shrink-0" style={{ backgroundColor: color }}></span>
                                        <span className="truncate">{typeInfo.icon} {f.field_label || SIGNER_LABELS[si] || `서명자 ${f.signer_order}`}</span>
                                        {f.field_variable && <span className="text-[8px] bg-amber-100 text-amber-700 px-1 rounded flex-shrink-0" title={f.field_variable}>변수</span>}
                                        <span className="text-gray-400 flex-shrink-0">p.{f.page_number}</span>
                                    </div>
                                    <button onClick={(e) => { e.stopPropagation(); onRemoveField(idx); }}
                                            className="text-gray-300 hover:text-red-500 flex-shrink-0 ml-1">&times;</button>
                                </div>
                            );
                        })}
                    </div>
                </div>
            </div>
        </div>
    );
};

// ─── 유틸 ───
const round2 = (n) => Math.round(n * 100) / 100;

// ─── App ───
const App = () => {
    const [template, setTemplate] = useState(null);
    const [fields, setFields] = useState([]);
    const [signerCount, setSignerCount] = useState(2);
    const [currentPage, setCurrentPage] = useState(1);
    const [totalPages, setTotalPages] = useState(0);
    const [saving, setSaving] = useState(false);
    const [loading, setLoading] = useState(true);
    const [pdfDoc, setPdfDoc] = useState(null);
    const [selectedFieldIndex, setSelectedFieldIndex] = useState(null);
    const [zoom, setZoom] = useState(1.0);
    const [gridEnabled, setGridEnabled] = useState(false);
    const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
    const [clipboard, setClipboard] = useState(null);
    const [templateVariables, setTemplateVariables] = useState([]);
    const [selectedIndices, setSelectedIndices] = useState(new Set());
    const [lassoStart, setLassoStart] = useState(null);
    const [lassoEnd, setLassoEnd] = useState(null);

    // 필드 선택 헬퍼
    const selectField = useCallback((idx, e) => {
        if (e && (e.ctrlKey || e.metaKey || e.shiftKey)) {
            setSelectedIndices(prev => {
                const next = new Set(prev);
                if (next.has(idx)) {
                    next.delete(idx);
                } else {
                    next.add(idx);
                }
                setSelectedFieldIndex(next.size > 0 ? (next.has(idx) ? idx : [...next][next.size - 1]) : null);
                return next;
            });
        } else {
            setSelectedFieldIndex(idx);
            setSelectedIndices(new Set(idx !== null ? [idx] : []));
        }
    }, []);

    const clearSelection = useCallback(() => {
        setSelectedFieldIndex(null);
        setSelectedIndices(new Set());
    }, []);

    // History (Undo/Redo)
    const [history, setHistory] = useState([]);
    const [historyIndex, setHistoryIndex] = useState(-1);
    const skipHistoryRef = useRef(false);

    const canvasRef = useRef(null);
    const containerRef = useRef(null);
    const renderTaskRef = useRef(null);
    const lassoUsedRef = useRef(false);

    const pushHistory = useCallback((newFields) => {
        if (skipHistoryRef.current) return;
        setHistory(prev => {
            const trimmed = prev.slice(0, historyIndex + 1);
            const next = [...trimmed, JSON.parse(JSON.stringify(newFields))];
            if (next.length > MAX_HISTORY) next.shift();
            return next;
        });
        setHistoryIndex(prev => Math.min(prev + 1, MAX_HISTORY - 1));
    }, [historyIndex]);

    const undo = useCallback(() => {
        if (historyIndex <= 0) return;
        const newIndex = historyIndex - 1;
        skipHistoryRef.current = true;
        setFields(JSON.parse(JSON.stringify(history[newIndex])));
        setHistoryIndex(newIndex);
        skipHistoryRef.current = false;
    }, [history, historyIndex]);

    const redo = useCallback(() => {
        if (historyIndex >= history.length - 1) return;
        const newIndex = historyIndex + 1;
        skipHistoryRef.current = true;
        setFields(JSON.parse(JSON.stringify(history[newIndex])));
        setHistoryIndex(newIndex);
        skipHistoryRef.current = false;
    }, [history, historyIndex]);

    // 템플릿 정보 로드
    const fetchTemplate = useCallback(async () => {
        try {
            const res = await fetch(`/esign/contracts/templates/${TEMPLATE_ID}`, { headers: getHeaders() });
            const json = await res.json();
            if (json.success) {
                setTemplate(json.data);
                const sc = json.data.signer_count || 2;
                setSignerCount(sc < 1 ? 2 : sc);
                setTemplateVariables(json.data.variables || []);
                const loadedFields = (json.data.items || []).map(f => ({
                    signer_order: f.signer_order,
                    page_number: f.page_number,
                    position_x: parseFloat(f.position_x),
                    position_y: parseFloat(f.position_y),
                    width: parseFloat(f.width),
                    height: parseFloat(f.height),
                    field_type: f.field_type,
                    field_label: f.field_label || '',
                    field_variable: f.field_variable || null,
                    font_size: f.font_size || null,
                    text_align: f.text_align || 'L',
                    is_required: f.is_required !== false,
                }));
                setFields(loadedFields);
                setHistory([JSON.parse(JSON.stringify(loadedFields))]);
                setHistoryIndex(0);
            }
        } catch (e) { console.error(e); }
        setLoading(false);
    }, []);

    // PDF 로드
    const loadPdf = useCallback(async () => {
        if (!template) return;
        if (!template.file_path) return;
        try {
            const url = `/esign/contracts/templates/${TEMPLATE_ID}/download`;
            const res = await fetch(url, { headers: { 'X-CSRF-TOKEN': csrfToken } });
            if (!res.ok) return;
            const arrayBuffer = await res.arrayBuffer();
            const doc = await pdfjsLib.getDocument({
                data: arrayBuffer,
                cMapUrl: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/cmaps/',
                cMapPacked: true,
            }).promise;
            setPdfDoc(doc);
            setTotalPages(doc.numPages);
        } catch (e) { console.error('PDF 로드 실패:', e); }
    }, [template]);

    // PDF 페이지 렌더링
    const renderPage = useCallback(async (pageNum) => {
        if (!pdfDoc || !canvasRef.current) return;
        if (renderTaskRef.current) {
            try { renderTaskRef.current.cancel(); } catch (_) {}
            renderTaskRef.current = null;
        }
        try {
            const page = await pdfDoc.getPage(pageNum);
            const viewport = page.getViewport({ scale: zoom });
            const canvas = canvasRef.current;
            if (!canvas) return;
            canvas.height = viewport.height;
            canvas.width = viewport.width;
            setCanvasSize({ width: viewport.width, height: viewport.height });
            const ctx = canvas.getContext('2d');
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            const renderTask = page.render({ canvasContext: ctx, viewport });
            renderTaskRef.current = renderTask;
            await renderTask.promise;
            renderTaskRef.current = null;
        } catch (e) {
            if (e.name !== 'RenderingCancelledException') console.error(e);
        }
    }, [pdfDoc, zoom]);

    useEffect(() => { fetchTemplate(); }, [fetchTemplate]);
    useEffect(() => { loadPdf(); }, [loadPdf]);
    useEffect(() => { renderPage(currentPage); }, [renderPage, currentPage]);

    // PDF.js 메모리 정리
    useEffect(() => {
        return () => { if (pdfDoc) pdfDoc.destroy(); };
    }, [pdfDoc]);

    // 필드 추가
    const addField = useCallback((signerOrder, fieldType = 'signature') => {
        const newField = {
            signer_order: signerOrder, page_number: currentPage,
            position_x: 25, position_y: 40, width: 20, height: 5,
            field_type: fieldType, field_label: '', field_variable: null, font_size: null, text_align: 'L', is_required: true,
        };
        const newFields = [...fields, newField];
        setFields(newFields);
        setSelectedFieldIndex(newFields.length - 1);
        setSelectedIndices(new Set([newFields.length - 1]));
        pushHistory(newFields);
    }, [fields, currentPage, pushHistory]);

    // 필드 삭제
    const removeField = useCallback((idx) => {
        const newFields = fields.filter((_, i) => i !== idx);
        setFields(newFields);
        clearSelection();
        pushHistory(newFields);
    }, [fields, pushHistory, clearSelection]);

    // 필드 업데이트 (드래그/리사이즈 중)
    const updateField = useCallback((idx, updates) => {
        setFields(prev => prev.map((f, i) => i === idx ? { ...f, ...updates } : f));
    }, []);

    // 필드 업데이트 + 히스토리
    const updateFieldWithHistory = useCallback((idx, updates) => {
        const newFields = fields.map((f, i) => i === idx ? { ...f, ...updates } : f);
        setFields(newFields);
        pushHistory(newFields);
    }, [fields, pushHistory]);

    // 그룹 드래그 (올가미 선택된 필드 동시 이동)
    const handleGroupMove = useCallback((dx, dy) => {
        setFields(prev => prev.map((f, i) => {
            if (!selectedIndices.has(i)) return f;
            return {
                ...f,
                position_x: round2(Math.max(0, Math.min(100 - f.width, f.position_x + dx))),
                position_y: round2(Math.max(0, Math.min(100 - f.height, f.position_y + dy))),
            };
        }));
    }, [selectedIndices]);

    // 드래그/리사이즈 종료 시 히스토리 push
    const handleDragEnd = useCallback(() => {
        pushHistory(fields);
    }, [fields, pushHistory]);

    // 서명자 수 변경
    const handleSetSignerCount = useCallback((count) => {
        setSignerCount(count);
        // 서명자 수보다 큰 signer_order를 가진 필드가 있으면 경고
        const hasExceeding = fields.some(f => f.signer_order > count);
        if (hasExceeding) {
            if (!window.confirm(`서명자 ${count}명보다 높은 순서의 필드가 있습니다. 해당 필드를 삭제하시겠습니까?`)) {
                return;
            }
            const newFields = fields.filter(f => f.signer_order <= count);
            setFields(newFields);
            pushHistory(newFields);
            clearSelection();
        }
    }, [fields, pushHistory, clearSelection]);

    // 저장
    const saveFields = async () => {
        setSaving(true);
        try {
            // 1) 필드 아이템 저장
            const res = await fetch(`/esign/contracts/templates/${TEMPLATE_ID}/items`, {
                method: 'PUT', headers: getHeaders(),
                body: JSON.stringify({
                    items: fields.map((f, i) => ({
                        signer_order: f.signer_order,
                        page_number: f.page_number,
                        position_x: round2(f.position_x),
                        position_y: round2(f.position_y),
                        width: round2(f.width),
                        height: round2(f.height),
                        field_type: f.field_type,
                        field_label: f.field_label || '',
                        field_variable: f.field_variable || null,
                        font_size: f.font_size || null,
                        text_align: f.text_align || 'L',
                        is_required: f.is_required !== false,
                    })),
                }),
            });
            const json = await res.json();
            if (!json.success) { alert(json.message || '저장 실패'); setSaving(false); return; }

            // 2) 템플릿 변수 정의 저장
            const res2 = await fetch(`/esign/contracts/templates/${TEMPLATE_ID}`, {
                method: 'PUT', headers: getHeaders(),
                body: JSON.stringify({
                    name: template.name,
                    description: template.description || '',
                    category: template.category || '',
                    variables: templateVariables,
                }),
            });
            const json2 = await res2.json();
            if (json2.success) {
                alert('템플릿 필드와 변수가 저장되었습니다.');
            } else {
                alert('필드는 저장되었으나 변수 저장 실패: ' + (json2.message || ''));
            }
        } catch (e) { alert('서버 오류'); }
        setSaving(false);
    };

    // 올가미(Lasso) 선택
    const handleLassoStart = useCallback((e) => {
        if (e.target !== containerRef.current) return;
        if (e.button !== 0) return;
        const rect = containerRef.current.getBoundingClientRect();
        const x = ((e.clientX - rect.left) / rect.width) * 100;
        const y = ((e.clientY - rect.top) / rect.height) * 100;
        setLassoStart({ x, y });
        setLassoEnd({ x, y });
        if (!(e.ctrlKey || e.metaKey || e.shiftKey)) {
            clearSelection();
        }
    }, [clearSelection]);

    const handleLassoMove = useCallback((e) => {
        if (!lassoStart) return;
        const rect = containerRef.current.getBoundingClientRect();
        const x = Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100));
        const y = Math.max(0, Math.min(100, ((e.clientY - rect.top) / rect.height) * 100));
        setLassoEnd({ x, y });
    }, [lassoStart]);

    const handleLassoEnd = useCallback(() => {
        if (!lassoStart || !lassoEnd) {
            setLassoStart(null);
            setLassoEnd(null);
            return;
        }
        const minX = Math.min(lassoStart.x, lassoEnd.x);
        const maxX = Math.max(lassoStart.x, lassoEnd.x);
        const minY = Math.min(lassoStart.y, lassoEnd.y);
        const maxY = Math.max(lassoStart.y, lassoEnd.y);
        if (maxX - minX < 1 && maxY - minY < 1) {
            setLassoStart(null);
            setLassoEnd(null);
            return;
        }
        const selected = new Set(selectedIndices);
        fields.forEach((f, idx) => {
            if (f.page_number !== currentPage) return;
            const fRight = f.position_x + f.width;
            const fBottom = f.position_y + f.height;
            if (f.position_x < maxX && fRight > minX && f.position_y < maxY && fBottom > minY) {
                selected.add(idx);
            }
        });
        setSelectedIndices(selected);
        if (selected.size === 1) {
            setSelectedFieldIndex([...selected][0]);
        } else {
            setSelectedFieldIndex(null);
        }
        // 올가미 완료 후 click 이벤트가 clearSelection을 호출하지 않도록 플래그
        lassoUsedRef.current = true;
        setTimeout(() => { lassoUsedRef.current = false; }, 0);
        setLassoStart(null);
        setLassoEnd(null);
    }, [lassoStart, lassoEnd, fields, currentPage, selectedIndices]);

    // 키보드 단축키
    useEffect(() => {
        const handler = (e) => {
            if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
                e.preventDefault();
                if (e.shiftKey) redo(); else undo();
                return;
            }
            if ((e.ctrlKey || e.metaKey) && e.key === 'y') {
                e.preventDefault();
                redo();
                return;
            }
            if ((e.key === 'Delete' || e.key === 'Backspace') && selectedIndices.size > 0) {
                if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return;
                e.preventDefault();
                const newFields = fields.filter((_, i) => !selectedIndices.has(i));
                setFields(newFields);
                clearSelection();
                pushHistory(newFields);
                return;
            }
            if (e.key === 'Escape') {
                clearSelection();
                setLassoStart(null);
                setLassoEnd(null);
                return;
            }
            if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key) && selectedIndices.size > 0) {
                if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
                e.preventDefault();
                const step = e.shiftKey ? 1 : 0.25;
                const newFields = fields.map((f, i) => {
                    if (!selectedIndices.has(i)) return f;
                    const updated = { ...f };
                    switch (e.key) {
                        case 'ArrowLeft':  updated.position_x = round2(Math.max(0, f.position_x - step)); break;
                        case 'ArrowRight': updated.position_x = round2(Math.min(100 - f.width, f.position_x + step)); break;
                        case 'ArrowUp':    updated.position_y = round2(Math.max(0, f.position_y - step)); break;
                        case 'ArrowDown':  updated.position_y = round2(Math.min(100 - f.height, f.position_y + step)); break;
                    }
                    return updated;
                });
                setFields(newFields);
                pushHistory(newFields);
                return;
            }
            if ((e.ctrlKey || e.metaKey) && e.key === 'x' && selectedIndices.size > 0) {
                if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
                e.preventDefault();
                const cut = fields.filter((_, i) => selectedIndices.has(i)).map(f => ({ ...f }));
                setClipboard(cut);
                const newFields = fields.filter((_, i) => !selectedIndices.has(i));
                setFields(newFields);
                clearSelection();
                pushHistory(newFields);
                return;
            }
            if ((e.ctrlKey || e.metaKey) && e.key === 'c' && selectedIndices.size > 0) {
                if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
                e.preventDefault();
                const copied = fields.filter((_, i) => selectedIndices.has(i)).map(f => ({ ...f }));
                setClipboard(copied);
                return;
            }
            if ((e.ctrlKey || e.metaKey) && e.key === 'v' && clipboard) {
                if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
                e.preventDefault();
                const items = Array.isArray(clipboard) ? clipboard : [clipboard];
                const pasted = items.map(f => ({
                    ...f,
                    position_x: Math.min(80, f.position_x + 2),
                    position_y: Math.min(90, f.position_y + 2),
                    page_number: currentPage,
                }));
                const newFields = [...fields, ...pasted];
                setFields(newFields);
                const newIndices = new Set();
                for (let i = fields.length; i < newFields.length; i++) newIndices.add(i);
                setSelectedIndices(newIndices);
                setSelectedFieldIndex(newIndices.size === 1 ? [...newIndices][0] : null);
                pushHistory(newFields);
                setClipboard(pasted);
                return;
            }
            if ((e.ctrlKey || e.metaKey) && (e.key === '-' || e.key === '=')) {
                e.preventDefault();
                setZoom(prev => {
                    const i = ZOOM_LEVELS.indexOf(prev);
                    if (e.key === '-' && i > 0) return ZOOM_LEVELS[i - 1];
                    if (e.key === '=' && i < ZOOM_LEVELS.length - 1) return ZOOM_LEVELS[i + 1];
                    return prev;
                });
                return;
            }
        };
        window.addEventListener('keydown', handler);
        return () => window.removeEventListener('keydown', handler);
    }, [undo, redo, selectedIndices, fields, clearSelection, clipboard, currentPage, pushHistory]);

    if (loading) return <div className="flex items-center justify-center h-screen text-gray-400">로딩 중...</div>;
    if (!template) return <div className="flex items-center justify-center h-screen text-red-500">템플릿을 찾을 수 없습니다.</div>;

    const pageFields = fields.map((f, idx) => ({ ...f, _idx: idx })).filter(f => f.page_number === currentPage);

    return (
        <div className="flex flex-col h-screen bg-gray-100">
            <Toolbar
                zoom={zoom} setZoom={setZoom}
                gridEnabled={gridEnabled} setGridEnabled={setGridEnabled}
                undo={undo} redo={redo}
                canUndo={historyIndex > 0} canRedo={historyIndex < history.length - 1}
                saving={saving} saveFields={saveFields}
                goBack={() => location.href = '/esign/templates'}
            />
            <div className="flex flex-1 overflow-hidden">
                {/* 썸네일 사이드바 */}
                <ThumbnailSidebar
                    pdfDoc={pdfDoc} totalPages={totalPages}
                    currentPage={currentPage} setCurrentPage={setCurrentPage}
                    fields={fields}
                />

                {/* PDF 뷰어 (중앙) */}
                <div className="flex-1 overflow-auto bg-gray-200 relative"
                     onClick={() => clearSelection()}>
                    {!pdfDoc && !template.file_path ? (
                        <div className="flex items-center justify-center h-full">
                            <div className="text-center p-8">
                                <svg className="mx-auto mb-4 text-gray-300" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
                                    <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>
                                </svg>
                                <p className="text-gray-500 mb-4 text-sm">이 템플릿에 PDF 파일이 없습니다.</p>
                                <p className="text-gray-400 text-xs">템플릿 관리 화면에서 PDF를 먼저 업로드해주세요.</p>
                                <a href="/esign/templates" className="inline-block mt-4 px-4 py-2 border rounded-lg text-sm text-gray-700 hover:bg-gray-50">
                                    템플릿 관리로 이동
                                </a>
                            </div>
                        </div>
                    ) : (
                    <div className="flex justify-center p-6 min-h-full">
                        <div className="relative" style={{ width: canvasSize.width || 'auto', height: canvasSize.height || 'auto' }}>
                            <canvas ref={canvasRef} className="shadow-lg block" />
                            {gridEnabled && canvasSize.width > 0 && (
                                <div style={{
                                    position: 'absolute', inset: 0, pointerEvents: 'none', zIndex: 5,
                                    backgroundImage: 'linear-gradient(rgba(59,130,246,0.08) 1px, transparent 1px), linear-gradient(90deg, rgba(59,130,246,0.08) 1px, transparent 1px)',
                                    backgroundSize: '5% 5%',
                                }} />
                            )}
                            <div ref={containerRef}
                                 style={{ position: 'absolute', inset: 0, zIndex: 10, userSelect: 'none' }}
                                 onClick={(e) => {
                                     e.stopPropagation();
                                     if (lassoUsedRef.current) return;
                                     if (e.target === e.currentTarget) clearSelection();
                                 }}
                                 onMouseDown={handleLassoStart}
                                 onMouseMove={handleLassoMove}
                                 onMouseUp={handleLassoEnd}
                                 onMouseLeave={() => { setLassoStart(null); setLassoEnd(null); }}>
                                {pageFields.map((f) => (
                                    <FieldOverlay
                                        key={f._idx}
                                        field={f}
                                        index={f._idx}
                                        selected={selectedIndices.has(f._idx)}
                                        selectedIndices={selectedIndices}
                                        signerCount={signerCount}
                                        containerRef={containerRef}
                                        onSelect={selectField}
                                        onUpdate={updateField}
                                        onDragEnd={handleDragEnd}
                                        onGroupMove={handleGroupMove}
                                        gridEnabled={gridEnabled}
                                    />
                                ))}
                                {lassoStart && lassoEnd && (
                                    <div style={{
                                        position: 'absolute',
                                        left: `${Math.min(lassoStart.x, lassoEnd.x)}%`,
                                        top: `${Math.min(lassoStart.y, lassoEnd.y)}%`,
                                        width: `${Math.abs(lassoEnd.x - lassoStart.x)}%`,
                                        height: `${Math.abs(lassoEnd.y - lassoStart.y)}%`,
                                        border: '2px dashed #3B82F6',
                                        backgroundColor: 'rgba(59, 130, 246, 0.1)',
                                        pointerEvents: 'none',
                                        zIndex: 50,
                                    }} />
                                )}
                            </div>
                        </div>
                    </div>
                    )}
                </div>

                {/* 속성 패널 (우측) */}
                <PropertiesPanel
                    signerCount={signerCount}
                    fields={fields}
                    selectedFieldIndex={selectedFieldIndex}
                    currentPage={currentPage}
                    onAddField={addField}
                    onUpdateField={updateFieldWithHistory}
                    onRemoveField={removeField}
                    onSetSignerCount={handleSetSignerCount}
                    onSelectField={(idx) => {
                        setSelectedFieldIndex(idx);
                        setSelectedIndices(new Set([idx]));
                        if (fields[idx]) setCurrentPage(fields[idx].page_number);
                    }}
                    templateVariables={templateVariables}
                    onSetTemplateVariables={setTemplateVariables}
                />
            </div>
        </div>
    );
};

ReactDOM.createRoot(document.getElementById('esign-template-fields-root')).render(<App />);
</script>
@endverbatim
@endpush
