import React, {
    forwardRef,
    useCallback,
    useEffect,
    useImperativeHandle,
    useMemo,
    useRef,
    useState,
} from 'react';

import Konva from 'konva';
import {
    ArmchairIcon,
    BrickWall,
    ClipboardCheck,
    CopyIcon,
    Download,
    Edit2Icon,
    Grid2X2,
    Info,
    LassoSelect,
    Maximize2,
    Pentagon,
    PlusCircle,
    RowsIcon,
    Save,
    SquareIcon,
    TheaterIcon,
    TrashIcon,
    Undo2,
    Upload,
    VaultIcon,
    ZoomIn,
    ZoomOut,
} from 'lucide-react';
import { MdOutlineTableRestaurant } from 'react-icons/md';
import {
    Circle,
    Layer,
    Line,
    Rect,
    Stage,
    Text,
    Transformer,
} from 'react-konva';

import { HallElementType } from '@/api/types';
import { Button } from '@/components/ui/button';
import {
    DropdownMenu,
    DropdownMenuContent,
    DropdownMenuGroup,
    DropdownMenuItem,
    DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
    Grid,
    GridCol,
    GridRow,
} from '@/components/ui/grid';
import { SystemWindowDialog } from '@/components/ui/system-window-dialog';
import { toast } from '@/componentsRoot/ToastRoot';
import { Lang } from '@/lang/Lang';

import CanvasSettingsDialog from './components/CanvasSettingsDialog';
import HallSummaryDialog from './components/HallSummaryDialog';
import ImportHallJsonDialog from './components/ImportHallJsonDialog';
import SeatNumberingDialog from './components/SeatNumberingDialog';
import SeatRowsDialog from './components/SeatRowsDialog';
import SeatSettingsDialog from './components/SeatSettingsDialog';
import SectorSettingsDialog from './components/SectorSettingsDialog';
import StageSettingsDialog from './components/StageSettingsDialog';
import {
    buildHallElementsArray,
    coerceHallElementsSingleUUID,
    ensureHallCanvas,
    HALL_CANVAS_SIZE_MIN,
    hallElementsFromInitValue,
    isHallImportJsonValue,
    makeHallElementId,
    reconcileSeatSectorsLayout,
    resolveHallCanvasSize,
    sortHallElementsByPaintOrder,
} from './hallFlat';
import {
    hallPolygonAbsolutePoints,
    hallPolygonBBox,
    hallPolygonCloseSnapDistance,
    hallPolygonDefaultRelativePoints,
    hallPolygonLabelLayout,
    hallPolygonLayoutFromAbsolute,
    hallPolygonLocalSize,
    type HallPolygonPoint,
    isHallNamedSectorType,
    pointInHallPolygon,
    scaleHallPolygonRelativePoints,
} from './hallPolygon';
import {
    HALL_SEAT_FILL_DIRECTION_DEFAULT_ROWS,
    HALL_SEAT_FILL_DIRECTION_DEFAULT_SEAT_NUMBERS,
    type HallSeatFillDirection,
    hallSeatsGroupedLinesOrdered,
} from './hallSeatOrder';
import {
    hallSeatLabelAt,
    parseHallSeatStart,
} from './hallSeatStartLabel';
import { hallSectorLabelLayout } from './hallSectorLabel';
import { buildHallSummaryText } from './hallSummary';
import {
    buildHallSeatSmartLabels,
    getHallBlockCornerRadius,
    getHallBlockOnShapeTextFill,
    getHallSeatCornerRadius,
    HALL_CANVAS,
    HALL_CHECK_FLASH_MS,
    HALL_DUPLICATE_HIGHLIGHT_STROKE,
    HALL_EDITOR_GRID,
    HALL_ELEMENT_DEFAULTS,
    HALL_SEAT_DRAFT,
    HALL_SEAT_GAP,
    HALL_SEAT_SIZE,
    HALL_SECTOR,
    HALL_SELECTION,
    HALL_ZOOM,
    type HallElementPaletteDefaults,
    hallSeatLabelFill,
    hallSeatLabelFontSizeForRect,
    hallSeatRowLabelFill,
    hallSeatRowLabelFontSizeForRect,
    resolveHallElementFill,
} from './hallTheme';

type DialogHallEditorOpenParams = {
    /** Массив `HallElementType` или старый объект с полями `seats` / `elements`. */
    initValue?: unknown;
    onSave?: (value: HallElementType[]) => void;
}
type DialogHallEditorRef = {
    /** Открыть редактор; `initValue` — массив элементов или старый объект зала. */
    open: (params: DialogHallEditorOpenParams) => void;
    close: () => void;
}

const HALL_UNDO_MAX = 3;

const findInLayout = (els: any[], id: string): any | undefined => els.find((e) => e._id === id);

const mapItemInLayout = (els: any[], id: string, fn: (e: any) => any): any[] => (
    els.map((e) => (e._id === id ? fn(e) : e))
);

const filterIdsFromLayout = (els: any[], drop: Set<string>): any[] => (
    els.filter((e) => !drop.has(e._id))
);

const appendToLayoutByType = (els: any[], el: any): any[] => (
    sortHallElementsByPaintOrder([...els, el])
);

const appendManyToLayout = (els: any[], newOnes: any[]): any[] => (
    sortHallElementsByPaintOrder([...els, ...newOnes])
);

const mapAllItemsInLayout = (els: any[], fn: (e: any) => any): any[] => els.map(fn);

type HallEditorUndoSnapshot = {
    layout: any[];
};

export const dialogHallEditorRef = React.createRef<DialogHallEditorRef>();




/** Геометрия и визуальные поля элемента в JSON (без логики места / вместимости). */
export type HallJsonLayoutFields = {
    _id: string;
    x: number;
    y: number;
    width: number;
    height: number;
    rotation: number;
    label: string;
};


type SeatDraftRect = {
    startX: number;
    startY: number;
    currentX: number;
    currentY: number;
};

/** Рисование области: сетка мест или один блок (сцена / стол / зона). */
type RectPlacementDraft =
    | ({ mode: 'seats' } & SeatDraftRect)
    | ({ mode: 'block'; blockType: 'stage' | 'table' | 'zone' | 'wall' } & SeatDraftRect);

type SelectionRect = {
    startX: number;
    startY: number;
    currentX: number;
    currentY: number;
    appendToSelection: boolean;
};

type SelectionLassoDraft = {
    points: HallPolygonPoint[];
    appendToSelection: boolean;
};

const LASSO_POINT_MIN_DIST = 4;
const LASSO_MIN_BBOX = 4;

type SeatDraftDefaults = {
    w: number;
    h: number;
    halfW: number;
    halfH: number;
    gapX: number;
    gapY: number;
};

type SeatDraftGridItem = {
    x: number;
    y: number;
    w: number;
    h: number;
    halfW: number;
    halfH: number;
};

function buildSeatDraftGrid(
    draft: SeatDraftRect,
    seatDefaults: SeatDraftDefaults,
): {
    x: number;
    y: number;
    width: number;
    height: number;
    cols: number;
    rows: number;
    total: number;
    items: SeatDraftGridItem[];
} {
    const width = Math.abs(draft.currentX - draft.startX);
    const height = Math.abs(draft.currentY - draft.startY);
    const pitchX = seatDefaults.w + seatDefaults.gapX;
    const pitchY = seatDefaults.h + seatDefaults.gapY;
    const cols = Math.max(1, Math.floor((width - seatDefaults.w + seatDefaults.gapX) / pitchX) + 1);
    const rows = Math.max(1, Math.floor((height - seatDefaults.h + seatDefaults.gapY) / pitchY) + 1);
    const x = Math.min(draft.startX, draft.currentX);
    const y = Math.min(draft.startY, draft.currentY);
    const items: SeatDraftGridItem[] = [];

    for (let row = 0; row < rows; row++) {
        for (let col = 0; col < cols; col++) {
            items.push({
                x: x + seatDefaults.halfW + col * pitchX,
                y: y + seatDefaults.halfH + row * pitchY,
                w: seatDefaults.w,
                h: seatDefaults.h,
                halfW: seatDefaults.halfW,
                halfH: seatDefaults.halfH,
            });
        }
    }

    return {
        x,
        y,
        width,
        height,
        cols,
        rows,
        total: cols * rows,
        items,
    };
}

/** Значения по умолчанию для мест: явные `null`, чтобы в JSON поля всегда присутствовали. */
const DEFAULT_SEAT_LOGIC: Pick<any, 'seatNumber' | 'seatRow' | 'sector'> = {
    seatNumber: undefined,
    seatRow: undefined,
    sector: undefined,
};

const PALETTE: Record<string, HallElementPaletteDefaults & { icon?: React.ReactNode }> = {
    seat: {
        icon: <ArmchairIcon />,
        ...HALL_ELEMENT_DEFAULTS.seat,
        ...DEFAULT_SEAT_LOGIC,
    },
    table: {
        icon: <MdOutlineTableRestaurant size={24} />,
        ...HALL_ELEMENT_DEFAULTS.table,
    },

    stage: {
        icon: <TheaterIcon />,
        ...HALL_ELEMENT_DEFAULTS.stage,
    },
    wall: {
        icon: <BrickWall />,
        ...HALL_ELEMENT_DEFAULTS.wall,
    },
    sector: {
        icon: <SquareIcon />,
        ...HALL_ELEMENT_DEFAULTS.sector,
    },
    sector_polygon: {
        icon: <Pentagon />,
        ...HALL_ELEMENT_DEFAULTS.sector_polygon,
    },
    zone: {
        icon: <Grid2X2 />,
        ...HALL_ELEMENT_DEFAULTS.zone,
    },
};

function seatSize(el: any): { w: number; h: number; halfW: number; halfH: number } {
    const wRaw = el?.width;
    const hRaw = el?.height;
    const wNum = typeof wRaw === 'number' ? wRaw : Number(wRaw);
    const hNum = typeof hRaw === 'number' ? hRaw : Number(hRaw);
    if (!(Number.isFinite(wNum) && wNum > 0) || !(Number.isFinite(hNum) && hNum > 0)) {
        console.warn('[DialogHallEditor] Invalid seat size, fallback to default size', {
            seatId: el?._id,
            width: wRaw,
            height: hRaw,
            fallback: HALL_SEAT_SIZE,
        });
    }
    const w = Number.isFinite(wNum) && wNum > 0 ? wNum : HALL_SEAT_SIZE;
    const h = Number.isFinite(hNum) && hNum > 0 ? hNum : HALL_SEAT_SIZE;
    return { w, h, halfW: w / 2, halfH: h / 2 };
}

function hallCenteredRectBounds(
    centerX: number,
    centerY: number,
    width: number,
    height: number,
    rotation = 0,
): { x: number; y: number; width: number; height: number } {
    const angle = ((rotation ?? 0) * Math.PI) / 180;
    const cos = Math.cos(angle);
    const sin = Math.sin(angle);
    const bboxWidth = Math.abs(width * cos) + Math.abs(height * sin);
    const bboxHeight = Math.abs(width * sin) + Math.abs(height * cos);
    return {
        x: centerX - bboxWidth / 2,
        y: centerY - bboxHeight / 2,
        width: bboxWidth,
        height: bboxHeight,
    };
}

/** Bounding box для hit-test и группового выделения (место — квадрат вокруг центра `x,y`). */
function hallElementBounds(el: any): { x: number; y: number; width: number; height: number } {
    if (el.type === 'seat') {
        const { w, h } = seatSize(el);
        return hallCenteredRectBounds(el.x, el.y, w, h, el.rotation ?? 0);
    }
    if (el.type === 'sector_polygon') {
        const abs = hallPolygonAbsolutePoints(el);
        const bbox = hallPolygonBBox(abs);
        if (bbox) {
            return bbox;
        }
    }
    return { x: el.x, y: el.y, width: el.width, height: el.height };
}

function hallElementsAABB(elements: any[]): { x: number; y: number; width: number; height: number } | null {
    if (!elements.length) {
        return null;
    }
    let minX = Number.POSITIVE_INFINITY;
    let minY = Number.POSITIVE_INFINITY;
    let maxX = Number.NEGATIVE_INFINITY;
    let maxY = Number.NEGATIVE_INFINITY;
    elements.forEach((el) => {
        const b = hallElementBounds(el);
        minX = Math.min(minX, b.x);
        minY = Math.min(minY, b.y);
        maxX = Math.max(maxX, b.x + b.width);
        maxY = Math.max(maxY, b.y + b.height);
    });
    if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(maxX) || !Number.isFinite(maxY)) {
        return null;
    }
    return {
        x: minX,
        y: minY,
        width: Math.max(1, maxX - minX),
        height: Math.max(1, maxY - minY),
    };
}

/** Remap a point from old AABB into new AABB (proportional spread). */
function remapPointIntoBounds(
    x: number,
    y: number,
    oldBounds: { x: number; y: number; width: number; height: number },
    newBounds: { x: number; y: number; width: number; height: number },
): { x: number; y: number } {
    const sx = oldBounds.width > 0 ? newBounds.width / oldBounds.width : 1;
    const sy = oldBounds.height > 0 ? newBounds.height / oldBounds.height : 1;
    return {
        x: newBounds.x + (x - oldBounds.x) * sx,
        y: newBounds.y + (y - oldBounds.y) * sy,
    };
}

type HallSpreadSeatSnap = {
    x: number;
    y: number;
    w: number;
    h: number;
    rotation: number;
};

type HallSpreadGesture = {
    active: boolean;
    oldBounds: { x: number; y: number; width: number; height: number };
    seats: Record<string, HallSpreadSeatSnap>;
};

function rectIntersectsRect(
    a: { x: number; y: number; width: number; height: number },
    b: { x: number; y: number; width: number; height: number },
): boolean {
    return !(
        a.x > b.x + b.width
        || a.x + a.width < b.x
        || a.y > b.y + b.height
        || a.y + a.height < b.y
    );
}

const normalizeSeatLogicRecord = (s: any) => ({
    seatNumber: (s.seatNumber == null || String(s.seatNumber).trim() === '') ? null : String(s.seatNumber).trim(),
    seatRow: (s.seatRow == null || String(s.seatRow).trim() === '') ? null : String(s.seatRow).trim(),
    sector: (s.sector == null || s.sector === '') ? null : String(s.sector).trim(),
});

const seatLogicSignature = (s: any) => JSON.stringify({
    type: s.type,
    ...normalizeSeatLogicRecord(s),
});

/** Дубликаты: места с одинаковой «логикой»; сектора/зоны с одинаковым названием. */
const collectHallDuplicateIdsFromLayout = (els: any[]): string[] => {
    const dup = new Set<string>();

    const bySeatSig = new Map<string, string[]>();
    els.filter((e) => e.type === 'seat').forEach((s) => {
        const sig = seatLogicSignature(s);
        const arr = bySeatSig.get(sig) ?? [];
        arr.push(s._id);
        bySeatSig.set(sig, arr);
    });
    bySeatSig.forEach((ids) => {
        if (ids.length > 1) {
            ids.forEach((id) => dup.add(id));
        }
    });

    const namedSectors = els.filter((e) => isHallNamedSectorType(e.type));
    const byLabel = new Map<string, string[]>();
    namedSectors.forEach((s) => {
        const key = (s.label ?? '').trim().toLowerCase();
        if (!key) {
            return;
        }
        const arr = byLabel.get(key) ?? [];
        arr.push(s._id);
        byLabel.set(key, arr);
    });
    byLabel.forEach((ids) => {
        if (ids.length > 1) {
            ids.forEach((id) => dup.add(id));
        }
    });

    return Array.from(dup);
};

const createElement = (type: string, x: number, y: number): any => {
    const base = PALETTE[type];
    const el: any = {
        _id: makeHallElementId(),
        type,
        x,
        y,
        width: base.width,
        height: base.height,
        rotation: 0,
        label: type === 'sector' || type === 'sector_polygon'
            ? 'Новый сектор'
            : (type === 'zone' ? 'Новый сектор без мест' : base.label),
        ...(type === 'seat' ? DEFAULT_SEAT_LOGIC : {}),
    };
    if (type === 'sector_polygon') {
        el.points = hallPolygonDefaultRelativePoints(base.width, base.height);
    }
    return el;
};

const DialogHallEditor = forwardRef<DialogHallEditorRef, {}>((props, ref) => {
    const [open, setOpen] = useState(false); // Не трогать для теста всегда открыт
    const [layout, setLayout] = useState<any[]>([]);
    const [selectedIds, setSelectedIds] = useState<string[]>([]);
    const [toolForClickCreate, setToolForClickCreate] = useState<string | null>(null);
    const [polygonDraft, setPolygonDraft] = useState<{
        points: HallPolygonPoint[];
        cursor: HallPolygonPoint | null;
    } | null>(null);
    const [zoom, setZoom] = useState(1);
    const zoomRef = useRef(zoom);
    zoomRef.current = zoom;
    const [rectPlacementDraft, setRectPlacementDraft] = useState<RectPlacementDraft | null>(null);
    const [selectionRect, setSelectionRect] = useState<SelectionRect | null>(null);
    const [lassoMode, setLassoMode] = useState(false);
    const [lassoDraft, setLassoDraft] = useState<SelectionLassoDraft | null>(null);
    const [seatNumberingOpen, setSeatNumberingOpen] = useState(false);
    const [seatNumberStart, setSeatNumberStart] = useState('1');
    const [seatNumberFillDirection, setSeatNumberFillDirection] = useState<HallSeatFillDirection>(
        HALL_SEAT_FILL_DIRECTION_DEFAULT_SEAT_NUMBERS,
    );
    const [seatRowsOpen, setSeatRowsOpen] = useState(false);
    const [seatRowStart, setSeatRowStart] = useState('1');
    const [seatRowFillDirection, setSeatRowFillDirection] = useState<HallSeatFillDirection>(
        HALL_SEAT_FILL_DIRECTION_DEFAULT_ROWS,
    );
    const layoutRef = useRef(layout);
    layoutRef.current = layout;

    const hallUndoRef = useRef<HallEditorUndoSnapshot[]>([]);
    const [seatSettingsOpen, setSeatSettingsOpen] = useState(false);
    const [seatSettingsSeatId, setSeatSettingsSeatId] = useState<string | null>(null);
    const [seatSettingsSeatNumber, setSeatSettingsSeatNumber] = useState<string>('');
    const [seatSettingsSeatRow, setSeatSettingsSeatRow] = useState<string>('');
    const [hallImportOpen, setHallImportOpen] = useState(false);
    const [hallSummaryOpen, setHallSummaryOpen] = useState(false);
    const [sectorSettingsOpen, setSectorSettingsOpen] = useState(false);
    const [sectorSettingsId, setSectorSettingsId] = useState<string | null>(null);
    const [sectorSettingsName, setSectorSettingsName] = useState('');
    const [sectorSettingsIsNoSeats, setSectorSettingsIsNoSeats] = useState(false);
    const [freezeSectorSelection, setFreezeSectorSelection] = useState(false);
    const [stageSettingsOpen, setStageSettingsOpen] = useState(false);
    const [stageSettingsId, setStageSettingsId] = useState<string | null>(null);
    const [stageSettingsName, setStageSettingsName] = useState('');
    const [canvasSettingsOpen, setCanvasSettingsOpen] = useState(false);
    const [canvasSettingsWidth, setCanvasSettingsWidth] = useState('800');
    const [canvasSettingsHeight, setCanvasSettingsHeight] = useState('800');
    const [seatHotkeyEdit, setSeatHotkeyEdit] = useState<{
        mode: 'row' | 'number';
        seatId: string;
        buffer: string;
    } | null>(null);
    const seatHotkeyEditRef = useRef<{
        mode: 'row' | 'number';
        seatId: string;
        buffer: string;
        dirty: boolean;
        layoutBefore: any[];
    } | null>(null);

    const [hallCheckFlash, setHallCheckFlash] = useState<{ ids: string[]; startedAt: number } | null>(null);
    const [hallCheckPulseMs, setHallCheckPulseMs] = useState(0);

    const stageRef = useRef<any>(null);
    const wrapperRef = useRef<HTMLDivElement | null>(null);
    const trRef = useRef<any>(null);
    const nodeRefs = useRef<Record<string, any>>({});
    const dragGroupStartRef = useRef<Record<string, { x: number; y: number }>>({});
    const dragGroupSelectedIdsRef = useRef<string[]>([]);
    const activeDragRef = useRef<{ id: string; startX: number; startY: number } | null>(null);
    const dragGroupPointerStartRef = useRef<{ x: number; y: number } | null>(null);
    const singleDragStartRef = useRef<{ id: string; x: number; y: number } | null>(null);
    const selectionFrameDragStartRef = useRef<Record<string, { x: number; y: number }>>({});
    const selectionFramePointerStartRef = useRef<{ x: number; y: number } | null>(null);
    /** Alt held — arms spread mode (positions only, never scale seats). */
    const altKeyRef = useRef(false);
    const [altHeld, setAltHeld] = useState(false);
    /** Invisible rect that Transformer resizes in spread mode — seats stay unscaled. */
    const spreadProxyNodeRef = useRef<Konva.Rect | null>(null);
    const spreadGestureActiveRef = useRef(false);
    /** Snapshot for Alt+resize: keep seat size/rotation, only remap x/y. */
    const spreadTransformRef = useRef<HallSpreadGesture | null>(null);

    const onSaveRef = useRef<((value: HallElementType[]) => void) | null>(null);

    useImperativeHandle(ref, () => ({
        open: (params: DialogHallEditorOpenParams) => {

            setOpen(true);

            onSaveRef.current = params.onSave ?? null;
            const raw = hallElementsFromInitValue(params.initValue ?? null);
            const hallUuidCoerce = coerceHallElementsSingleUUID(raw);
            if (!hallUuidCoerce.ok) {
                toast.error(Lang.key('hall_uuid_unique_error'));
            }
            setLayout(hallUuidCoerce.ok ? hallUuidCoerce.elements : raw);
            setSelectedIds([]);
            setLassoMode(false);
            setLassoDraft(null);
            setSelectionRect(null);
            hallUndoRef.current = [];
        },
        close: () => {
            spreadProxyNodeRef.current?.destroy();
            spreadProxyNodeRef.current = null;
            spreadGestureActiveRef.current = false;
            spreadTransformRef.current = null;
            altKeyRef.current = false;
            setAltHeld(false);
            setOpen(false);
        }
    }));

    const handleSave = () => {



        if (!runHallCheck()) {
            return;
        }


        onSaveRef.current?.(buildHallElementsArray(layout));
        setOpen(false);
    }

    const handleExportHallJson = () => {
        const data = buildHallElementsArray(layout);
        const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'hall.json';
        document.body.appendChild(a);
        a.click();
        a.remove();
        URL.revokeObjectURL(url);
    };

    // NOTE: Transformer нужно кормить нодами, которые уже попали в refs.
    // Поэтому вычисляем их в эффекте, а не во время рендера.
    const seatDefaults = useMemo(() => {
        const seats = layout.filter((e) => e.type === 'seat');
        const ws = seats.map((s) => seatSize(s).w);
        const hs = seats.map((s) => seatSize(s).h);
        const average = (values: number[], fallback: number) => {
            if (!values.length) {
                console.warn('[DialogHallEditor] Failed to calculate average seat size, using default size', {
                    reason: 'no-seat-sizes',
                    fallback,
                });
                return fallback;
            }
            const sum = values.reduce((acc, value) => acc + value, 0);
            const v = sum / values.length;
            if (!(Number.isFinite(v) && v > 0)) {
                console.warn('[DialogHallEditor] Failed to calculate average seat size, using default size', {
                    reason: 'invalid-average',
                    values,
                    fallback,
                });
            }
            return Number.isFinite(v) && v > 0 ? v : fallback;
        };
        const w = average(ws, HALL_SEAT_SIZE);
        const h = average(hs, HALL_SEAT_SIZE);
        return {
            w,
            h,
            halfW: w / 2,
            halfH: h / 2,
            tol: Math.min(w, h) / 2,
            gapX: HALL_SEAT_GAP,
            gapY: HALL_SEAT_GAP,
        };
    }, [layout]);

    const seatDraftStats = useMemo(() => {
        if (!rectPlacementDraft || rectPlacementDraft.mode !== 'seats') {
            return null;
        }
        return buildSeatDraftGrid(rectPlacementDraft, seatDefaults);
    }, [rectPlacementDraft, seatDefaults]);
    const blockPlacementPreview = useMemo(() => {
        if (!rectPlacementDraft || rectPlacementDraft.mode !== 'block') {
            return null;
        }
        const d = rectPlacementDraft;
        const left = Math.min(d.startX, d.currentX);
        const top = Math.min(d.startY, d.currentY);
        let width = Math.abs(d.currentX - d.startX);
        let height = Math.abs(d.currentY - d.startY);
        const base = PALETTE[d.blockType];
        if (width < 10) {
            width = base.width;
        }
        if (height < 10) {
            height = base.height;
        }
        return { x: left, y: top, width, height, blockType: d.blockType };
    }, [rectPlacementDraft]);
    const seatDraftPreviewSeats = useMemo(() => {
        if (!seatDraftStats) {
            return [];
        }
        return seatDraftStats.items;
    }, [seatDraftStats]);
    const normalizedSelectionRect = useMemo(() => {
        if (!selectionRect) {
            return null;
        }
        return {
            x: Math.min(selectionRect.startX, selectionRect.currentX),
            y: Math.min(selectionRect.startY, selectionRect.currentY),
            width: Math.abs(selectionRect.currentX - selectionRect.startX),
            height: Math.abs(selectionRect.currentY - selectionRect.startY),
        };
    }, [selectionRect]);
    const selectedSeats = useMemo(
        () => layout.filter((e) => e.type === 'seat' && selectedIds.includes(e._id)),
        [layout, selectedIds]
    );
    const selectedSingleElement = useMemo(() => {
        if (selectedIds.length !== 1) {
            return null;
        }
        return findInLayout(layout, selectedIds[0]!) ?? null;
    }, [layout, selectedIds]);
    const showHallEditStage = selectedSingleElement?.type === 'stage';
    const showHallEditSector = isHallNamedSectorType(selectedSingleElement?.type);
    const showHallSeatActions = selectedSeats.length > 0;
    const selectedElements = useMemo(
        () => selectedIds.map((id) => findInLayout(layout, id)).filter((x): x is any => x != null),
        [layout, selectedIds]
    );
    const spreadModeArmed = altHeld
        && selectedElements.length >= 2
        && selectedElements.every((el) => el.type === 'seat');
    const layoutSectorsAndZones = useMemo(
        () => layout.filter((e) => isHallNamedSectorType(e.type)),
        [layout],
    );
    const hallSummaryText = useMemo(() => buildHallSummaryText(layout), [layout]);
    const { width: stageW, height: stageH } = useMemo(
        () => resolveHallCanvasSize(layout),
        [layout],
    );
    const layoutBlocks = useMemo(
        () => layout.filter((e) => (
            e.type === 'stage' || e.type === 'table' || e.type === 'wall' || e.type === 'seat'
        )),
        [layout],
    );
    const seatSmartLabels = useMemo(
        () => buildHallSeatSmartLabels(layout.filter((e) => e.type === 'seat')),
        [layout],
    );
    const selectionBounds = useMemo(() => {
        if (selectedElements.length < 2) {
            return null;
        }
        let minX = Number.POSITIVE_INFINITY;
        let minY = Number.POSITIVE_INFINITY;
        let maxX = Number.NEGATIVE_INFINITY;
        let maxY = Number.NEGATIVE_INFINITY;
        selectedElements.forEach((el) => {
            const b = hallElementBounds(el);
            minX = Math.min(minX, b.x);
            minY = Math.min(minY, b.y);
            maxX = Math.max(maxX, b.x + b.width);
            maxY = Math.max(maxY, b.y + b.height);
        });
        if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(maxX) || !Number.isFinite(maxY)) {
            return null;
        }
        return {
            x: minX,
            y: minY,
            width: Math.max(1, maxX - minX),
            height: Math.max(1, maxY - minY),
        };
    }, [selectedElements]);

    const hallEditorGridLines = useMemo(() => {
        const { step, stroke, strokeWidth } = HALL_EDITOR_GRID;
        const nodes: React.ReactNode[] = [];
        let k = 0;
        for (let x = 0; x <= stageW; x += step) {
            nodes.push(
                <Line
                    key={`hall-grid-v-${k++}`}
                    points={[x, 0, x, stageH]}
                    stroke={stroke}
                    strokeWidth={strokeWidth}
                    listening={false}
                    perfectDrawEnabled={false}
                />
            );
        }
        for (let y = 0; y <= stageH; y += step) {
            nodes.push(
                <Line
                    key={`hall-grid-h-${k++}`}
                    points={[0, y, stageW, y]}
                    stroke={stroke}
                    strokeWidth={strokeWidth}
                    listening={false}
                    perfectDrawEnabled={false}
                />
            );
        }
        return nodes;
    }, [stageW, stageH]);

    useEffect(() => {
        if (!trRef.current) {
            return;
        }

        const destroySpreadProxy = () => {
            const proxy = spreadProxyNodeRef.current;
            if (!proxy) {
                return;
            }
            proxy.destroy();
            spreadProxyNodeRef.current = null;
        };

        const rafId = window.requestAnimationFrame(() => {
            const tr = trRef.current;
            if (!tr) {
                return;
            }

            const selectedNodes = selectedIds
                .map((id) => nodeRefs.current[id])
                .filter(Boolean);
            const elements = layoutRef.current.filter((el) => selectedIds.includes(el._id));
            const allSeats = elements.length >= 2 && elements.every((el) => el.type === 'seat');
            const useSpreadProxy = altHeld && allSeats;
            const bounds = useSpreadProxy ? hallElementsAABB(elements) : null;

            if (useSpreadProxy && bounds) {
                let proxy = spreadProxyNodeRef.current;
                if (!proxy) {
                    const layer = tr.getLayer();
                    if (!layer) {
                        tr.nodes(selectedNodes);
                        tr.getLayer()?.batchDraw();
                        return;
                    }
                    proxy = new Konva.Rect({
                        name: '__hallSpreadProxy',
                        opacity: 0,
                        fillEnabled: false,
                        strokeEnabled: false,
                        listening: true,
                    });
                    layer.add(proxy);
                    spreadProxyNodeRef.current = proxy;
                }
                if (!spreadGestureActiveRef.current) {
                    proxy.setAttrs({
                        x: bounds.x,
                        y: bounds.y,
                        width: bounds.width,
                        height: bounds.height,
                        scaleX: 1,
                        scaleY: 1,
                        rotation: 0,
                    });
                }
                tr.nodes([proxy]);
            } else {
                destroySpreadProxy();
                spreadGestureActiveRef.current = false;
                spreadTransformRef.current = null;
                tr.nodes(selectedNodes);
            }
            tr.getLayer()?.batchDraw();
        });

        return () => window.cancelAnimationFrame(rafId);
    }, [selectedIds, layout, altHeld]);

    useEffect(() => {
        if (!hallCheckFlash) {
            return;
        }
        let raf = 0;
        const start = hallCheckFlash.startedAt;
        const tick = () => {
            const elapsed = Date.now() - start;
            if (elapsed >= HALL_CHECK_FLASH_MS) {
                setHallCheckFlash(null);
                setHallCheckPulseMs(0);
                return;
            }
            setHallCheckPulseMs(elapsed);
            raf = requestAnimationFrame(tick);
        };
        raf = requestAnimationFrame(tick);
        return () => cancelAnimationFrame(raf);
    }, [hallCheckFlash]);

    const clampZoom = (value: number) => Math.min(HALL_ZOOM.max, Math.max(HALL_ZOOM.min, value));

    // React wheel listeners are passive by default → preventDefault() would not work.
    // Zoom only on pinch / Ctrl+wheel; plain wheel keeps wrapper scroll.
    useEffect(() => {
        if (!open) {
            return;
        }
        const el = wrapperRef.current;
        if (!el) {
            return;
        }
        const handler = (e: WheelEvent) => {
            // Trackpad pinch and Ctrl+mouse-wheel report ctrlKey; leave normal scroll alone.
            if (!e.ctrlKey) {
                return;
            }
            e.preventDefault();
            const next = clampZoom(zoomRef.current * Math.exp(-e.deltaY * 0.009));
            setZoom(Number(next.toFixed(2)));
        };
        el.addEventListener('wheel', handler, { passive: false });
        return () => el.removeEventListener('wheel', handler);
    }, [open]);

    const rectsIntersect = (
        a: { x: number; y: number; width: number; height: number },
        b: { x: number; y: number; width: number; height: number }
    ) => !(
        a.x > b.x + b.width
        || a.x + a.width < b.x
        || a.y > b.y + b.height
        || a.y + a.height < b.y
    );

    const violatesNoSeatsSectorRuleLayout = (next: any[]) => {
        const seats = next.filter((e) => e.type === 'seat');
        const noSeatSectors = next.filter((e) => e.type === 'zone');
        if (!seats.length || !noSeatSectors.length) {
            return false;
        }
        for (const s of seats) {
            const { w, h } = seatSize(s);
            const seatRect = hallCenteredRectBounds(s.x, s.y, w, h, s.rotation ?? 0);
            for (const z of noSeatSectors) {
                const zoneRect = { x: z.x, y: z.y, width: z.width, height: z.height };
                if (rectIntersectsRect(seatRect, zoneRect)) {
                    return true;
                }
            }
        }
        return false;
    };

    /** Проверка по плоскому списку координат (после перетаскивания Konva). */
    const violatesNoSeatsSectorRule = (nextFlat: any[]) => (
        violatesNoSeatsSectorRuleLayout(nextFlat)
    );

    const runHallCheck = (): boolean => {
        const uuidRes = coerceHallElementsSingleUUID(layout);
        if (!uuidRes.ok) {
            toast.error(Lang.key('hall_uuid_unique_error'));
            return false;
        }
        const dupIds = collectHallDuplicateIdsFromLayout(layout);
        const ids = Array.from(new Set([...dupIds]));
        if (!ids.length) {
            setHallCheckFlash(null);
            setHallCheckPulseMs(0);
            toast.success(Lang.key('hall_without_errors'));
            return true;
        }
        setHallCheckFlash({ ids, startedAt: Date.now() });
        const parts: string[] = [];
        if (dupIds.length) {
            parts.push(Lang.key('duplicates_of_seats_or_sectors'));
        }
        toast.error(parts.length ? `${Lang.key('hall_with_errors')}: ${parts.join(', ')}` : Lang.key('hall_with_errors_settings'));
        return false;
    };

    const hallCheckOverlayOpacity = (t: number) => {
        const fadeIn = Math.min(1, t / 180);
        const fadeOut = t > HALL_CHECK_FLASH_MS - 520 ? (HALL_CHECK_FLASH_MS - t) / 520 : 1;
        const pulse = 0.42 + 0.58 * (0.5 + 0.5 * Math.sin(t * 0.014));
        return Math.max(0, Math.min(1, fadeIn * fadeOut * pulse));
    };

    const pushHallUndoSnapshot = useCallback(() => {
        hallUndoRef.current.push({
            layout: structuredClone(layoutRef.current),
        });
        while (hallUndoRef.current.length > HALL_UNDO_MAX) {
            hallUndoRef.current.shift();
        }
    }, []);

    const commitPreparedHallLayout = useCallback((next: any[]) => {
        const r = coerceHallElementsSingleUUID(next);
        if (!r.ok) {
            toast.error(Lang.key('hall_uuid_unique_error'));
            return false;
        }
        pushHallUndoSnapshot();
        setLayout(r.elements);
        return true;
    }, [pushHallUndoSnapshot]);

    const handleImportHallJsonFile = useCallback(async (file: File) => {
        let parsed: unknown;
        try {
            parsed = JSON.parse(await file.text());
        } catch {
            toast.error(Lang.key('hall_import_json_error'));
            return;
        }
        if (!isHallImportJsonValue(parsed)) {
            toast.error(Lang.key('hall_import_json_error'));
            return;
        }
        const next = hallElementsFromInitValue(parsed);
        if (!commitPreparedHallLayout(next)) {
            return;
        }
        setSelectedIds([]);
        setHallImportOpen(false);
        toast.success(Lang.key('hall_import_json_success'));
    }, [commitPreparedHallLayout]);

    const patchLayout = useCallback((
        fn: (els: any[]) => any[],
        reconcileAfter = true,
    ) => {
        const prev = layoutRef.current;
        let next = fn(prev);
        if (reconcileAfter) {
            next = reconcileSeatSectorsLayout(next);
        }
        next = sortHallElementsByPaintOrder(next);
        return commitPreparedHallLayout(next);
    }, [commitPreparedHallLayout]);

    const commitHallLayoutFromProposed = useCallback((proposed: any[]) => {
        const next = sortHallElementsByPaintOrder(reconcileSeatSectorsLayout(proposed));
        return commitPreparedHallLayout(next);
    }, [commitPreparedHallLayout]);

    const undoLastHallChange = useCallback(() => {
        const stack = hallUndoRef.current;
        if (!stack.length) {
            toast.error(Lang.key('hall_undo_impossible'));
            return;
        }
        const snap = stack[stack.length - 1]!;
        hallUndoRef.current = stack.slice(0, -1);
        setLayout(structuredClone(snap.layout));
        setSelectedIds((ids) => ids.filter((id) => snap.layout.some((e) => e._id === id)));
    }, []);

    const duplicateSelected = useCallback(() => {
        if (!selectedIds.length) {
            return;
        }
        const sources = selectedIds
            .map((id) => findInLayout(layout, id))
            .filter((x): x is any => x != null && x.type !== 'canvas');
        if (!sources.length) {
            return;
        }
        const copies: any[] = sources.map((source) => ({
            ...source,
            _id: makeHallElementId(),
            x: source.x + 24,
            y: source.y + 24,
        }));
        patchLayout((b) => appendManyToLayout(b, copies), true);
        setSelectedIds(copies.map((copy) => copy._id));
    }, [layout, selectedIds, patchLayout]);

    const deleteSelected = useCallback(() => {
        if (!selectedIds.length) {
            return;
        }
        const ids = new Set(
            selectedIds.filter((id) => {
                const el = findInLayout(layoutRef.current, id);
                return el && el.type !== 'canvas';
            }),
        );
        if (!ids.size) {
            return;
        }
        patchLayout((b) => filterIdsFromLayout(b, ids), true);
        setSelectedIds([]);
    }, [selectedIds, patchLayout]);

    const finishPolygonDraft = useCallback((absPoints: HallPolygonPoint[]) => {
        const layoutGeom = hallPolygonLayoutFromAbsolute(absPoints);
        if (!layoutGeom) {
            return;
        }
        const newElement = {
            ...createElement('sector_polygon', layoutGeom.x, layoutGeom.y),
            ...layoutGeom,
            label: 'Новый сектор',
        };
        patchLayout((b) => appendToLayoutByType(b, newElement), true);
        setSelectedIds([newElement._id]);
        setPolygonDraft(null);
        setToolForClickCreate(null);
    }, [patchLayout]);

    useEffect(() => {
        if (!open) {
            altKeyRef.current = false;
            setAltHeld(false);
            spreadProxyNodeRef.current?.destroy();
            spreadProxyNodeRef.current = null;
            spreadGestureActiveRef.current = false;
            spreadTransformRef.current = null;
            return;

        }
        const syncAlt = (e: KeyboardEvent | MouseEvent) => {
            const next = e.altKey;
            if (altKeyRef.current === next) {
                return;
            }
            altKeyRef.current = next;
            setAltHeld(next);
        };
        const onBlur = () => {
            altKeyRef.current = false;
            setAltHeld(false);
        };
        window.addEventListener('keydown', syncAlt, true);
        window.addEventListener('keyup', syncAlt, true);
        window.addEventListener('mousedown', syncAlt, true);
        window.addEventListener('mousemove', syncAlt, true);
        window.addEventListener('mouseup', syncAlt, true);
        window.addEventListener('blur', onBlur);
        return () => {
            window.removeEventListener('keydown', syncAlt, true);
            window.removeEventListener('keyup', syncAlt, true);
            window.removeEventListener('mousedown', syncAlt, true);
            window.removeEventListener('mousemove', syncAlt, true);
            window.removeEventListener('mouseup', syncAlt, true);
            window.removeEventListener('blur', onBlur);
            altKeyRef.current = false;
        };
    }, [open]);

    useEffect(() => {
        const isTypingTarget = (target: EventTarget | null) => {
            const el = target as HTMLElement | null;
            return Boolean(
                el
                && (
                    el.tagName === 'INPUT'
                    || el.tagName === 'TEXTAREA'
                    || el.isContentEditable
                ),
            );
        };

        const paintSeatHotkeyBuffer = (
            seatId: string,
            mode: 'row' | 'number',
            buffer: string,
        ) => {
            const field = mode === 'row' ? 'seatRow' : 'seatNumber';
            setLayout((prev) => sortHallElementsByPaintOrder(prev.map((item) => {
                if (item._id !== seatId || item.type !== 'seat') {
                    return item;
                }
                return {
                    ...item,
                    [field]: buffer.length ? buffer : undefined,
                };
            })));
        };

        const cancelSeatHotkeyEdit = () => {
            const session = seatHotkeyEditRef.current;
            if (!session) {
                return;
            }
            if (session.dirty) {
                setLayout(structuredClone(session.layoutBefore));
            }
            seatHotkeyEditRef.current = null;
            setSeatHotkeyEdit(null);
        };

        const commitSeatHotkeyEdit = () => {
            const session = seatHotkeyEditRef.current;
            if (!session) {
                return;
            }
            const trimmed = session.buffer.trim();
            if (!session.dirty || !trimmed) {
                cancelSeatHotkeyEdit();
                return;
            }
            paintSeatHotkeyBuffer(session.seatId, session.mode, trimmed);
            hallUndoRef.current.push({
                layout: structuredClone(session.layoutBefore),
            });
            while (hallUndoRef.current.length > HALL_UNDO_MAX) {
                hallUndoRef.current.shift();
            }
            seatHotkeyEditRef.current = null;
            setSeatHotkeyEdit(null);
        };

        const onKeyDown = (e: KeyboardEvent) => {
            if (isTypingTarget(e.target)) {
                return;
            }
            if (!open) {
                return;
            }

            const hotkeySession = seatHotkeyEditRef.current;
            if (hotkeySession) {
                if (e.key === 'Escape') {
                    e.preventDefault();
                    cancelSeatHotkeyEdit();
                    return;
                }
                if (e.code === 'KeyR' || e.code === 'KeyS') {
                    e.preventDefault();
                    return;
                }
                if (e.key === 'Backspace') {
                    e.preventDefault();
                    if (!hotkeySession.dirty) {
                        return;
                    }
                    hotkeySession.buffer = hotkeySession.buffer.slice(0, -1);
                    setSeatHotkeyEdit({
                        mode: hotkeySession.mode,
                        seatId: hotkeySession.seatId,
                        buffer: hotkeySession.buffer,
                    });
                    paintSeatHotkeyBuffer(
                        hotkeySession.seatId,
                        hotkeySession.mode,
                        hotkeySession.buffer,
                    );
                    return;
                }
                if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
                    e.preventDefault();
                    hotkeySession.buffer += e.key;
                    hotkeySession.dirty = true;
                    setSeatHotkeyEdit({
                        mode: hotkeySession.mode,
                        seatId: hotkeySession.seatId,
                        buffer: hotkeySession.buffer,
                    });
                    paintSeatHotkeyBuffer(
                        hotkeySession.seatId,
                        hotkeySession.mode,
                        hotkeySession.buffer,
                    );
                }
                return;
            }

            const dialogBlocked = seatSettingsOpen
                || seatNumberingOpen
                || seatRowsOpen
                || sectorSettingsOpen
                || stageSettingsOpen
                || canvasSettingsOpen
                || hallImportOpen
                || hallSummaryOpen;
            if (dialogBlocked) {
                return;
            }

            if (polygonDraft || toolForClickCreate === 'sector_polygon') {
                if (e.key === 'Escape') {
                    e.preventDefault();
                    setPolygonDraft(null);
                    setToolForClickCreate(null);
                    return;
                }
                if (e.key === 'Enter' && polygonDraft && polygonDraft.points.length >= 3) {
                    e.preventDefault();
                    finishPolygonDraft(polygonDraft.points);
                    return;
                }
            }

            if (lassoMode || lassoDraft) {
                if (e.key === 'Escape') {
                    e.preventDefault();
                    setLassoDraft(null);
                    setLassoMode(false);
                    return;
                }
            }

            if (
                (e.code === 'KeyR' || e.code === 'KeyS')
                && !e.ctrlKey
                && !e.metaKey
                && !e.altKey
                && !e.repeat
            ) {
                const onlySeat = selectedIds.length === 1
                    ? layoutRef.current.find((item) => item._id === selectedIds[0] && item.type === 'seat')
                    : null;
                if (!onlySeat) {
                    return;
                }
                e.preventDefault();
                const mode = e.code === 'KeyR' ? 'row' : 'number';
                seatHotkeyEditRef.current = {
                    mode,
                    seatId: onlySeat._id,
                    buffer: '',
                    dirty: false,
                    layoutBefore: structuredClone(layoutRef.current),
                };
                setSeatHotkeyEdit({
                    mode,
                    seatId: onlySeat._id,
                    buffer: '',
                });
                return;
            }

            if (!selectedIds.length) {
                return;
            }
            if (e.key === 'Delete' || e.key === 'Backspace') {
                deleteSelected();
            }
            if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'd') {
                e.preventDefault();
                duplicateSelected();
            }
        };

        const onKeyUp = (e: KeyboardEvent) => {
            const session = seatHotkeyEditRef.current;
            if (!session) {
                return;
            }
            if (session.mode === 'row' && e.code === 'KeyR') {
                e.preventDefault();
                commitSeatHotkeyEdit();
            }
            if (session.mode === 'number' && e.code === 'KeyS') {
                e.preventDefault();
                commitSeatHotkeyEdit();
            }
        };

        const onBlur = () => {
            if (seatHotkeyEditRef.current) {
                cancelSeatHotkeyEdit();
            }
        };

        window.addEventListener('keydown', onKeyDown);
        window.addEventListener('keyup', onKeyUp);
        window.addEventListener('blur', onBlur);
        return () => {
            window.removeEventListener('keydown', onKeyDown);
            window.removeEventListener('keyup', onKeyUp);
            window.removeEventListener('blur', onBlur);
        };
    }, [
        open,
        selectedIds,
        deleteSelected,
        duplicateSelected,
        seatSettingsOpen,
        seatNumberingOpen,
        seatRowsOpen,
        sectorSettingsOpen,
        stageSettingsOpen,
        canvasSettingsOpen,
        hallImportOpen,
        hallSummaryOpen,
        polygonDraft,
        toolForClickCreate,
        finishPolygonDraft,
        lassoMode,
        lassoDraft,
    ]);

    useEffect(() => {
        const session = seatHotkeyEditRef.current;
        if (!session) {
            return;
        }
        if (selectedIds.length !== 1 || selectedIds[0] !== session.seatId) {
            if (session.dirty) {
                setLayout(structuredClone(session.layoutBefore));
            }
            seatHotkeyEditRef.current = null;
            setSeatHotkeyEdit(null);
        }
    }, [selectedIds]);

    const getPointerPositionInStage = () => {
        const stage = stageRef.current;
        if (!stage) {
            return { x: 40, y: 40 };
        }
        const pos = stage.getPointerPosition();
        if (!pos) {
            return { x: 40, y: 40 };
        }
        const transform = stage.getAbsoluteTransform().copy();
        transform.invert();
        return transform.point(pos);
    };

    const addElement = (type: string, x: number, y: number) => {
        const newElement = createElement(type, x, y);
        patchLayout((b) => appendToLayoutByType(b, newElement), true);
        setSelectedIds([newElement._id]);
    };

    const bakeSectorPolygonFromNode = (old: any, node: any) => {
        const scaleX = node.scaleX();
        const scaleY = node.scaleY();
        const scaledPoints = scaleHallPolygonRelativePoints(
            Array.isArray(old.points) ? old.points : [],
            scaleX,
            scaleY,
        );
        node.scaleX(1);
        node.scaleY(1);
        node.points(scaledPoints);
        const size = hallPolygonLocalSize(scaledPoints);
        const next = {
            ...old,
            x: node.x(),
            y: node.y(),
            rotation: node.rotation(),
            points: scaledPoints,
            width: size.width,
            height: size.height,
        };
        return next;
    };

    const createSeatsInRect = (draft: Extract<RectPlacementDraft, { mode: 'seats' }>) => {
        const seatGrid = buildSeatDraftGrid(draft, seatDefaults);
        const seats: any[] = [];
        seatGrid.items.forEach((previewSeat) => {
            const seat = createElement('seat', previewSeat.x, previewSeat.y);
            seat.width = previewSeat.w;
            seat.height = previewSeat.h;
            seats.push(seat);
        });

        if (!seats.length) {
            return;
        }
        console.info('[DialogHallEditor] Creating seats from preview grid', {
            seatDefaults,
            grid: {
                cols: seatGrid.cols,
                rows: seatGrid.rows,
                total: seatGrid.total,
                width: seatGrid.width,
                height: seatGrid.height,
            },
            createdSeatSize: seats[0] ? { width: seats[0].width, height: seats[0].height } : null,
        });
        patchLayout((b) => appendManyToLayout(b, seats), true);
        setSelectedIds(seats.map((seat) => seat._id));
    };

    const createBlockInRect = (draft: Extract<RectPlacementDraft, { mode: 'block' }>) => {
        const left = Math.min(draft.startX, draft.currentX);
        const top = Math.min(draft.startY, draft.currentY);
        let width = Math.abs(draft.currentX - draft.startX);
        let height = Math.abs(draft.currentY - draft.startY);
        const base = PALETTE[draft.blockType];
        if (width < 10) {
            width = base.width;
        }
        if (height < 10) {
            height = base.height;
        }
        const newElement = createElement(draft.blockType, left, top);
        newElement.width = width;
        newElement.height = height;
        patchLayout((b) => appendToLayoutByType(b, newElement), true);
        setSelectedIds([newElement._id]);
    };

    const updateElement = (id: string, updater: (oldItem: any) => any) => {
        patchLayout((b) => mapItemInLayout(b, id, updater), true);
    };
    const applySeatNumbering = () => {
        if (!selectedSeats.length) {
            setSeatNumberingOpen(false);
            return;
        }
        const parsed = parseHallSeatStart(seatNumberStart);
        if (!parsed) {
            toast.error(Lang.key('hall_seat_start_invalid'));
            return;
        }

        const tol = seatDefaults.tol;
        const lines = hallSeatsGroupedLinesOrdered(selectedSeats, seatNumberFillDirection, tol);
        const seatNumbers = new Map<string, string>();
        lines.forEach((line, lineIdx) => {
            const label = hallSeatLabelAt(parsed, lineIdx);
            line.forEach((seat) => {
                seatNumbers.set(seat._id, label);
            });
        });

        patchLayout((b) => mapAllItemsInLayout(b, (item) => (
            seatNumbers.has(item._id)
                ? { ...item, seatNumber: seatNumbers.get(item._id)! }
                : item
        )), true);
        setSeatNumberingOpen(false);
    };
    const applySeatRows = () => {
        if (!selectedSeats.length) {
            setSeatRowsOpen(false);
            return;
        }
        const parsed = parseHallSeatStart(seatRowStart);
        if (!parsed) {
            toast.error(Lang.key('hall_seat_start_invalid'));
            return;
        }

        const tol = seatDefaults.tol;
        const lines = hallSeatsGroupedLinesOrdered(selectedSeats, seatRowFillDirection, tol);
        const seatRowsMap = new Map<string, string>();
        lines.forEach((line, lineIdx) => {
            const label = hallSeatLabelAt(parsed, lineIdx);
            line.forEach((seat) => {
                seatRowsMap.set(seat._id, label);
            });
        });

        patchLayout((b) => mapAllItemsInLayout(b, (item) => (
            seatRowsMap.has(item._id)
                ? { ...item, seatRow: seatRowsMap.get(item._id)! }
                : item
        )), true);
        setSeatRowsOpen(false);
    };

    const openSeatSettings = (seatId: string) => {
        const seat = layout.find((s) => s.type === 'seat' && s._id === seatId);
        if (!seat) {
            return;
        }
        setSelectedIds([seatId]);
        setSeatSettingsSeatId(seatId);
        setSeatSettingsSeatNumber(seat.seatNumber ?? '');
        setSeatSettingsSeatRow(seat.seatRow ?? '');
        setSeatSettingsOpen(true);
    };

    const saveSeatSettings = () => {
        if (!seatSettingsSeatId) {
            setSeatSettingsOpen(false);
            return;
        }
        const nextSeatNumber = seatSettingsSeatNumber.trim() ? seatSettingsSeatNumber.trim() : undefined;
        const nextSeatRow = seatSettingsSeatRow.trim() ? seatSettingsSeatRow.trim() : undefined;

        patchLayout((b) => mapAllItemsInLayout(b, (item) => {
            if (item._id !== seatSettingsSeatId || item.type !== 'seat') {
                return item;
            }
            return {
                ...item,
                seatNumber: nextSeatNumber,
                seatRow: nextSeatRow,
            };
        }), true);
        setSeatSettingsOpen(false);
    };

    const openSectorSettings = (sectorId: string) => {
        const sector = layout.find((s) => isHallNamedSectorType(s.type) && s._id === sectorId);
        if (!sector) {
            return;
        }
        setSelectedIds([sectorId]);
        setSectorSettingsId(sectorId);
        const isNoSeats = sector.type === 'zone';
        setSectorSettingsIsNoSeats(isNoSeats);
        setSectorSettingsName(sector.label || (isNoSeats ? 'Новый сектор без мест' : 'Новый сектор'));
        setSectorSettingsOpen(true);
    };

    const saveSectorSettings = () => {
        if (!sectorSettingsId) {
            setSectorSettingsOpen(false);
            return;
        }
        const name = sectorSettingsName.trim() || (sectorSettingsIsNoSeats ? 'Новый сектор без мест' : 'Новый сектор');
        patchLayout((b) => mapAllItemsInLayout(b, (item) => {
            if (item._id !== sectorSettingsId) {
                return item;
            }
            if (isHallNamedSectorType(item.type)) {
                return { ...item, label: name };
            }
            return item;
        }), true);
        setSectorSettingsOpen(false);
    };

    const isStage = (t: string) => t === 'stage';

    const openStageSettings = (stageId: string) => {
        const stage = layout.find((s) => s.type === 'stage' && s._id === stageId);
        if (!stage) {
            return;
        }
        setSelectedIds([stageId]);
        setStageSettingsId(stageId);
        setStageSettingsName(stage.label || 'Сцена');
        setStageSettingsOpen(true);
    };

    const saveStageSettings = () => {
        if (!stageSettingsId) {
            setStageSettingsOpen(false);
            return;
        }
        const name = stageSettingsName.trim() || 'Сцена';
        patchLayout((b) => mapAllItemsInLayout(b, (item) => (
            item._id === stageSettingsId && isStage(item.type as string)
                ? { ...item, label: name }
                : item
        )), false);
        setStageSettingsOpen(false);
    };

    const openCanvasSettings = () => {
        const size = resolveHallCanvasSize(layout);
        setCanvasSettingsWidth(String(size.width));
        setCanvasSettingsHeight(String(size.height));
        setCanvasSettingsOpen(true);
    };

    const saveCanvasSettings = () => {
        const nextW = Math.max(
            HALL_CANVAS_SIZE_MIN,
            Math.round(Number(canvasSettingsWidth)) || HALL_CANVAS_SIZE_MIN,
        );
        const nextH = Math.max(
            HALL_CANVAS_SIZE_MIN,
            Math.round(Number(canvasSettingsHeight)) || HALL_CANVAS_SIZE_MIN,
        );
        patchLayout((b) => {
            const withCanvas = ensureHallCanvas(b);
            return withCanvas.map((item) => (
                item.type === 'canvas'
                    ? { ...item, x: 0, y: 0, width: nextW, height: nextH, rotation: 0 }
                    : item
            ));
        }, false);
        setCanvasSettingsWidth(String(nextW));
        setCanvasSettingsHeight(String(nextH));
        setCanvasSettingsOpen(false);
    };

    return <>
        <SystemWindowDialog
            open={open}
            onOpenChange={setOpen}
            width="100vw"
            height="100vh"
            allowFullscreen
            stackIndex={0}
            bodyClassName="p-0 overflow-hidden"

        >

            {open && <Grid className='h-full w-full'>

                <GridCol className='bg-white'>

                    <GridRow autoSize className="p-2 gap-2 flex  flex-wrap items-center justify-between">

                        <div className='flex items-center gap-2'>
                            <Button
                                size="sm"
                                type="button"
                                variant="outline"
                                tooltip={Lang.key('zoom_out')}
                                aria-label={Lang.key('zoom_out')}
                                onClick={() => setZoom((z) => clampZoom(Number((z - HALL_ZOOM.step).toFixed(2))))}
                            >
                                <ZoomOut />
                            </Button>

                            <Button
                                size="sm"
                                type="button"
                                variant="outline"
                                tooltip={Lang.key('zoom_in')}
                                aria-label={Lang.key('zoom_in')}
                                onClick={() => setZoom((z) => clampZoom(Number((z + HALL_ZOOM.step).toFixed(2))))}
                            >
                                <ZoomIn />
                            </Button>

                            <DropdownMenu>
                                <DropdownMenuTrigger asChild>
                                    <Button
                                        size="sm"
                                        type="button"
                                        variant="outline"
                                        tooltip={Lang.key('create_hall_element')}
                                        aria-label={Lang.key('create_hall_element')}
                                    >
                                        <PlusCircle />
                                    </Button>
                                </DropdownMenuTrigger>
                                <DropdownMenuContent className="w-[300px]" align="start">
                                    <DropdownMenuGroup>
                                        {(Object.keys(PALETTE) as string[]).map((type) => (
                                            <DropdownMenuItem
                                                key={type}
                                                style={{
                                                    backgroundColor: toolForClickCreate == type ? 'rgba(0, 0, 0, 0.05)' : 'transparent',
                                                }}
                                                onClick={() => {
                                                    if (toolForClickCreate == type) {
                                                        setToolForClickCreate(null);
                                                        setPolygonDraft(null);
                                                    } else {
                                                        setToolForClickCreate(type);
                                                        setPolygonDraft(null);
                                                        setLassoMode(false);
                                                        setLassoDraft(null);
                                                    }
                                                }}
                                            >
                                                {PALETTE[type].icon}
                                                {' '}
                                                {PALETTE[type].label}
                                            </DropdownMenuItem>
                                        ))}
                                    </DropdownMenuGroup>
                                </DropdownMenuContent>
                            </DropdownMenu>



                            <Button
                                size="sm"
                                type="button"
                                variant="outline"
                                tooltip="Размер полотна"
                                aria-label="Размер полотна"
                                onClick={() => openCanvasSettings()}
                            >
                                <Maximize2 />
                            </Button>

                            <Button
                                size="sm"
                                type="button"
                                variant={lassoMode ? 'default' : 'outline'}
                                tooltip={Lang.key('hall_lasso_select')}
                                aria-label={Lang.key('hall_lasso_select')}
                                onClick={() => {
                                    setLassoMode((v) => !v);
                                    setLassoDraft(null);
                                    setSelectionRect(null);
                                    setToolForClickCreate(null);
                                    setPolygonDraft(null);
                                    setRectPlacementDraft(null);
                                }}
                            >
                                <LassoSelect />
                            </Button>
                            <Button
                                size="sm"
                                type="button"
                                variant={!freezeSectorSelection ? 'outline' : 'default'}
                                tooltip={Lang.key('fix_hall_sector')}
                                aria-label={Lang.key('fix_hall_sector')}
                                onClick={() => setFreezeSectorSelection((v) => !v)}
                            >
                                <VaultIcon />
                            </Button>
                            <Button
                                size="sm"
                                type="button"
                                variant="outline"
                                tooltip={Lang.key('hall_undo')}
                                aria-label={Lang.key('hall_undo')}
                                onClick={() => undoLastHallChange()}
                            >
                                <Undo2 />
                            </Button>




                        </div>



                        <div className='flex items-center gap-2'>






                            {showHallEditStage && (
                                <Button
                                    size="sm"
                                    type="button"
                                    variant="outline"
                                    tooltip={Lang.key('hall_edit_selected_stage')}
                                    aria-label={Lang.key('hall_edit_selected_stage')}
                                    onClick={() => openStageSettings(selectedIds[0])}
                                >
                                    <Edit2Icon />
                                </Button>
                            )}

                            {showHallEditSector && (
                                <Button
                                    size="sm"
                                    type="button"
                                    variant="outline"
                                    tooltip={Lang.key('hall_edit_selected_sector')}
                                    aria-label={Lang.key('hall_edit_selected_sector')}
                                    onClick={() => openSectorSettings(selectedIds[0])}
                                >
                                    <Edit2Icon />
                                </Button>
                            )}

                            {showHallSeatActions && (
                                <>
                                    <Button
                                        size="sm"
                                        type="button"
                                        variant="outline"
                                        tooltip={Lang.key('hall_set_seat_rows')}
                                        aria-label={Lang.key('hall_set_seat_rows')}
                                        onClick={() => {
                                            setSeatRowFillDirection(HALL_SEAT_FILL_DIRECTION_DEFAULT_ROWS);
                                            setSeatRowsOpen(true);
                                        }}
                                    >
                                        <RowsIcon />
                                    </Button>
                                    <Button
                                        size="sm"
                                        type="button"
                                        variant="outline"
                                        tooltip={Lang.key('hall_set_seat_numbers')}
                                        aria-label={Lang.key('hall_set_seat_numbers')}
                                        onClick={() => {
                                            setSeatNumberFillDirection(HALL_SEAT_FILL_DIRECTION_DEFAULT_SEAT_NUMBERS);
                                            setSeatNumberingOpen(true);
                                        }}
                                    >
                                        <Grid2X2 />
                                    </Button>
                                    <Button
                                        size="sm"
                                        type="button"
                                        variant="outline"
                                        tooltip={Lang.key('hall_copy_selected_seats')}
                                        aria-label={Lang.key('hall_copy_selected_seats')}
                                        onClick={() => duplicateSelected()}
                                    >
                                        <CopyIcon />
                                    </Button>
                                    {seatHotkeyEdit && (
                                        <div className="text-xs text-slate-600 tabular-nums px-2 py-1 rounded-md bg-slate-100 border border-slate-200">
                                            {seatHotkeyEdit.mode === 'row'
                                                ? Lang.key('hall_seat_settings_row')
                                                : Lang.key('hall_seat_settings_place')}
                                            {': '}
                                            <span className="font-medium text-slate-900">
                                                {seatHotkeyEdit.buffer || '…'}
                                            </span>
                                        </div>
                                    )}
                                </>
                            )}

                            {selectedIds.length > 0 && (
                                <Button
                                    size="sm"
                                    type="button"
                                    variant="outline"
                                    tooltip={Lang.key('hall_delete_selected')}
                                    aria-label={Lang.key('hall_delete_selected')}
                                    onClick={() => deleteSelected()}
                                >
                                    <TrashIcon />
                                </Button>
                            )}





                        </div>


                        <div
                            className='flex items-center gap-2'

                        >
                            <Button
                                size="sm"
                                type="button"
                                variant="outline"
                                tooltip={Lang.key('hall_export_json')}
                                aria-label={Lang.key('hall_export_json')}
                                onClick={() => {
                                    handleExportHallJson();
                                }}
                            >
                                <Download />
                            </Button>

                            <Button
                                size="sm"
                                type="button"
                                variant="outline"
                                tooltip={Lang.key('hall_import_json')}
                                aria-label={Lang.key('hall_import_json')}
                                onClick={() => {
                                    setHallImportOpen(true);
                                }}
                            >
                                <Upload />
                            </Button>

                            <Button
                                size="sm"
                                type="button"
                                variant="outline"
                                tooltip={Lang.key('hall_check')}
                                aria-label={Lang.key('hall_check')}
                                onClick={() => {
                                    runHallCheck();
                                }}
                            >
                                <ClipboardCheck />
                            </Button>

                            <Button
                                size="sm"
                                type="button"
                                variant="outline"
                                tooltip={Lang.key('hall_summary')}
                                aria-label={Lang.key('hall_summary')}
                                onClick={() => setHallSummaryOpen(true)}
                            >
                                <Info />
                            </Button>

                            <Button
                                onClick={() => {

                                    handleSave();
                                }}
                                size="sm"
                                type="button"
                                variant="outline"
                                tooltip={Lang.key('hall_save_and_close')}
                                aria-label={Lang.key('hall_save_and_close')}
                            >
                                <Save />
                            </Button>
                        </div>

                        <div></div>


                    </GridRow>
                    <GridRow>

                        <div
                            ref={wrapperRef}
                            className="flex-1 border rounded-md overflow-auto bg-white flex"
                            onDragOver={(e) => e.preventDefault()}
                            onDrop={(e) => {
                                e.preventDefault();
                                const type = e.dataTransfer.getData('hall-element-type') as string;
                                if (!type || !(type in PALETTE)) {
                                    return;
                                }
                                const stage = stageRef.current;
                                if (!stage) {
                                    return;
                                }
                                stage.setPointersPositions(e as any);
                                const pos = getPointerPositionInStage();
                                addElement(type, pos.x, pos.y);
                            }}
                        >
                            <div
                                style={{
                                    width: stageW * zoom,
                                    height: stageH * zoom,
                                    background: HALL_CANVAS.background,
                                    outline: HALL_CANVAS.outline,
                                    outlineOffset: '0px',
                                    margin: 'auto',
                                }}
                            >
                                <Stage
                                    ref={stageRef}
                                    width={stageW * zoom}
                                    height={stageH * zoom}
                                    scaleX={zoom}
                                    scaleY={zoom}
                                    onMouseDown={(e) => {
                                        const clickedOnEmpty = e.target === e.target.getStage();
                                        if (clickedOnEmpty) {
                                            if (toolForClickCreate === 'sector_polygon' || polygonDraft) {
                                                const pos = getPointerPositionInStage();
                                                const snap = hallPolygonCloseSnapDistance();
                                                const points = polygonDraft?.points ?? [];
                                                if (
                                                    points.length >= 3
                                                    && Math.hypot(pos.x - points[0]!.x, pos.y - points[0]!.y) <= snap
                                                ) {
                                                    finishPolygonDraft(points);
                                                    return;
                                                }
                                                setPolygonDraft({
                                                    points: [...points, { x: pos.x, y: pos.y }],
                                                    cursor: { x: pos.x, y: pos.y },
                                                });
                                                return;
                                            }
                                            if (toolForClickCreate) {
                                                const pos = getPointerPositionInStage();
                                                if (toolForClickCreate === 'seat') {
                                                    setRectPlacementDraft({
                                                        mode: 'seats',
                                                        startX: pos.x,
                                                        startY: pos.y,
                                                        currentX: pos.x,
                                                        currentY: pos.y,
                                                    });
                                                } else if (
                                                    toolForClickCreate === 'stage'
                                                    || toolForClickCreate === 'table'
                                                    || toolForClickCreate === 'zone'
                                                    || toolForClickCreate === 'wall'
                                                ) {
                                                    setRectPlacementDraft({
                                                        mode: 'block',
                                                        blockType: toolForClickCreate,
                                                        startX: pos.x,
                                                        startY: pos.y,
                                                        currentX: pos.x,
                                                        currentY: pos.y,
                                                    });
                                                } else {
                                                    addElement(toolForClickCreate, pos.x, pos.y);
                                                    setToolForClickCreate(null);
                                                }
                                            } else if (lassoMode) {
                                                const pos = getPointerPositionInStage();
                                                setSelectionRect(null);
                                                setLassoDraft({
                                                    points: [{ x: pos.x, y: pos.y }],
                                                    appendToSelection: e.evt.shiftKey || e.evt.metaKey || e.evt.ctrlKey,
                                                });
                                            } else {
                                                const pos = getPointerPositionInStage();
                                                setSelectionRect({
                                                    startX: pos.x,
                                                    startY: pos.y,
                                                    currentX: pos.x,
                                                    currentY: pos.y,
                                                    appendToSelection: e.evt.shiftKey || e.evt.metaKey || e.evt.ctrlKey,
                                                });
                                            }
                                        }
                                    }}
                                    onDblClick={(e) => {
                                        if (!(toolForClickCreate === 'sector_polygon' || polygonDraft)) {
                                            return;
                                        }
                                        if (e.target !== e.target.getStage()) {
                                            return;
                                        }
                                        if (polygonDraft && polygonDraft.points.length >= 3) {
                                            finishPolygonDraft(polygonDraft.points);
                                        }
                                    }}
                                    onMouseMove={() => {
                                        const pos = getPointerPositionInStage();
                                        setPolygonDraft((prev) => (prev ? {
                                            ...prev,
                                            cursor: { x: pos.x, y: pos.y },
                                        } : null));
                                        setRectPlacementDraft((prev) => {
                                            if (!prev) {
                                                return null;
                                            }
                                            return {
                                                ...prev,
                                                currentX: pos.x,
                                                currentY: pos.y,
                                            };
                                        });
                                        setLassoDraft((prev) => {
                                            if (!prev) {
                                                return null;
                                            }
                                            const last = prev.points[prev.points.length - 1];
                                            if (last) {
                                                const dx = pos.x - last.x;
                                                const dy = pos.y - last.y;
                                                if ((dx * dx + dy * dy) < LASSO_POINT_MIN_DIST * LASSO_POINT_MIN_DIST) {
                                                    return prev;
                                                }
                                            }
                                            return {
                                                ...prev,
                                                points: [...prev.points, { x: pos.x, y: pos.y }],
                                            };
                                        });
                                        setSelectionRect((prev) => (prev ? {
                                            ...prev,
                                            currentX: pos.x,
                                            currentY: pos.y,
                                        } : null));
                                    }}
                                    onMouseUp={() => {
                                        if (rectPlacementDraft) {
                                            if (rectPlacementDraft.mode === 'seats') {
                                                createSeatsInRect(rectPlacementDraft);
                                            } else {
                                                createBlockInRect(rectPlacementDraft);
                                            }
                                            setRectPlacementDraft(null);
                                            setToolForClickCreate(null);
                                            return;
                                        }
                                        if (lassoDraft) {
                                            const points = lassoDraft.points;
                                            const bbox = hallPolygonBBox(points);
                                            const tooSmall = !bbox
                                                || points.length < 3
                                                || bbox.width < LASSO_MIN_BBOX
                                                || bbox.height < LASSO_MIN_BBOX;
                                            if (tooSmall) {
                                                if (!lassoDraft.appendToSelection) {
                                                    setSelectedIds([]);
                                                }
                                                setLassoDraft(null);
                                                return;
                                            }
                                            const selectedInLasso = layout.filter((item) => {
                                                if (freezeSectorSelection && isHallNamedSectorType(item.type)) {
                                                    return false;
                                                }
                                                const node = nodeRefs.current[item._id];
                                                const stage = stageRef.current;
                                                if (!node || !stage) {
                                                    return false;
                                                }
                                                const bounds = node.getClientRect({
                                                    relativeTo: stage,
                                                    skipShadow: true,
                                                    skipStroke: false,
                                                });
                                                const cx = bounds.x + bounds.width / 2;
                                                const cy = bounds.y + bounds.height / 2;
                                                return pointInHallPolygon(cx, cy, points);
                                            }).map((item) => item._id);

                                            if (lassoDraft.appendToSelection) {
                                                setSelectedIds((prev) => Array.from(new Set([...prev, ...selectedInLasso])));
                                            } else {
                                                setSelectedIds(selectedInLasso);
                                            }
                                            setLassoDraft(null);
                                            return;
                                        }
                                        if (!selectionRect || !normalizedSelectionRect) {
                                            return;
                                        }
                                        const tooSmall = normalizedSelectionRect.width < 4 || normalizedSelectionRect.height < 4;
                                        if (tooSmall) {
                                            if (!selectionRect.appendToSelection) {
                                                setSelectedIds([]);
                                            }
                                            setSelectionRect(null);
                                            return;
                                        }
                                        const selectedInRect = layout.filter((item) => {
                                            const intersectsRect = (
                                                a: { x: number; y: number; width: number; height: number },
                                                b: { x: number; y: number; width: number; height: number }
                                            ) => !(
                                                a.x > b.x + b.width
                                                || a.x + a.width < b.x
                                                || a.y > b.y + b.height
                                                || a.y + a.height < b.y
                                            );

                                            if (freezeSectorSelection && isHallNamedSectorType(item.type)) {
                                                return false;
                                            }

                                            const node = nodeRefs.current[item._id];
                                            const stage = stageRef.current;
                                            if (!node || !stage) {
                                                return false;
                                            }
                                            // IMPORTANT: keep coordinates in Stage space (unscaled),
                                            // otherwise selection will drift under zoom.
                                            const bounds = node.getClientRect({
                                                relativeTo: stage,
                                                skipShadow: true,
                                                skipStroke: false,
                                            });
                                            return intersectsRect(
                                                normalizedSelectionRect,
                                                { x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height }
                                            );
                                        }).map((item) => item._id);

                                        if (selectionRect.appendToSelection) {
                                            setSelectedIds((prev) => Array.from(new Set([...prev, ...selectedInRect])));
                                        } else {
                                            setSelectedIds(selectedInRect);
                                        }
                                        setSelectionRect(null);
                                    }}
                                >
                                    <Layer listening={false}>
                                        {hallEditorGridLines}
                                    </Layer>
                                    <Layer>
                                        {/* sectors always behind other elements */}
                                        {layoutSectorsAndZones.map((item) => {
                                            const isPolygon = item.type === 'sector_polygon';
                                            const absPolygonPoints = isPolygon
                                                ? hallPolygonAbsolutePoints(item)
                                                : [];
                                            const sectorLabel = isPolygon
                                                ? (hallPolygonLabelLayout({
                                                    absPoints: absPolygonPoints,
                                                    fontSize: 14,
                                                }) ?? hallSectorLabelLayout({
                                                    x: item.x,
                                                    y: item.y,
                                                    width: item.width,
                                                    height: item.height,
                                                    rotation: 0,
                                                    fontSize: 14,
                                                }))
                                                : hallSectorLabelLayout({
                                                    x: item.x,
                                                    y: item.y,
                                                    width: item.width,
                                                    height: item.height,
                                                    rotation: item.rotation ?? 0,
                                                    fontSize: item.type === 'zone' ? 16 : 14,
                                                });
                                            const showVertexHandles = isPolygon
                                                && selectedIds.length === 1
                                                && selectedIds[0] === item._id
                                                && !freezeSectorSelection;
                                            const sectorInteractive = {
                                                ref: (node: Konva.Shape | null) => {
                                                    if (node) {
                                                        nodeRefs.current[item._id] = node;
                                                    }
                                                },
                                                fill: HALL_SECTOR.fill,
                                                stroke: HALL_SECTOR.stroke,
                                                strokeWidth: HALL_SECTOR.strokeWidth,
                                                draggable: true as const,
                                                listening: !freezeSectorSelection,
                                                onClick: (e: Konva.KonvaEventObject<MouseEvent>) => {
                                                    const multi = e.evt.shiftKey || e.evt.metaKey || e.evt.ctrlKey;
                                                    if (multi) {
                                                        setSelectedIds((prev) => (
                                                            prev.includes(item._id)
                                                                ? prev.filter((id) => id !== item._id)
                                                                : [...prev, item._id]
                                                        ));
                                                        return;
                                                    }
                                                    setSelectedIds([item._id]);
                                                },
                                                onDblClick: (e: Konva.KonvaEventObject<MouseEvent>) => {
                                                    e.cancelBubble = true;
                                                    if (isHallNamedSectorType(item.type)) {
                                                        openSectorSettings(item._id);
                                                    }
                                                },
                                                onTap: () => setSelectedIds([item._id]),
                                                onDragStart: (e: Konva.KonvaEventObject<DragEvent>) => {
                                                    const node = e.target;
                                                    singleDragStartRef.current = { id: item._id, x: node.x(), y: node.y() };
                                                    if (!selectedIds.includes(item._id)) {
                                                        setSelectedIds([item._id]);
                                                        return;
                                                    }
                                                    if (selectedIds.length < 2) {
                                                        return;
                                                    }
                                                    const startPositions: Record<string, { x: number; y: number }> = {};
                                                    selectedIds.forEach((id) => {
                                                        const nodeRef = nodeRefs.current[id];
                                                        if (nodeRef) {
                                                            startPositions[id] = { x: nodeRef.x(), y: nodeRef.y() };
                                                        }
                                                    });
                                                    const activeStart = startPositions[item._id] ?? { x: node.x(), y: node.y() };
                                                    startPositions[item._id] = activeStart;
                                                    dragGroupStartRef.current = startPositions;
                                                    dragGroupSelectedIdsRef.current = [...selectedIds];
                                                    const pointerPos = getPointerPositionInStage();
                                                    dragGroupPointerStartRef.current = pointerPos ? { x: pointerPos.x, y: pointerPos.y } : null;
                                                    activeDragRef.current = {
                                                        id: item._id,
                                                        startX: activeStart.x,
                                                        startY: activeStart.y,
                                                    };
                                                },
                                                onDragMove: (e: Konva.KonvaEventObject<DragEvent>) => {
                                                    if (!activeDragRef.current || selectedIds.length < 2) {
                                                        return;
                                                    }
                                                    const activeNode = e.target;
                                                    const pointerPos = getPointerPositionInStage();
                                                    const pointerStart = dragGroupPointerStartRef.current;
                                                    if (!pointerPos || !pointerStart) {
                                                        return;
                                                    }
                                                    const dx = pointerPos.x - pointerStart.x;
                                                    const dy = pointerPos.y - pointerStart.y;
                                                    const activeId = activeDragRef.current.id;
                                                    dragGroupSelectedIdsRef.current.forEach((id) => {
                                                        if (id === activeId) {
                                                            return;
                                                        }
                                                        const nodeRef = nodeRefs.current[id];
                                                        const startPos = dragGroupStartRef.current[id];
                                                        if (nodeRef && startPos) {
                                                            nodeRef.x(startPos.x + dx);
                                                            nodeRef.y(startPos.y + dy);
                                                        }
                                                    });
                                                    activeNode.getLayer()?.batchDraw();
                                                },
                                                onDragEnd: (e: Konva.KonvaEventObject<DragEvent>) => {
                                                    if (activeDragRef.current && selectedIds.length > 1) {
                                                        const startPositions = dragGroupStartRef.current;
                                                        const prevEls = layoutRef.current;
                                                        const proposed = prevEls.map((old) => {
                                                            const nodeRef = nodeRefs.current[old._id];
                                                            if (!selectedIds.includes(old._id) || !nodeRef) {
                                                                return old;
                                                            }
                                                            return { ...old, x: nodeRef.x(), y: nodeRef.y() };
                                                        });
                                                        if (violatesNoSeatsSectorRule(proposed)) {
                                                            selectedIds.forEach((id) => {
                                                                const nodeRef = nodeRefs.current[id];
                                                                const start = startPositions[id];
                                                                if (nodeRef && start) {
                                                                    nodeRef.x(start.x);
                                                                    nodeRef.y(start.y);
                                                                }
                                                            });
                                                            e.target.getLayer()?.batchDraw();
                                                        } else if (!commitHallLayoutFromProposed(proposed)) {
                                                            selectedIds.forEach((id) => {
                                                                const nodeRef = nodeRefs.current[id];
                                                                const start = startPositions[id];
                                                                if (nodeRef && start) {
                                                                    nodeRef.x(start.x);
                                                                    nodeRef.y(start.y);
                                                                }
                                                            });
                                                            e.target.getLayer()?.batchDraw();
                                                        }
                                                        activeDragRef.current = null;
                                                        dragGroupStartRef.current = {};
                                                        dragGroupSelectedIdsRef.current = [];
                                                        dragGroupPointerStartRef.current = null;
                                                        return;
                                                    }
                                                    const start = singleDragStartRef.current?.id === item._id ? singleDragStartRef.current : null;
                                                    const prevElsSingle = layoutRef.current;
                                                    const proposedSingle = prevElsSingle.map((el) => (
                                                        el._id === item._id ? { ...el, x: e.target.x(), y: e.target.y() } : el
                                                    ));
                                                    if (violatesNoSeatsSectorRule(proposedSingle)) {
                                                        if (start) {
                                                            e.target.x(start.x);
                                                            e.target.y(start.y);
                                                            e.target.getLayer()?.batchDraw();
                                                        }
                                                        return;
                                                    }
                                                    if (!commitHallLayoutFromProposed(proposedSingle) && start) {
                                                        e.target.x(start.x);
                                                        e.target.y(start.y);
                                                        e.target.getLayer()?.batchDraw();
                                                    }
                                                },
                                                onTransformEnd: (e: Konva.KonvaEventObject<Event>) => {
                                                    const node = e.target;
                                                    if (isPolygon) {
                                                        const current = findInLayout(layoutRef.current, item._id) ?? item;
                                                        const baked = bakeSectorPolygonFromNode(current, node);
                                                        const proposed = layoutRef.current.map((el) => (
                                                            el._id === item._id ? baked : el
                                                        ));
                                                        if (violatesNoSeatsSectorRule(proposed)) {
                                                            const current = findInLayout(layout, item._id);
                                                            if (current) {
                                                                node.x(current.x);
                                                                node.y(current.y);
                                                                node.rotation(current.rotation ?? 0);
                                                                (node as any).points?.(current.points ?? []);
                                                                node.scaleX(1);
                                                                node.scaleY(1);
                                                                node.getLayer()?.batchDraw();
                                                            }
                                                            return;
                                                        }
                                                        updateElement(item._id, () => baked);
                                                        return;
                                                    }
                                                    const scaleX = node.scaleX();
                                                    const scaleY = node.scaleY();
                                                    const nextWidth = Math.max(14, node.width() * scaleX);
                                                    const nextHeight = Math.max(14, node.height() * scaleY);
                                                    node.scaleX(1);
                                                    node.scaleY(1);
                                                    const proposed = layoutRef.current.map((el) => (
                                                        el._id === item._id ? {
                                                            ...el,
                                                            x: node.x(),
                                                            y: node.y(),
                                                            rotation: node.rotation(),
                                                            width: nextWidth,
                                                            height: nextHeight,
                                                        } : el
                                                    ));
                                                    if (violatesNoSeatsSectorRule(proposed)) {
                                                        const current = findInLayout(layout, item._id);
                                                        if (current) {
                                                            node.x(current.x);
                                                            node.y(current.y);
                                                            node.rotation(current.rotation);
                                                            node.width(current.width);
                                                            node.height(current.height);
                                                            node.getLayer()?.batchDraw();
                                                        }
                                                        return;
                                                    }
                                                    updateElement(item._id, (old) => ({
                                                        ...old,
                                                        x: node.x(),
                                                        y: node.y(),
                                                        rotation: node.rotation(),
                                                        width: nextWidth,
                                                        height: nextHeight,
                                                    }));
                                                },
                                            };
                                            return (
                                                <React.Fragment key={item._id}>
                                                    {isPolygon ? (
                                                        <Line
                                                            {...sectorInteractive}
                                                            x={item.x}
                                                            y={item.y}
                                                            points={Array.isArray(item.points) ? item.points : []}
                                                            closed
                                                            rotation={item.rotation ?? 0}
                                                        />
                                                    ) : (
                                                        <Rect
                                                            {...sectorInteractive}
                                                            x={item.x}
                                                            y={item.y}
                                                            width={item.width}
                                                            height={item.height}
                                                            rotation={item.rotation}
                                                            cornerRadius={getHallBlockCornerRadius(item.type as string)}
                                                        />
                                                    )}
                                                    <Text
                                                        x={sectorLabel.x}
                                                        y={sectorLabel.y}
                                                        width={sectorLabel.width}
                                                        offsetX={sectorLabel.offsetX}
                                                        offsetY={sectorLabel.offsetY}
                                                        rotation={sectorLabel.rotation}
                                                        align="center"
                                                        verticalAlign="middle"
                                                        text={item.label}
                                                        fontSize={sectorLabel.fontSize}
                                                        fill={item.type === 'zone' ? HALL_SECTOR.labelNoSeats : HALL_SECTOR.labelRegular}
                                                        listening={false}
                                                    />
                                                    {showVertexHandles && absPolygonPoints.map((pt, idx) => (
                                                        <Circle
                                                            key={`${item._id}-vertex-${idx}`}
                                                            x={pt.x}
                                                            y={pt.y}
                                                            radius={6}
                                                            fill="#ffffff"
                                                            stroke="#0ea5e9"
                                                            strokeWidth={2}
                                                            draggable
                                                            onDragStart={(e) => {
                                                                e.cancelBubble = true;
                                                            }}
                                                            onDragMove={(e) => {
                                                                e.cancelBubble = true;
                                                            }}
                                                            onDragEnd={(e) => {
                                                                e.cancelBubble = true;
                                                                const nextAbs = absPolygonPoints.map((p, i) => (
                                                                    i === idx ? { x: e.target.x(), y: e.target.y() } : p
                                                                ));
                                                                const synced = hallPolygonLayoutFromAbsolute(nextAbs);
                                                                if (!synced) {
                                                                    e.target.x(pt.x);
                                                                    e.target.y(pt.y);
                                                                    return;
                                                                }
                                                                const proposed = layoutRef.current.map((el) => (
                                                                    el._id === item._id ? { ...el, ...synced } : el
                                                                ));
                                                                if (violatesNoSeatsSectorRule(proposed)) {
                                                                    e.target.x(pt.x);
                                                                    e.target.y(pt.y);
                                                                    e.target.getLayer()?.batchDraw();
                                                                    return;
                                                                }
                                                                updateElement(item._id, (old) => ({ ...old, ...synced }));
                                                            }}
                                                        />
                                                    ))}
                                                </React.Fragment>
                                            );
                                        })}
                                    </Layer>

                                    <Layer>
                                        {layoutBlocks.map((item) => {
                                            const seatLabel = item.type === 'seat'
                                                ? (seatSmartLabels.seatLabelsById[item._id] ?? '')
                                                : '';
                                            const { w: seatW, h: seatH, halfW: seatHalfW, halfH: seatHalfH } = item.type === 'seat'
                                                ? seatSize(item)
                                                : { w: 0, h: 0, halfW: 0, halfH: 0 };
                                            const sideLabels = item.type === 'seat'
                                                ? seatSmartLabels.sideRowLabelsBySeatId[item._id]
                                                : undefined;
                                            const rowLabelFontSize = hallSeatRowLabelFontSizeForRect(seatW, seatH);
                                            const rowLabelGap = Math.max(6, Math.round(seatW * 0.18));
                                            const leftRowLabelWidth = sideLabels?.left
                                                ? Math.max(16, Math.round(sideLabels.left.length * rowLabelFontSize * 0.72) + 4)
                                                : 0;
                                            const rightRowLabelWidth = sideLabels?.right
                                                ? Math.max(16, Math.round(sideLabels.right.length * rowLabelFontSize * 0.72) + 4)
                                                : 0;
                                            const shapeCommon = {
                                                id: item._id,
                                                ref: (node: Konva.Shape | null) => {
                                                    if (node) {
                                                        nodeRefs.current[item._id] = node;
                                                    }
                                                },
                                                fill: resolveHallElementFill(item),
                                                draggable: true as const,
                                                rotation: item.rotation ?? 0,
                                                onClick: (e: Konva.KonvaEventObject<MouseEvent>) => {
                                                    const multi = e.evt.shiftKey || e.evt.metaKey || e.evt.ctrlKey;
                                                    if (multi) {
                                                        setSelectedIds((prev) => (
                                                            prev.includes(item._id)
                                                                ? prev.filter((id) => id !== item._id)
                                                                : [...prev, item._id]
                                                        ));
                                                        return;
                                                    }
                                                    setSelectedIds([item._id]);
                                                },
                                                onDblClick: (e: Konva.KonvaEventObject<MouseEvent>) => {
                                                    e.cancelBubble = true;
                                                    if (item.type === 'seat') {
                                                        openSeatSettings(item._id);
                                                    } else if (item.type === 'stage') {
                                                        openStageSettings(item._id);
                                                    }
                                                },
                                                onTap: () => setSelectedIds([item._id]),
                                                onDragStart: (e: Konva.KonvaEventObject<DragEvent>) => {
                                                    const node = e.target;
                                                    singleDragStartRef.current = { id: item._id, x: node.x(), y: node.y() };
                                                    if (!selectedIds.includes(item._id)) {
                                                        setSelectedIds([item._id]);
                                                        return;
                                                    }
                                                    if (selectedIds.length < 2) {
                                                        return;
                                                    }
                                                    const startPositions: Record<string, { x: number; y: number }> = {};
                                                    selectedIds.forEach((id) => {
                                                        const nodeRef = nodeRefs.current[id];
                                                        if (nodeRef) {
                                                            startPositions[id] = { x: nodeRef.x(), y: nodeRef.y() };
                                                        }
                                                    });
                                                    const activeStart = startPositions[item._id] ?? { x: node.x(), y: node.y() };
                                                    startPositions[item._id] = activeStart;
                                                    dragGroupStartRef.current = startPositions;
                                                    dragGroupSelectedIdsRef.current = [...selectedIds];
                                                    const pointerPos = getPointerPositionInStage();
                                                    dragGroupPointerStartRef.current = pointerPos ? { x: pointerPos.x, y: pointerPos.y } : null;
                                                    activeDragRef.current = {
                                                        id: item._id,
                                                        startX: activeStart.x,
                                                        startY: activeStart.y,
                                                    };
                                                },
                                                onDragMove: (e: Konva.KonvaEventObject<DragEvent>) => {
                                                    if (!activeDragRef.current || selectedIds.length < 2) {
                                                        return;
                                                    }
                                                    const activeNode = e.target;
                                                    const pointerPos = getPointerPositionInStage();
                                                    const pointerStart = dragGroupPointerStartRef.current;
                                                    if (!pointerPos || !pointerStart) {
                                                        return;
                                                    }
                                                    const dx = pointerPos.x - pointerStart.x;
                                                    const dy = pointerPos.y - pointerStart.y;

                                                    const activeId = activeDragRef.current.id;
                                                    dragGroupSelectedIdsRef.current.forEach((id) => {
                                                        if (id === activeId) {
                                                            return;
                                                        }
                                                        const nodeRef = nodeRefs.current[id];
                                                        const startPos = dragGroupStartRef.current[id];
                                                        if (nodeRef && startPos) {
                                                            nodeRef.x(startPos.x + dx);
                                                            nodeRef.y(startPos.y + dy);
                                                        }
                                                    });
                                                    activeNode.getLayer()?.batchDraw();
                                                },
                                                onDragEnd: (e: Konva.KonvaEventObject<DragEvent>) => {
                                                    if (activeDragRef.current && selectedIds.length > 1) {
                                                        const startPositions = dragGroupStartRef.current;
                                                        const prevEls = layoutRef.current;
                                                        const proposed = prevEls.map((old) => {
                                                            const nodeRef = nodeRefs.current[old._id];
                                                            if (!selectedIds.includes(old._id) || !nodeRef) {
                                                                return old;
                                                            }
                                                            return { ...old, x: nodeRef.x(), y: nodeRef.y() };
                                                        });
                                                        if (violatesNoSeatsSectorRule(proposed)) {
                                                            selectedIds.forEach((id) => {
                                                                const nodeRef = nodeRefs.current[id];
                                                                const start = startPositions[id];
                                                                if (nodeRef && start) {
                                                                    nodeRef.x(start.x);
                                                                    nodeRef.y(start.y);
                                                                }
                                                            });
                                                            e.target.getLayer()?.batchDraw();
                                                        } else if (!commitHallLayoutFromProposed(proposed)) {
                                                            selectedIds.forEach((id) => {
                                                                const nodeRef = nodeRefs.current[id];
                                                                const start = startPositions[id];
                                                                if (nodeRef && start) {
                                                                    nodeRef.x(start.x);
                                                                    nodeRef.y(start.y);
                                                                }
                                                            });
                                                            e.target.getLayer()?.batchDraw();
                                                        }
                                                        activeDragRef.current = null;
                                                        dragGroupStartRef.current = {};
                                                        dragGroupSelectedIdsRef.current = [];
                                                        dragGroupPointerStartRef.current = null;
                                                        return;
                                                    }
                                                    const start = singleDragStartRef.current?.id === item._id ? singleDragStartRef.current : null;
                                                    const prevElsSingle = layoutRef.current;
                                                    const proposedSingle = prevElsSingle.map((el) => (
                                                        el._id === item._id ? { ...el, x: e.target.x(), y: e.target.y() } : el
                                                    ));
                                                    if (violatesNoSeatsSectorRule(proposedSingle)) {
                                                        if (start) {
                                                            e.target.x(start.x);
                                                            e.target.y(start.y);
                                                            e.target.getLayer()?.batchDraw();
                                                        }
                                                        return;
                                                    }
                                                    if (!commitHallLayoutFromProposed(proposedSingle) && start) {
                                                        e.target.x(start.x);
                                                        e.target.y(start.y);
                                                        e.target.getLayer()?.batchDraw();
                                                    }
                                                },
                                                onTransformEnd: (e: Konva.KonvaEventObject<Event>) => {
                                                    if (selectedIds.length > 1) {
                                                        return;
                                                    }
                                                    const node = e.target;
                                                    if (item.type === 'seat') {
                                                        const scaleX = node.scaleX();
                                                        const scaleY = node.scaleY();
                                                        const nextWidth = Math.max(8, node.width() * scaleX);
                                                        const nextHeight = Math.max(8, node.height() * scaleY);
                                                        const nextX = node.x();
                                                        const nextY = node.y();

                                                        node.scaleX(1);
                                                        node.scaleY(1);
                                                        (node as any).width?.(nextWidth);
                                                        (node as any).height?.(nextHeight);
                                                        (node as any).offsetX?.(nextWidth / 2);
                                                        (node as any).offsetY?.(nextHeight / 2);
                                                        node.x(nextX);
                                                        node.y(nextY);
                                                        updateElement(item._id, (old) => ({
                                                            ...old,
                                                            x: nextX,
                                                            y: nextY,
                                                            rotation: node.rotation(),
                                                            width: nextWidth,
                                                            height: nextHeight,
                                                        }));
                                                        return;
                                                    }
                                                    const scaleX = node.scaleX();
                                                    const scaleY = node.scaleY();
                                                    const nextWidth = Math.max(14, node.width() * scaleX);
                                                    const nextHeight = Math.max(14, node.height() * scaleY);
                                                    node.scaleX(1);
                                                    node.scaleY(1);
                                                    updateElement(item._id, (old) => ({
                                                        ...old,
                                                        x: node.x(),
                                                        y: node.y(),
                                                        rotation: node.rotation(),
                                                        width: nextWidth,
                                                        height: nextHeight,
                                                    }));
                                                },
                                            };
                                            return (
                                                <React.Fragment key={item._id}>
                                                    {item.type === 'seat' ? (
                                                        <Rect
                                                            {...shapeCommon}
                                                            x={item.x}
                                                            y={item.y}
                                                            offsetX={seatHalfW}
                                                            offsetY={seatHalfH}
                                                            width={seatW}
                                                            height={seatH}
                                                            cornerRadius={getHallSeatCornerRadius(seatW, seatH)}
                                                        />
                                                    ) : (
                                                        <Rect
                                                            {...shapeCommon}
                                                            x={item.x}
                                                            y={item.y}
                                                            width={item.width}
                                                            height={item.height}
                                                            cornerRadius={getHallBlockCornerRadius(item.type as string)}
                                                        />
                                                    )}
                                                    {item.type === 'seat' && (
                                                        <>
                                                            <Text
                                                                x={item.x}
                                                                y={item.y}
                                                                offsetX={seatHalfW}
                                                                offsetY={seatHalfH}
                                                                width={seatW}
                                                                height={seatH}
                                                                rotation={item.rotation ?? 0}
                                                                align="center"
                                                                verticalAlign="middle"
                                                                wrap="none"
                                                                text={seatLabel}
                                                                fontSize={hallSeatLabelFontSizeForRect(
                                                                    seatLabel,
                                                                    seatW,
                                                                    seatH,
                                                                )}
                                                                fontStyle="bold"
                                                                fontFamily="Arial,sans-serif"
                                                                fill={hallSeatLabelFill(item)}
                                                                listening={false}
                                                            />
                                                            {sideLabels?.left && (
                                                                <Text
                                                                    x={item.x - seatHalfW - rowLabelGap - leftRowLabelWidth / 2}
                                                                    y={item.y}
                                                                    offsetX={leftRowLabelWidth / 2}
                                                                    offsetY={seatH / 2}
                                                                    width={leftRowLabelWidth}
                                                                    height={seatH}
                                                                    rotation={item.rotation ?? 0}
                                                                    align="right"
                                                                    verticalAlign="middle"
                                                                    wrap="none"
                                                                    text={sideLabels.left}
                                                                    fontSize={rowLabelFontSize}
                                                                    fontStyle="bold"
                                                                    fontFamily="Arial,sans-serif"
                                                                    fill={hallSeatRowLabelFill()}
                                                                    listening={false}
                                                                />
                                                            )}
                                                            {sideLabels?.right && (
                                                                <Text
                                                                    x={item.x + seatHalfW + rowLabelGap + rightRowLabelWidth / 2}
                                                                    y={item.y}
                                                                    offsetX={rightRowLabelWidth / 2}
                                                                    offsetY={seatH / 2}
                                                                    width={rightRowLabelWidth}
                                                                    height={seatH}
                                                                    rotation={item.rotation ?? 0}
                                                                    align="left"
                                                                    verticalAlign="middle"
                                                                    wrap="none"
                                                                    text={sideLabels.right}
                                                                    fontSize={rowLabelFontSize}
                                                                    fontStyle="bold"
                                                                    fontFamily="Arial,sans-serif"
                                                                    fill={hallSeatRowLabelFill()}
                                                                    listening={false}
                                                                />
                                                            )}
                                                        </>
                                                    )}
                                                    {item.type !== 'seat' && item.type !== 'stage' && (
                                                        <Text
                                                            x={item.x + 6}
                                                            y={item.y + 6}
                                                            text={item.type === 'wall' ? '' : item.label}
                                                            fontSize={12}
                                                            fill={getHallBlockOnShapeTextFill(item.type as string)}
                                                            listening={false}
                                                        />
                                                    )}
                                                    <Text
                                                        x={item.x}
                                                        y={item.y + item.height / 2 - 8}
                                                        width={item.width}
                                                        align="center"
                                                        text={item.type === 'stage' ? item.label : ''}
                                                        fontSize={16}
                                                        fill={getHallBlockOnShapeTextFill('stage')}
                                                        listening={false}
                                                        visible={item.type === 'stage'}
                                                    />
                                                </React.Fragment>
                                            );
                                        })}
                                        {seatDraftStats && (
                                            <>
                                                <Rect
                                                    x={seatDraftStats.x}
                                                    y={seatDraftStats.y}
                                                    width={seatDraftStats.width}
                                                    height={seatDraftStats.height}
                                                    fill={HALL_SEAT_DRAFT.rectFill}
                                                    stroke={HALL_SEAT_DRAFT.rectStroke}
                                                    dash={[6, 6]}
                                                    listening={false}
                                                />
                                                {seatDraftPreviewSeats.map((previewSeat, idx) => (
                                                    <Rect
                                                        key={`seat-preview-${idx}`}
                                                        x={previewSeat.x}
                                                        y={previewSeat.y}
                                                        offsetX={previewSeat.halfW}
                                                        offsetY={previewSeat.halfH}
                                                        width={previewSeat.w}
                                                        height={previewSeat.h}
                                                        cornerRadius={getHallSeatCornerRadius(previewSeat.w, previewSeat.h)}
                                                        fill={HALL_SEAT_DRAFT.previewFill}
                                                        stroke={HALL_SEAT_DRAFT.previewStroke}
                                                        strokeWidth={1}
                                                        listening={false}
                                                    />
                                                ))}
                                                <Rect
                                                    x={seatDraftStats.x + 6}
                                                    y={seatDraftStats.y + 6}
                                                    width={220}
                                                    height={38}
                                                    cornerRadius={6}
                                                    fill={HALL_SEAT_DRAFT.statsBg}
                                                    listening={false}
                                                />
                                                <Text
                                                    x={seatDraftStats.x + 12}
                                                    y={seatDraftStats.y + 13}
                                                    text={`${Lang.key('hall_seat_draft_rows')}: ${seatDraftStats.rows}  ${Lang.key('hall_seat_draft_seats_in_line')}: ${seatDraftStats.cols}  ${Lang.key('hall_seat_draft_total')}: ${seatDraftStats.total}`}
                                                    fontSize={12}
                                                    fill={HALL_SEAT_DRAFT.statsText}
                                                    listening={false}
                                                />
                                            </>
                                        )}
                                        {blockPlacementPreview && (
                                            <>
                                                <Rect
                                                    x={blockPlacementPreview.x}
                                                    y={blockPlacementPreview.y}
                                                    width={blockPlacementPreview.width}
                                                    height={blockPlacementPreview.height}
                                                    fill={HALL_SEAT_DRAFT.rectFill}
                                                    stroke={HALL_SEAT_DRAFT.rectStroke}
                                                    dash={[6, 6]}
                                                    listening={false}
                                                />
                                                <Text
                                                    x={blockPlacementPreview.x + 8}
                                                    y={blockPlacementPreview.y + 8}
                                                    text={`${Math.round(blockPlacementPreview.width)} × ${Math.round(blockPlacementPreview.height)}`}
                                                    fontSize={12}
                                                    fill={HALL_SEAT_DRAFT.statsText}
                                                    listening={false}
                                                />
                                            </>
                                        )}
                                        {polygonDraft && (() => {
                                            const draftPts = polygonDraft.points;
                                            const cursor = polygonDraft.cursor;
                                            const previewFlat: number[] = [];
                                            draftPts.forEach((p) => {
                                                previewFlat.push(p.x, p.y);
                                            });
                                            if (cursor) {
                                                previewFlat.push(cursor.x, cursor.y);
                                            }
                                            if (draftPts.length >= 1) {
                                                previewFlat.push(draftPts[0]!.x, draftPts[0]!.y);
                                            }
                                            return (
                                                <>
                                                    {previewFlat.length >= 4 && (
                                                        <Line
                                                            points={previewFlat}
                                                            stroke={HALL_SEAT_DRAFT.rectStroke}
                                                            strokeWidth={2}
                                                            dash={[6, 4]}
                                                            listening={false}
                                                        />
                                                    )}
                                                    {draftPts.map((p, idx) => (
                                                        <Circle
                                                            key={`poly-draft-${idx}`}
                                                            x={p.x}
                                                            y={p.y}
                                                            radius={idx === 0 ? 7 : 5}
                                                            fill={idx === 0 ? '#0ea5e9' : '#ffffff'}
                                                            stroke={HALL_SEAT_DRAFT.rectStroke}
                                                            strokeWidth={2}
                                                            listening={false}
                                                        />
                                                    ))}
                                                    <Text
                                                        x={(cursor?.x ?? draftPts[draftPts.length - 1]?.x ?? 8) + 10}
                                                        y={(cursor?.y ?? draftPts[draftPts.length - 1]?.y ?? 8) + 10}
                                                        text={draftPts.length < 3
                                                            ? `Точки: ${draftPts.length} (мин. 3). Enter/двойной клик — готово, Esc — отмена`
                                                            : 'Клик у первой точки / Enter / двойной клик — замкнуть'}
                                                        fontSize={12}
                                                        fill={HALL_SEAT_DRAFT.statsText}
                                                        listening={false}
                                                    />
                                                </>
                                            );
                                        })()}
                                        {normalizedSelectionRect && (
                                            <Rect
                                                x={normalizedSelectionRect.x}
                                                y={normalizedSelectionRect.y}
                                                width={normalizedSelectionRect.width}
                                                height={normalizedSelectionRect.height}
                                                fill={HALL_SELECTION.rectFill}
                                                stroke={HALL_SELECTION.rectStroke}
                                                dash={[4, 4]}
                                                listening={false}
                                            />
                                        )}
                                        {lassoDraft && lassoDraft.points.length >= 2 && (
                                            <Line
                                                points={lassoDraft.points.flatMap((p) => [p.x, p.y])}
                                                closed={lassoDraft.points.length >= 3}
                                                fill={HALL_SELECTION.rectFill}
                                                stroke={HALL_SELECTION.rectStroke}
                                                strokeWidth={2}
                                                dash={[4, 4]}
                                                listening={false}
                                            />
                                        )}

                                        {/* Drag by visible selection frame (multi-select) */}
                                        {selectionBounds && !rectPlacementDraft && !selectionRect && !lassoDraft && (
                                            <Rect
                                                x={selectionBounds.x}
                                                y={selectionBounds.y}
                                                width={selectionBounds.width}
                                                height={selectionBounds.height}
                                                stroke={HALL_SELECTION.frameStroke}
                                                dash={[6, 4]}
                                                strokeWidth={2}
                                                fill={HALL_SELECTION.frameFill}
                                                draggable
                                                onDragStart={() => {
                                                    const pointerPos = getPointerPositionInStage();
                                                    selectionFramePointerStartRef.current = pointerPos ? { x: pointerPos.x, y: pointerPos.y } : null;

                                                    const startPositions: Record<string, { x: number; y: number }> = {};
                                                    selectedIds.forEach((id) => {
                                                        const nodeRef = nodeRefs.current[id];
                                                        if (nodeRef) {
                                                            startPositions[id] = { x: nodeRef.x(), y: nodeRef.y() };
                                                        }
                                                    });
                                                    selectionFrameDragStartRef.current = startPositions;
                                                }}
                                                onDragMove={(e) => {
                                                    const pointerPos = getPointerPositionInStage();
                                                    const pointerStart = selectionFramePointerStartRef.current;
                                                    if (!pointerPos || !pointerStart) {
                                                        return;
                                                    }
                                                    const dx = pointerPos.x - pointerStart.x;
                                                    const dy = pointerPos.y - pointerStart.y;

                                                    selectedIds.forEach((id) => {
                                                        const nodeRef = nodeRefs.current[id];
                                                        const startPos = selectionFrameDragStartRef.current[id];
                                                        if (nodeRef && startPos) {
                                                            nodeRef.x(startPos.x + dx);
                                                            nodeRef.y(startPos.y + dy);
                                                        }
                                                    });
                                                    e.target.getLayer()?.batchDraw();
                                                }}
                                                onDragEnd={() => {
                                                    const startPositions = selectionFrameDragStartRef.current;
                                                    const prevEls = layoutRef.current;
                                                    const proposed = prevEls.map((old) => {
                                                        const nodeRef = nodeRefs.current[old._id];
                                                        if (!selectedIds.includes(old._id) || !nodeRef) {
                                                            return old;
                                                        }
                                                        return { ...old, x: nodeRef.x(), y: nodeRef.y() };
                                                    });
                                                    if (violatesNoSeatsSectorRule(proposed)) {
                                                        selectedIds.forEach((id) => {
                                                            const nodeRef = nodeRefs.current[id];
                                                            const start = startPositions[id];
                                                            if (nodeRef && start) {
                                                                nodeRef.x(start.x);
                                                                nodeRef.y(start.y);
                                                            }
                                                        });
                                                        stageRef.current?.getLayers?.()?.forEach((l: any) => l.batchDraw());
                                                    } else if (!commitHallLayoutFromProposed(proposed)) {
                                                        selectedIds.forEach((id) => {
                                                            const nodeRef = nodeRefs.current[id];
                                                            const start = startPositions[id];
                                                            if (nodeRef && start) {
                                                                nodeRef.x(start.x);
                                                                nodeRef.y(start.y);
                                                            }
                                                        });
                                                        stageRef.current?.getLayers?.()?.forEach((l: any) => l.batchDraw());
                                                    }
                                                    selectionFrameDragStartRef.current = {};
                                                    selectionFramePointerStartRef.current = null;
                                                }}
                                            />
                                        )}
                                        {hallCheckFlash?.ids.map((id) => {
                                            const el = findInLayout(layout, id);
                                            if (!el) {
                                                return null;
                                            }
                                            const o = hallCheckOverlayOpacity(hallCheckPulseMs);
                                            if (el.type === 'seat') {
                                                const { w, h, halfW, halfH } = seatSize(el);
                                                return (
                                                    <Rect
                                                        key={`hall-check-${id}`}
                                                        x={el.x}
                                                        y={el.y}
                                                        offsetX={halfW}
                                                        offsetY={halfH}
                                                        width={w}
                                                        height={h}
                                                        rotation={el.rotation ?? 0}
                                                        stroke={HALL_DUPLICATE_HIGHLIGHT_STROKE}
                                                        strokeWidth={5}
                                                        fillEnabled={false}
                                                        cornerRadius={getHallSeatCornerRadius(w, h)}
                                                        opacity={o}
                                                        listening={false}
                                                    />
                                                );
                                            }
                                            if (el.type === 'sector_polygon') {
                                                return (
                                                    <Line
                                                        key={`hall-check-${id}`}
                                                        x={el.x}
                                                        y={el.y}
                                                        points={Array.isArray(el.points) ? el.points : []}
                                                        closed
                                                        rotation={el.rotation ?? 0}
                                                        stroke={HALL_DUPLICATE_HIGHLIGHT_STROKE}
                                                        strokeWidth={5}
                                                        fillEnabled={false}
                                                        opacity={o}
                                                        listening={false}
                                                    />
                                                );
                                            }
                                            return (
                                                <Rect
                                                    key={`hall-check-${id}`}
                                                    x={el.x}
                                                    y={el.y}
                                                    width={el.width}
                                                    height={el.height}
                                                    rotation={el.rotation}
                                                    cornerRadius={getHallBlockCornerRadius(el.type as string)}
                                                    stroke={HALL_DUPLICATE_HIGHLIGHT_STROKE}
                                                    strokeWidth={5}
                                                    fillEnabled={false}
                                                    opacity={o}
                                                    listening={false}
                                                />
                                            );
                                        })}
                                        <Transformer
                                            ref={trRef}
                                            rotateEnabled={!spreadModeArmed}
                                            enabledAnchors={[
                                                'top-left',
                                                'top-center',
                                                'top-right',
                                                'middle-right',
                                                'bottom-right',
                                                'bottom-center',
                                                'bottom-left',
                                                'middle-left',
                                            ]}
                                            boundBoxFunc={(_, newBox) => {
                                                if (newBox.width < 14 || newBox.height < 14) {
                                                    return _;
                                                }
                                                return newBox;
                                            }}
                                            onTransformStart={(e) => {
                                                const tr = trRef.current;
                                                if (!tr) {
                                                    spreadTransformRef.current = null;
                                                    spreadGestureActiveRef.current = false;
                                                    return;
                                                }
                                                const proxy = spreadProxyNodeRef.current;
                                                const evt = e.evt as MouseEvent;
                                                const alt = altKeyRef.current || Boolean(evt?.altKey);
                                                const usingProxy = Boolean(proxy && tr.nodes?.()?.[0] === proxy);

                                                if (!alt || !usingProxy || !proxy) {
                                                    spreadTransformRef.current = null;
                                                    spreadGestureActiveRef.current = false;
                                                    return;
                                                }

                                                const elements = layoutRef.current.filter((el) => selectedIds.includes(el._id));
                                                const allSeats = elements.length >= 2
                                                    && elements.every((el) => el.type === 'seat');
                                                if (!allSeats) {
                                                    spreadTransformRef.current = null;
                                                    spreadGestureActiveRef.current = false;
                                                    return;
                                                }

                                                const oldBounds = {
                                                    x: proxy.x(),
                                                    y: proxy.y(),
                                                    width: Math.max(1, proxy.width() * proxy.scaleX()),
                                                    height: Math.max(1, proxy.height() * proxy.scaleY()),
                                                };
                                                const seats: Record<string, HallSpreadSeatSnap> = {};
                                                elements.forEach((el) => {
                                                    const { w, h } = seatSize(el);
                                                    seats[el._id] = {
                                                        x: el.x,
                                                        y: el.y,
                                                        w,
                                                        h,
                                                        rotation: el.rotation ?? 0,
                                                    };
                                                });
                                                spreadGestureActiveRef.current = true;
                                                spreadTransformRef.current = {
                                                    active: true,
                                                    oldBounds,
                                                    seats,
                                                };
                                            }}
                                            onTransform={() => {
                                                const snap = spreadTransformRef.current;
                                                const proxy = spreadProxyNodeRef.current;
                                                if (!snap?.active || !proxy) {
                                                    return;
                                                }
                                                const newBounds = {
                                                    x: proxy.x(),
                                                    y: proxy.y(),
                                                    width: Math.max(1, proxy.width() * proxy.scaleX()),
                                                    height: Math.max(1, proxy.height() * proxy.scaleY()),
                                                };
                                                Object.entries(snap.seats).forEach(([id, seat]) => {
                                                    const node: any = nodeRefs.current[id];
                                                    if (!node) {
                                                        return;
                                                    }
                                                    const next = remapPointIntoBounds(
                                                        seat.x,
                                                        seat.y,
                                                        snap.oldBounds,
                                                        newBounds,
                                                    );
                                                    // Never apply scale/size/rotation — only x/y.
                                                    node.scaleX(1);
                                                    node.scaleY(1);
                                                    node.width?.(seat.w);
                                                    node.height?.(seat.h);
                                                    node.offsetX?.(seat.w / 2);
                                                    node.offsetY?.(seat.h / 2);
                                                    node.rotation(seat.rotation);
                                                    node.x(next.x);
                                                    node.y(next.y);
                                                });
                                                proxy.getLayer()?.batchDraw();
                                            }}
                                            onTransformEnd={() => {
                                                const tr = trRef.current;
                                                if (!tr) {
                                                    return;
                                                }
                                                const nodes = tr.nodes?.() ?? [];
                                                const proxy = spreadProxyNodeRef.current;
                                                const snap = spreadTransformRef.current;
                                                spreadTransformRef.current = null;
                                                spreadGestureActiveRef.current = false;

                                                if (snap?.active && proxy) {
                                                    const newBounds = {
                                                        x: proxy.x(),
                                                        y: proxy.y(),
                                                        width: Math.max(1, proxy.width() * proxy.scaleX()),
                                                        height: Math.max(1, proxy.height() * proxy.scaleY()),
                                                    };
                                                    proxy.scaleX(1);
                                                    proxy.scaleY(1);
                                                    proxy.width(newBounds.width);
                                                    proxy.height(newBounds.height);
                                                    proxy.x(newBounds.x);
                                                    proxy.y(newBounds.y);

                                                    const positions: Record<string, { x: number; y: number }> = {};
                                                    Object.entries(snap.seats).forEach(([id, seat]) => {
                                                        const next = remapPointIntoBounds(
                                                            seat.x,
                                                            seat.y,
                                                            snap.oldBounds,
                                                            newBounds,
                                                        );
                                                        positions[id] = next;
                                                        const node: any = nodeRefs.current[id];
                                                        if (!node) {
                                                            return;
                                                        }
                                                        node.scaleX(1);
                                                        node.scaleY(1);
                                                        node.width?.(seat.w);
                                                        node.height?.(seat.h);
                                                        node.offsetX?.(seat.w / 2);
                                                        node.offsetY?.(seat.h / 2);
                                                        node.rotation(seat.rotation);
                                                        node.x(next.x);
                                                        node.y(next.y);
                                                    });

                                                    patchLayout((prev) => prev.map((old) => {
                                                        const seat = snap.seats[old._id];
                                                        const pos = positions[old._id];
                                                        if (!seat || !pos) {
                                                            return old;
                                                        }
                                                        return {
                                                            ...old,
                                                            x: pos.x,
                                                            y: pos.y,
                                                            // size + rotation intentionally unchanged
                                                            width: seat.w,
                                                            height: seat.h,
                                                            rotation: seat.rotation,
                                                        };
                                                    }), true);
                                                    return;
                                                }

                                                if (nodes.length <= 1) {
                                                    return;
                                                }
                                                const ids = nodes
                                                    .map((n: any) => (typeof n.id === 'function' ? String(n.id()) : ''))
                                                    .filter(Boolean);
                                                // Секторы (в т.ч. полигон) часто без id — берём по nodeRefs.
                                                const selectedNow = selectedIds.filter((id) => nodeRefs.current[id]);
                                                const targetIds = ids.length ? ids : selectedNow;
                                                if (!targetIds.length) {
                                                    return;
                                                }

                                                patchLayout((prev) => prev.map((old) => {
                                                    if (!targetIds.includes(old._id)) {
                                                        return old;
                                                    }
                                                    const node: any = nodeRefs.current[old._id]
                                                        ?? nodes.find((n: any) => (typeof n.id === 'function' ? String(n.id()) : '') === old._id);
                                                    if (!node) {
                                                        return old;
                                                    }
                                                    if (old.type === 'sector_polygon') {
                                                        return bakeSectorPolygonFromNode(old, node);
                                                    }
                                                    const scaleX = node.scaleX();
                                                    const scaleY = node.scaleY();
                                                    const w0 = node.width();
                                                    const h0 = node.height();
                                                    const nextWidth = Math.max(old.type === 'seat' ? 8 : 14, w0 * scaleX);
                                                    const nextHeight = Math.max(old.type === 'seat' ? 8 : 14, h0 * scaleY);
                                                    const seatW = nextWidth;
                                                    const seatH = nextHeight;
                                                    const seatX = node.x();
                                                    const seatY = node.y();

                                                    node.scaleX(1);
                                                    node.scaleY(1);
                                                    (node as any).width?.(old.type === 'seat' ? seatW : nextWidth);
                                                    (node as any).height?.(old.type === 'seat' ? seatH : nextHeight);
                                                    if (old.type === 'seat') {
                                                        (node as any).offsetX?.(seatW / 2);
                                                        (node as any).offsetY?.(seatH / 2);
                                                        node.x(seatX);
                                                        node.y(seatY);
                                                    }
                                                    return {
                                                        ...old,
                                                        x: old.type === 'seat' ? seatX : node.x(),
                                                        y: old.type === 'seat' ? seatY : node.y(),
                                                        rotation: node.rotation(),
                                                        width: old.type === 'seat' ? seatW : nextWidth,
                                                        height: old.type === 'seat' ? seatH : nextHeight,
                                                    };
                                                }), true);
                                            }}
                                        />
                                    </Layer>
                                </Stage>
                            </div>
                        </div>
                    </GridRow>



                </GridCol>
            </Grid>}

        </SystemWindowDialog>

        <SeatNumberingDialog
            stackIndex={1}
            open={seatNumberingOpen}
            onOpenChange={setSeatNumberingOpen}
            startFrom={seatNumberStart}
            fillDirection={seatNumberFillDirection}
            onStartFromChange={setSeatNumberStart}
            onFillDirectionChange={setSeatNumberFillDirection}
            onApply={applySeatNumbering}
        />
        <SeatSettingsDialog
            stackIndex={1}
            open={seatSettingsOpen}
            onOpenChange={setSeatSettingsOpen}
            seatNumber={seatSettingsSeatNumber}
            onSeatNumberChange={setSeatSettingsSeatNumber}
            seatRow={seatSettingsSeatRow}
            onSeatRowChange={setSeatSettingsSeatRow}
            onApply={saveSeatSettings}
        />
        <SectorSettingsDialog
            stackIndex={1}
            open={sectorSettingsOpen}
            onOpenChange={setSectorSettingsOpen}
            name={sectorSettingsName}
            onNameChange={setSectorSettingsName}
            onApply={saveSectorSettings}
        />
        <StageSettingsDialog
            stackIndex={1}
            open={stageSettingsOpen}
            onOpenChange={setStageSettingsOpen}
            name={stageSettingsName}
            onNameChange={setStageSettingsName}
            onApply={saveStageSettings}
        />
        <CanvasSettingsDialog
            stackIndex={1}
            open={canvasSettingsOpen}
            onOpenChange={setCanvasSettingsOpen}
            width={canvasSettingsWidth}
            height={canvasSettingsHeight}
            onWidthChange={setCanvasSettingsWidth}
            onHeightChange={setCanvasSettingsHeight}
            onApply={saveCanvasSettings}
        />
        <SeatRowsDialog
            stackIndex={1}
            open={seatRowsOpen}
            onOpenChange={setSeatRowsOpen}
            startFrom={seatRowStart}
            fillDirection={seatRowFillDirection}
            onStartFromChange={setSeatRowStart}
            onFillDirectionChange={setSeatRowFillDirection}
            onApply={applySeatRows}
        />
        <ImportHallJsonDialog
            stackIndex={1}
            open={hallImportOpen}
            onOpenChange={setHallImportOpen}
            onFileSelected={handleImportHallJsonFile}
        />
        <HallSummaryDialog
            stackIndex={1}
            open={hallSummaryOpen}
            onOpenChange={setHallSummaryOpen}
            text={hallSummaryText}
            onCopy={() => {
                void navigator.clipboard.writeText(hallSummaryText).then(() => {
                    toast.success(Lang.key('hall_summary_copied'));
                }).catch(() => {
                    toast.error(Lang.key('hall_summary_copy'));
                });
            }}
        />
    </>;

});

export default DialogHallEditor;