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

import {
    ArmchairIcon,
    BrickWall,
    ClipboardCheck,
    CopyIcon,
    Download,
    Grid2X2,
    GroupIcon,
    LayoutGrid,
    PlusCircle,
    RowsIcon,
    Save,
    SquareIcon,
    Tag,
    TheaterIcon,
    TrashIcon,
    Undo2,
    Upload,
    VaultIcon,
    ZoomIn,
    ZoomOut,
} from 'lucide-react';
import { MdOutlineTableRestaurant } from 'react-icons/md';
import {
    Layer,
    Rect,
    Stage,
    Text,
    Transformer,
} from 'react-konva';

import {
    HallBeltType,
    HallElementType,
    HallType,
    TypeSTWZEnum,
} from '@/api/types';
import { toast } from '@/components/ToastRoot';
import { Button } from '@/components/ui/button';
import {
    Dialog,
    DialogContent,
    DialogFooter,
} from '@/components/ui/dialog';
import {
    DropdownMenu,
    DropdownMenuContent,
    DropdownMenuGroup,
    DropdownMenuItem,
    DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
    Popover,
    PopoverContent,
    PopoverTrigger,
} from '@/components/ui/popover';
import {
    Tooltip,
    TooltipContent,
    TooltipTrigger,
} from '@/components/ui/tooltip';
import { Lang } from '@/lang/Lang';

import {
    Grid,
    GridCol,
    GridRow,
} from '../ui/grid';
import { Separator } from '../ui/separator';
import SeatNumberingDialog from './components/SeatNumberingDialog';
import SeatRowsDialog from './components/SeatRowsDialog';
import SeatSettingsDialog from './components/SeatSettingsDialog';
import BeltCreateDialog from './components/SectorCreateDialog';
import SectorSettingsDialog from './components/SectorSettingsDialog';
import BeltsPanel from './components/SectorsPanel';
import StageSettingsDialog from './components/StageSettingsDialog';
import {
    getAutoBeltColorForIndex,
    getHallBlockCornerRadius,
    getHallBlockOnShapeTextFill,
    HALL_CANVAS,
    HALL_CHECK_FLASH_MS,
    HALL_DUPLICATE_HIGHLIGHT_STROKE,
    HALL_ELEMENT_DEFAULTS,
    HALL_LABELS,
    HALL_LEGACY_BELT_COLOR,
    HALL_SEAT_DRAFT,
    HALL_SEAT_GAP,
    HALL_SEAT_SIZE,
    HALL_SECTOR,
    HALL_SELECTION,
    HALL_STAGE_SIZE,
    HALL_ZOOM,
    type HallElementPaletteDefaults,
    resolveHallElementFill,
} from './hallTheme';

type DialogHallEditorOpenParams = {
    initValue?: HallType;
    onSave?: (value: HallType) => void;
}
type DialogHallEditorRef = {
    /** Открыть редактор; `initValue` — объект или JSON-строка в формате экспорта зала. */
    open: (params: DialogHallEditorOpenParams) => void;
    close: () => void;
}

const HALL_UNDO_MAX = 3;

type HallEditorUndoSnapshot = {
    elements: HallElementType[];
    belts: HallBeltType[];
};

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 SelectionRect = {
    startX: number;
    startY: number;
    currentX: number;
    currentY: number;
    appendToSelection: boolean;
}

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

const PALETTE: Record<TypeSTWZEnum, 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,
    },
    zone: {
        icon: <Grid2X2 />,
        ...HALL_ELEMENT_DEFAULTS.zone,
    },
};

const makeId = () => `hall-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;

const normalizeSeatLogicRecord = (s: HallElementType) => ({
    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(),
    beltId: (s.beltId == null || s.beltId === '') ? null : s.beltId,
    sectorId: (s.sectorId == null || s.sectorId === '') ? null : s.sectorId,
    sectorName: (s.sectorName == null || String(s.sectorName).trim() === '') ? null : String(s.sectorName).trim(),
});

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

/** Дубликаты: места с одинаковой «логикой» (без геометрии/цвета), в т.ч. все поля `null`; сектора с одинаковым названием. */
const collectHallDuplicateIds = (items: HallElementType[]): string[] => {
    const dup = new Set<string>();

    const seats = items.filter((e) => e.type === 'seat');
    const bySeatSig = new Map<string, string[]>();
    seats.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 = items.filter((e) => e.type === 'sector' || e.type === 'zone');
    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 seatHasBelt = (s: HallElementType) => (
    s.beltId != null && String(s.beltId).trim() !== ''
);

/** Места без назначенного пояса. */
const collectSeatIdsWithoutBelt = (items: HallElementType[]): string[] => (
    items
        .filter((e) => e.type === 'seat' && !seatHasBelt(e))
        .map((e) => e.id)
);

const sectorNoSeatsHasValidCapacity = (e: HallElementType): boolean => {
    const c = Number(e.capacity ?? NaN);
    return Number.isFinite(c) && Math.floor(c) > 0;
};

/** Секторы без мест без указанной положительной вместимости. */
const collectSectorNoSeatsIdsWithoutCapacity = (items: HallElementType[]): string[] => (
    items
        .filter((e) => e.type === 'zone' && !sectorNoSeatsHasValidCapacity(e))
        .map((e) => e.id)
);

const median = (values: number[]) => {
    if (!values.length) {
        return null;
    }
    const sorted = [...values].sort((a, b) => a - b);
    const mid = Math.floor(sorted.length / 2);
    if (sorted.length % 2 === 1) {
        return sorted[mid];
    }
    return (sorted[mid - 1] + sorted[mid]) / 2;
};

const reconcileSeatSectors = (items: HallElementType[]) => {
    const sectors = items.filter((e) => e.type === 'sector');
    if (!sectors.length) {
        return items.map((e) => (
            e.type === 'seat'
                ? { ...e, sectorId: undefined, sectorName: undefined }
                : e
        ));
    }

    // If multiple sectors overlap, prefer the latest in array (last created).
    const sectorsByPriority = [...sectors];

    return items.map((e) => {
        if (e.type !== 'seat') {
            return e;
        }

        const cx = e.x + e.width / 2;
        const cy = e.y + e.height / 2;

        let hit: HallElementType | null = null;
        for (let i = sectorsByPriority.length - 1; i >= 0; i--) {
            const s = sectorsByPriority[i];
            const inside = cx >= s.x && cx <= s.x + s.width && cy >= s.y && cy <= s.y + s.height;
            if (inside) {
                hit = s;
                break;
            }
        }

        if (!hit) {
            if (e.sectorId == null && e.sectorName == null) {
                return e;
            }
            return { ...e, sectorId: undefined, sectorName: undefined };
        }

        const nextName = hit.label || 'Новый сектор';
        if (e.sectorId === hit.id && e.sectorName === nextName) {
            return e;
        }
        return { ...e, sectorId: hit.id, sectorName: nextName };
    });
};

const importNullableStrField = (v: unknown): string | null => {
    if (v === null || v === undefined) {
        return null;
    }
    if (typeof v !== 'string') {
        return null;
    }
    const t = v.trim();
    return t.length > 0 ? t : null;
};

const numField = (v: unknown, fallback: number): number => {
    const n = typeof v === 'number' ? v : Number(v);
    return Number.isFinite(n) ? n : fallback;
};

/** Элемент из JSON; поля `fill` / `cornerRadius` из старых файлов отбрасываются. */
const normalizeHallElementFromJson = (raw: unknown): HallElementType | null => {
    if (!raw || typeof raw !== 'object') {
        return null;
    }
    const o = raw as Record<string, unknown>;
    if (typeof o.id !== 'string') {
        return null;
    }
    const typeRaw = o.type;
    if (typeof typeRaw !== 'string') {
        return null;
    }
    const type: TypeSTWZEnum = typeRaw === 'stage_round' ? 'stage' : (typeRaw as TypeSTWZEnum);

    const el: HallElementType = {
        id: o.id,
        type,
        x: numField(o.x, 0),
        y: numField(o.y, 0),
        width: numField(o.width, 1),
        height: numField(o.height, 1),
        rotation: numField(o.rotation, 0),
        label: typeof o.label === 'string' ? o.label : '',
    };

    const optStr = (v: unknown) => {
        if (v == null) {
            return undefined;
        }
        const s = String(v).trim();
        return s.length ? s : undefined;
    };

    if (type === 'seat') {
        const sn = optStr(o.seatNumber);
        const sr = optStr(o.seatRow);
        if (sn !== undefined) {
            el.seatNumber = sn;
        }
        if (sr !== undefined) {
            el.seatRow = sr;
        }
        if (typeof o.beltId === 'string' && o.beltId.length > 0) {
            el.beltId = o.beltId;
        }
        const sid = optStr(o.sectorId);
        if (sid !== undefined) {
            el.sectorId = sid;
        }
        const sname = optStr(o.sectorName);
        if (sname !== undefined) {
            el.sectorName = sname;
        }
    } else if (type === 'zone') {
        const c = o.capacity;
        const cap = typeof c === 'number' && Number.isFinite(c)
            ? Math.floor(c)
            : (typeof c === 'string' && c.trim() ? Math.floor(Number(c)) : 0);
        el.capacity = Number.isFinite(cap) ? Math.max(0, cap) : 0;
    }

    return el;
};

const normalizeHallElementsFromJson = (raw: unknown): HallElementType[] => {
    if (!Array.isArray(raw)) {
        return [];
    }
    return raw.map(normalizeHallElementFromJson).filter((e): e is HallElementType => e != null && e.type != null && ['seat', 'table', 'stage', 'wall', 'sector', 'zone'].includes(e.type));
};

const createElement = (type: TypeSTWZEnum, x: number, y: number): HallElementType => {
    const base = PALETTE[type as TypeSTWZEnum];
    return {
        id: makeId(),
        type,
        x,
        y,
        width: base.width,
        height: base.height,
        rotation: 0,
        label: type === 'sector'
            ? 'Новый сектор'
            : (type === 'zone' ? 'Новый сектор без мест' : base.label),
        capacity: type === 'zone' ? 0 : undefined,
        ...(type === 'seat' ? DEFAULT_SEAT_LOGIC : {}),
    };
};

const DialogHallEditor = forwardRef<DialogHallEditorRef, {}>((props, ref) => {
    const [open, setOpen] = useState(false); // Не трогать для теста всегда открыт
    const [elements, setElements] = useState<HallElementType[]>([]);
    const [selectedIds, setSelectedIds] = useState<string[]>([]);
    const [toolForClickCreate, setToolForClickCreate] = useState<TypeSTWZEnum | null>(null);
    const [zoom, setZoom] = useState(1);
    const [seatDraftRect, setSeatDraftRect] = useState<SeatDraftRect | null>(null);
    const [selectionRect, setSelectionRect] = useState<SelectionRect | null>(null);
    const [seatNumberingOpen, setSeatNumberingOpen] = useState(false);
    const [seatNumberStart, setSeatNumberStart] = useState('1');
    const [seatNumberDirection, setSeatNumberDirection] = useState<'ltr' | 'rtl'>('ltr');
    const [seatRowsOpen, setSeatRowsOpen] = useState(false);
    const [seatRowStart, setSeatRowStart] = useState('1');
    const [seatRowDirection, setSeatRowDirection] = useState<'ttb' | 'btt'>('ttb');
    const [belts, setBelts] = useState<HallBeltType[]>([]);
    const elementsRef = useRef(elements);
    const beltsRef = useRef(belts);
    elementsRef.current = elements;
    beltsRef.current = belts;
    const hallUndoRef = useRef<HallEditorUndoSnapshot[]>([]);
    const [beltDialogOpen, setBeltDialogOpen] = useState(false);
    const [beltName, setBeltName] = useState('');
    const [seatSettingsOpen, setSeatSettingsOpen] = useState(false);
    const [seatSettingsSeatId, setSeatSettingsSeatId] = useState<string | null>(null);
    const [seatSettingsBeltId, setSeatSettingsBeltId] = useState<string>('');
    const [seatSettingsSeatNumber, setSeatSettingsSeatNumber] = useState<string>('');
    const [seatSettingsSeatRow, setSeatSettingsSeatRow] = useState<string>('');
    const [sectorSettingsOpen, setSectorSettingsOpen] = useState(false);
    const [sectorSettingsId, setSectorSettingsId] = useState<string | null>(null);
    const [sectorSettingsName, setSectorSettingsName] = useState('');
    const [sectorSettingsCapacity, setSectorSettingsCapacity] = useState('0');
    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 [importJsonOpen, setImportJsonOpen] = useState(false);
    const importHallFileInputRef = useRef<HTMLInputElement>(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);

    const onSaveRef = useRef<((value: HallType) => void) | null>(null);

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

            setOpen(true);

            onSaveRef.current = params.onSave ?? null;
            setBelts(params.initValue?.belts?.map((e) => {
                return {
                    id: e?.id ?? 'belt-' + Date.now(),
                    name: e?.name ?? '',
                    color: e?.color ?? HALL_LEGACY_BELT_COLOR,
                }
            }) ?? []);
            setElements(reconcileSeatSectors(normalizeHallElementsFromJson(params.initValue?.elements ?? [])));
            setSelectedIds([]);
            hallUndoRef.current = [];
        },
        close: () => {
            setOpen(false);
        }
    }));

    const handleSave = () => {



        if (!runHallCheck()) {
            return;
        }

        onSaveRef.current?.({
            version: 4,
            belts,
            elements,
        });
        setOpen(false);
    }

    // NOTE: Transformer нужно кормить нодами, которые уже попали в refs.
    // Поэтому вычисляем их в эффекте, а не во время рендера.
    const seatDefaults = useMemo(() => {
        const seats = elements.filter((e) => e.type === 'seat');
        if (!seats.length) {
            return {
                width: HALL_SEAT_SIZE,
                height: HALL_SEAT_SIZE,
                gapX: HALL_SEAT_GAP,
                gapY: HALL_SEAT_GAP,
            };
        }

        const widths = seats.map((s) => s.width).filter((n) => Number.isFinite(n) && n > 0);
        const heights = seats.map((s) => s.height).filter((n) => Number.isFinite(n) && n > 0);
        const medW = median(widths) ?? HALL_SEAT_SIZE;
        const medH = median(heights) ?? HALL_SEAT_SIZE;

        // Estimate gaps from nearest neighbors (within approximate rows/cols).
        const yTol = medH / 2;
        const xTol = medW / 2;

        const gapXs: number[] = [];
        const byY = [...seats].sort((a, b) => a.y - b.y);
        const rows: HallElementType[][] = [];
        byY.forEach((s) => {
            const last = rows[rows.length - 1];
            if (!last) {
                rows.push([s]);
                return;
            }
            const avgY = last.reduce((sum, it) => sum + it.y, 0) / last.length;
            if (Math.abs(s.y - avgY) <= yTol) {
                last.push(s);
            } else {
                rows.push([s]);
            }
        });
        rows.forEach((row) => {
            const sorted = [...row].sort((a, b) => a.x - b.x);
            for (let i = 0; i < sorted.length - 1; i++) {
                const a = sorted[i];
                const b = sorted[i + 1];
                const gap = b.x - (a.x + a.width);
                if (Number.isFinite(gap) && gap >= 0 && gap < medW * 3) {
                    gapXs.push(gap);
                }
            }
        });

        const gapYs: number[] = [];
        const byX = [...seats].sort((a, b) => a.x - b.x);
        const cols: HallElementType[][] = [];
        byX.forEach((s) => {
            const last = cols[cols.length - 1];
            if (!last) {
                cols.push([s]);
                return;
            }
            const avgX = last.reduce((sum, it) => sum + it.x, 0) / last.length;
            if (Math.abs(s.x - avgX) <= xTol) {
                last.push(s);
            } else {
                cols.push([s]);
            }
        });
        cols.forEach((col) => {
            const sorted = [...col].sort((a, b) => a.y - b.y);
            for (let i = 0; i < sorted.length - 1; i++) {
                const a = sorted[i];
                const b = sorted[i + 1];
                const gap = b.y - (a.y + a.height);
                if (Number.isFinite(gap) && gap >= 0 && gap < medH * 3) {
                    gapYs.push(gap);
                }
            }
        });

        return {
            width: medW,
            height: medH,
            gapX: median(gapXs) ?? HALL_SEAT_GAP,
            gapY: median(gapYs) ?? HALL_SEAT_GAP,
        };
    }, [elements]);

    const seatDraftStats = useMemo(() => {
        if (!seatDraftRect) {
            return null;
        }
        const width = Math.abs(seatDraftRect.currentX - seatDraftRect.startX);
        const height = Math.abs(seatDraftRect.currentY - seatDraftRect.startY);
        const cols = Math.max(1, Math.floor((width + seatDefaults.gapX) / (seatDefaults.width + seatDefaults.gapX)));
        const rows = Math.max(1, Math.floor((height + seatDefaults.gapY) / (seatDefaults.height + seatDefaults.gapY)));
        return {
            cols,
            rows,
            total: cols * rows,
            x: Math.min(seatDraftRect.startX, seatDraftRect.currentX),
            y: Math.min(seatDraftRect.startY, seatDraftRect.currentY),
            width,
            height,
        };
    }, [seatDraftRect, seatDefaults]);
    const seatDraftPreviewSeats = useMemo(() => {
        if (!seatDraftStats) {
            return [];
        }
        const items: Array<{ x: number; y: number; width: number; height: number; radius: number }> = [];
        for (let row = 0; row < seatDraftStats.rows; row++) {
            for (let col = 0; col < seatDraftStats.cols; col++) {
                items.push({
                    x: seatDraftStats.x + col * (seatDefaults.width + seatDefaults.gapX),
                    y: seatDraftStats.y + row * (seatDefaults.height + seatDefaults.gapY),
                    width: seatDefaults.width,
                    height: seatDefaults.height,
                    radius: 999,
                });
            }
        }
        return items;
    }, [seatDraftStats, seatDefaults]);
    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(
        () => elements.filter((item) => selectedIds.includes(item.id) && item.type === 'seat'),
        [elements, selectedIds]
    );
    const selectedSingleElement = useMemo(() => {
        if (selectedIds.length !== 1) {
            return null;
        }
        return elements.find((e) => e.id === selectedIds[0]) ?? null;
    }, [elements, selectedIds]);
    const showHallEditStage = selectedSingleElement?.type === 'stage';
    const showHallEditSector = selectedSingleElement?.type === 'sector' || selectedSingleElement?.type === 'zone';
    const showHallSeatActions = selectedSeats.length > 0;
    const selectedElements = useMemo(
        () => elements.filter((item) => selectedIds.includes(item.id)),
        [elements, selectedIds]
    );
    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) => {
            minX = Math.min(minX, el.x);
            minY = Math.min(minY, el.y);
            maxX = Math.max(maxX, el.x + el.width);
            maxY = Math.max(maxY, el.y + el.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]);

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

        const rafId = window.requestAnimationFrame(() => {
            const nodes = selectedIds
                .map((id) => nodeRefs.current[id])
                .filter(Boolean);

            trRef.current.nodes(nodes);
            trRef.current.getLayer()?.batchDraw();
        });

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

    // Auto-remove belts that have no seats.
    useEffect(() => {
        const usedBeltIds = new Set(
            elements
                .filter((e) => e.type === 'seat' && e.beltId != null && String(e.beltId).length > 0)
                .map((e) => e.beltId as string)
        );
        setBelts((prev) => {
            const next = prev.filter((b) => usedBeltIds.has(b.id));
            return next.length === prev.length ? prev : next;
        });
    }, [elements]);

    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));

    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 violatesNoSeatsSectorRule = (nextElements: HallElementType[]) => {
        const seats = nextElements.filter((e) => e.type === 'seat');
        const noSeatSectors = nextElements.filter((e) => e.type === 'zone');
        if (!seats.length || !noSeatSectors.length) {
            return false;
        }
        for (const s of seats) {
            const seatRect = { x: s.x, y: s.y, width: s.width, height: s.height };
            for (const z of noSeatSectors) {
                const zoneRect = { x: z.x, y: z.y, width: z.width, height: z.height };
                if (rectsIntersect(seatRect, zoneRect)) {
                    return true;
                }
            }
        }
        return false;
    };

    const runHallCheck = (): boolean => {
        const dupIds = collectHallDuplicateIds(elements);
        const noBeltIds = collectSeatIdsWithoutBelt(elements);
        const noCapacityIds = collectSectorNoSeatsIdsWithoutCapacity(elements);
        const ids = Array.from(new Set([...dupIds, ...noBeltIds, ...noCapacityIds]));
        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'));
        }
        if (noBeltIds.length) {
            parts.push(Lang.key('seats_without_belt'));
        }
        if (noCapacityIds.length) {
            parts.push(Lang.key('sector_without_seats_without_capacity'));
        }
        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({
            elements: structuredClone(elementsRef.current),
            belts: structuredClone(beltsRef.current),
        });
        while (hallUndoRef.current.length > HALL_UNDO_MAX) {
            hallUndoRef.current.shift();
        }
    }, []);

    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);
        setElements(reconcileSeatSectors(structuredClone(snap.elements)));
        setBelts(structuredClone(snap.belts));
        setSelectedIds((ids) => ids.filter((id) => snap.elements.some((e) => e.id === id)));
    }, []);

    const duplicateSelected = useCallback(() => {
        if (!selectedIds.length) {
            return;
        }
        const sources = elements.filter((item) => selectedIds.includes(item.id));
        if (!sources.length) {
            return;
        }
        pushHallUndoSnapshot();
        const copies: HallElementType[] = sources.map((source) => ({
            ...source,
            id: makeId(),
            x: source.x + 24,
            y: source.y + 24,
        }));
        setElements((prev) => reconcileSeatSectors([...prev, ...copies]));
        setSelectedIds(copies.map((copy) => copy.id));
    }, [elements, selectedIds, pushHallUndoSnapshot]);

    const deleteSelected = useCallback(() => {
        if (!selectedIds.length) {
            return;
        }
        const ids = selectedIds;
        pushHallUndoSnapshot();
        setElements((prev) => reconcileSeatSectors(prev.filter((item) => !ids.includes(item.id))));
        setSelectedIds([]);
    }, [selectedIds, pushHallUndoSnapshot]);

    useEffect(() => {
        const onKeyDown = (e: KeyboardEvent) => {
            const target = e.target as HTMLElement | null;
            const isTypingTarget = Boolean(
                target
                && (
                    target.tagName === 'INPUT'
                    || target.tagName === 'TEXTAREA'
                    || target.isContentEditable
                )
            );
            if (isTypingTarget) {
                return;
            }
            if (!open || !selectedIds.length) {
                return;
            }
            if (e.key === 'Delete' || e.key === 'Backspace') {
                deleteSelected();
            }
            if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'd') {
                e.preventDefault();
                duplicateSelected();
            }
        };
        window.addEventListener('keydown', onKeyDown);
        return () => window.removeEventListener('keydown', onKeyDown);
    }, [open, selectedIds, deleteSelected, duplicateSelected]);

    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: TypeSTWZEnum, x: number, y: number) => {
        const newElement = createElement(type, x, y);
        // If user creates a single seat, keep consistent size with existing seats.
        if (type === 'seat') {
            newElement.width = seatDefaults.width;
            newElement.height = seatDefaults.height;
        }
        pushHallUndoSnapshot();
        setElements((prev) => reconcileSeatSectors([...prev, newElement]));
        setSelectedIds([newElement.id]);
    };

    const createSeatsInRect = (draft: SeatDraftRect) => {
        const left = Math.min(draft.startX, draft.currentX);
        const top = Math.min(draft.startY, draft.currentY);
        const width = Math.abs(draft.currentX - draft.startX);
        const height = Math.abs(draft.currentY - draft.startY);
        if (width < 10 || height < 10) {
            return;
        }

        const cols = Math.max(1, Math.floor((width + seatDefaults.gapX) / (seatDefaults.width + seatDefaults.gapX)));
        const rows = Math.max(1, Math.floor((height + seatDefaults.gapY) / (seatDefaults.height + seatDefaults.gapY)));

        const seats: HallElementType[] = [];
        for (let row = 0; row < rows; row++) {
            for (let col = 0; col < cols; col++) {
                const seat = createElement(
                    'seat',
                    left + col * (seatDefaults.width + seatDefaults.gapX),
                    top + row * (seatDefaults.height + seatDefaults.gapY)
                );
                seat.width = seatDefaults.width;
                seat.height = seatDefaults.height;
                seats.push(seat);
            }
        }

        if (!seats.length) {
            return;
        }
        pushHallUndoSnapshot();
        setElements((prev) => reconcileSeatSectors([...prev, ...seats]));
        setSelectedIds(seats.map((seat) => seat.id));
    };

    const updateElement = (id: string, updater: (oldItem: HallElementType) => HallElementType) => {
        pushHallUndoSnapshot();
        setElements((prev) => reconcileSeatSectors(prev.map((item) => (item.id === id ? updater(item) : item))));
    };
    const applySeatNumbering = () => {
        const start = Number.parseInt(seatNumberStart, 10);
        if (!Number.isFinite(start) || start < 0 || !selectedSeats.length) {
            setSeatNumberingOpen(false);
            return;
        }

        const yTolerance = seatDefaults.height / 2;
        const sortedByY = [...selectedSeats].sort((a, b) => a.y - b.y);
        const rows: HallElementType[][] = [];
        sortedByY.forEach((seat) => {
            const lastRow = rows[rows.length - 1];
            if (!lastRow) {
                rows.push([seat]);
                return;
            }
            const rowY = lastRow.reduce((sum, item) => sum + item.y, 0) / lastRow.length;
            if (Math.abs(seat.y - rowY) <= yTolerance) {
                lastRow.push(seat);
            } else {
                rows.push([seat]);
            }
        });

        const seatNumbers = new Map<string, string>();
        rows.forEach((row) => {
            const sortedRow = [...row].sort((a, b) => (
                seatNumberDirection === 'ltr' ? a.x - b.x : b.x - a.x
            ));
            sortedRow.forEach((seat, idx) => {
                seatNumbers.set(seat.id, String(start + idx));
            });
        });

        pushHallUndoSnapshot();
        setElements((prev) => reconcileSeatSectors(prev.map((item) => (
            seatNumbers.has(item.id)
                ? { ...item, seatNumber: seatNumbers.get(item.id)! }
                : item
        ))));
        setSeatNumberingOpen(false);
    };
    const applySeatRows = () => {
        const start = Number.parseInt(seatRowStart, 10);
        if (!Number.isFinite(start) || start < 0 || !selectedSeats.length) {
            setSeatRowsOpen(false);
            return;
        }

        const yTolerance = seatDefaults.height / 2;
        const sortedByY = [...selectedSeats].sort((a, b) => a.y - b.y);
        const rows: HallElementType[][] = [];
        sortedByY.forEach((seat) => {
            const lastRow = rows[rows.length - 1];
            if (!lastRow) {
                rows.push([seat]);
                return;
            }
            const rowY = lastRow.reduce((sum, item) => sum + item.y, 0) / lastRow.length;
            if (Math.abs(seat.y - rowY) <= yTolerance) {
                lastRow.push(seat);
            } else {
                rows.push([seat]);
            }
        });

        const orderedRows = seatRowDirection === 'ttb' ? rows : [...rows].reverse();
        const seatRowsMap = new Map<string, string>();
        orderedRows.forEach((row, rowIdx) => {
            row.forEach((seat) => {
                seatRowsMap.set(seat.id, String(start + rowIdx));
            });
        });

        pushHallUndoSnapshot();
        setElements((prev) => reconcileSeatSectors(prev.map((item) => (
            seatRowsMap.has(item.id)
                ? { ...item, seatRow: seatRowsMap.get(item.id)! }
                : item
        ))));
        setSeatRowsOpen(false);
    };

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

    const saveSeatSettings = () => {
        if (!seatSettingsSeatId) {
            setSeatSettingsOpen(false);
            return;
        }
        const belt = seatSettingsBeltId
            ? belts.find((s) => s.id === seatSettingsBeltId) ?? null
            : null;

        const nextSeatNumber = seatSettingsSeatNumber.trim() ? seatSettingsSeatNumber.trim() : undefined;
        const nextSeatRow = seatSettingsSeatRow.trim() ? seatSettingsSeatRow.trim() : undefined;

        pushHallUndoSnapshot();
        setElements((prev) => reconcileSeatSectors(prev.map((item) => {
            if (item.id !== seatSettingsSeatId || item.type !== 'seat') {
                return item;
            }
            return {
                ...item,
                beltId: belt ? belt.id : undefined,
                seatNumber: nextSeatNumber,
                seatRow: nextSeatRow,
            };
        })));
        setSeatSettingsOpen(false);
    };

    const openSectorSettings = (sectorId: string) => {
        const sector = elements.find((s) => s.id === sectorId && (s.type === 'sector' || s.type === 'zone'));
        if (!sector) {
            return;
        }
        setSelectedIds([sectorId]);
        setSectorSettingsId(sectorId);
        const isNoSeats = sector.type === 'zone';
        setSectorSettingsIsNoSeats(isNoSeats);
        setSectorSettingsName(sector.label || (isNoSeats ? 'Новый сектор без мест' : 'Новый сектор'));
        setSectorSettingsCapacity(String(typeof sector.capacity === 'number' && Number.isFinite(sector.capacity) ? sector.capacity : 0));
        setSectorSettingsOpen(true);
    };

    const saveSectorSettings = () => {
        if (!sectorSettingsId) {
            setSectorSettingsOpen(false);
            return;
        }
        const name = sectorSettingsName.trim() || (sectorSettingsIsNoSeats ? 'Новый сектор без мест' : 'Новый сектор');
        const capRaw = Number(sectorSettingsCapacity);
        const capacity = sectorSettingsIsNoSeats && Number.isFinite(capRaw) ? Math.max(0, Math.floor(capRaw)) : undefined;
        pushHallUndoSnapshot();
        setElements((prev) => reconcileSeatSectors(prev.map((item) => {
            if (item.id !== sectorSettingsId) {
                return item;
            }
            if (item.type === 'sector') {
                return { ...item, label: name };
            }
            if (item.type === 'zone') {
                return { ...item, label: name, capacity };
            }
            return item;
        })));
        setSectorSettingsOpen(false);
    };

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

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

    const saveStageSettings = () => {
        if (!stageSettingsId) {
            setStageSettingsOpen(false);
            return;
        }
        const name = stageSettingsName.trim() || 'Сцена';
        pushHallUndoSnapshot();
        setElements((prev) => prev.map((item) => (
            item.id === stageSettingsId && isStage(item.type as TypeSTWZEnum)
                ? { ...item, label: name }
                : item
        )));
        setStageSettingsOpen(false);
    };

    const getHallExportJsonText = () => {
        const payload: HallType = {
            version: 4,
            belts,
            elements,
        };
        return JSON.stringify(payload, null, 2);
    };

    const saveHallJsonToFile = async () => {
        const text = getHallExportJsonText();
        const blob = new Blob([text], { type: 'application/json;charset=utf-8' });
        try {
            const w = window as Window & {
                showSaveFilePicker?: (options?: {
                    suggestedName?: string;
                    types?: Array<{ description: string; accept: Record<string, string[]> }>;
                }) => Promise<FileSystemFileHandle>;
            };
            if (typeof w.showSaveFilePicker === 'function') {
                const handle = await w.showSaveFilePicker({
                    suggestedName: 'hall.json',
                    types: [{ description: 'JSON', accept: { 'application/json': ['.json'] } }],
                });
                const writable = await handle.createWritable();
                await writable.write(blob);
                await writable.close();
            } else {
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = 'hall.json';
                a.rel = 'noopener';
                a.click();
                URL.revokeObjectURL(url);
            }
        } catch (e) {
            if (e instanceof DOMException && e.name === 'AbortError') {
                return;
            }
            toast.error('Не удалось сохранить файл');
        }
    };

    const applyHallImportFromText = (raw: string) => {
        try {
            const trimmed = raw?.trim() ? raw : '{}';
            const payload: HallType = JSON.parse(trimmed) as HallType;
            if (!payload || typeof payload !== 'object') {
                toast.error('Некорректный JSON');
                return;
            }
            hallUndoRef.current = [];
            setBelts(payload.belts ?? []);
            setElements(reconcileSeatSectors(normalizeHallElementsFromJson(payload.elements ?? [])));
            setSelectedIds([]);
            setImportJsonOpen(false);
        } catch {
            toast.error('Некорректный JSON');
        }
    };

    const onImportHallFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const file = e.target.files?.[0];
        e.target.value = '';
        if (!file) {
            return;
        }
        const reader = new FileReader();
        reader.onload = () => {
            applyHallImportFromText(String(reader.result ?? ''));
        };
        reader.onerror = () => {
            toast.error('Не удалось прочитать файл');
        };
        reader.readAsText(file, 'UTF-8');
    };

    const createBeltFromSelected = () => {
        const name = beltName.trim();
        if (!name || !selectedSeats.length) {
            setBeltDialogOpen(false);
            return;
        }
        const beltId = makeId();
        const color = getAutoBeltColorForIndex(belts.length);
        const newBelt: HallBeltType = {
            id: beltId,
            name,
            color,
        };
        const selectedSeatIds = new Set(selectedSeats.map((seat) => seat.id));
        pushHallUndoSnapshot();
        setBelts((prev) => [...prev, newBelt]);
        setElements((prev) => prev.map((item) => (
            item.type === 'seat' && selectedSeatIds.has(item.id)
                ? { ...item, beltId }
                : item
        )));
        setBeltDialogOpen(false);
        setBeltName('');
    };
    const selectBeltSeats = (beltId: string) => {
        const ids = elements
            .filter((item) => item.type === 'seat' && item.beltId === beltId)
            .map((item) => item.id);
        setSelectedIds(ids);
    };
    const deleteBelt = (beltId: string) => {
        pushHallUndoSnapshot();
        setBelts((prev) => prev.filter((belt) => belt.id !== beltId));
        setElements((prev) => prev.map((item) => (
            item.type === 'seat' && item.beltId === beltId
                ? { ...item, beltId: undefined }
                : item
        )));
    };
    const addSelectedToBelt = (belt: HallBeltType) => {
        if (!selectedSeats.length) {
            return;
        }
        const selectedSeatIds = new Set(selectedSeats.map((seat) => seat.id));
        pushHallUndoSnapshot();
        setElements((prev) => prev.map((item) => (
            item.type === 'seat' && selectedSeatIds.has(item.id)
                ? { ...item, beltId: belt.id }
                : item
        )));
    };
    const removeSelectedFromBelt = (beltId: string) => {
        if (!selectedSeats.length) {
            return;
        }
        const selectedSeatIds = new Set(selectedSeats.map((seat) => seat.id));
        pushHallUndoSnapshot();
        setElements((prev) => prev.map((item) => (
            item.type === 'seat' && selectedSeatIds.has(item.id) && item.beltId === beltId
                ? { ...item, beltId: undefined }
                : item
        )));
    };

    return <Dialog
        open={open}
        onOpenChange={setOpen}
    >
        <DialogContent className="!w-[100vw] !max-w-[100vw] sm:!max-w-[100vw] h-[100vh] overflow-hidden !p-0" >

            {open && <Grid className='h-full w-full !w-[100vw] !max-w-[100vw] sm:!max-w-[100vw] h-[100vh]'>

                <GridCol className='bg-white'>

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

                        <Tooltip>
                            <TooltipTrigger asChild>
                                <Button
                                    size="sm"
                                    type="button"
                                    variant="outline"
                                    aria-label={Lang.key('zoom_out')}
                                    onClick={() => setZoom((z) => clampZoom(Number((z - HALL_ZOOM.step).toFixed(2))))}
                                >
                                    <ZoomOut />
                                </Button>
                            </TooltipTrigger>
                            <TooltipContent>
                                <p>{Lang.key('zoom_out')}</p>
                            </TooltipContent>
                        </Tooltip>

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

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

                        <Tooltip>
                            <TooltipTrigger asChild>
                                <Button
                                    size="sm"
                                    type="button"
                                    variant="outline"
                                    aria-label={Lang.key('hall_undo')}
                                    onClick={() => undoLastHallChange()}
                                >
                                    <Undo2 />
                                </Button>
                            </TooltipTrigger>
                            <TooltipContent>
                                <p>{Lang.key('hall_undo')}</p>
                            </TooltipContent>
                        </Tooltip>

                        <Tooltip>
                            <TooltipTrigger asChild>
                                <Button
                                    size="sm"
                                    type="button"
                                    variant={!freezeSectorSelection ? 'outline' : 'default'}
                                    aria-label={Lang.key('fix_hall_sector')}
                                    onClick={() => setFreezeSectorSelection((v) => !v)}
                                >
                                    <VaultIcon />
                                </Button>
                            </TooltipTrigger>
                            <TooltipContent>
                                <p>{Lang.key('fix_hall_sector')}</p>
                            </TooltipContent>
                        </Tooltip>

                        <Separator orientation="vertical" className="h-8 self-center" />


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

                        {(showHallEditStage || showHallEditSector || showHallSeatActions || selectedIds.length > 0) && (
                            <Separator orientation="vertical" className="h-8 self-center" />
                        )}

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

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

                        {showHallSeatActions && (
                            <>
                                <Tooltip>
                                    <TooltipTrigger asChild>
                                        <Button
                                            size="sm"
                                            type="button"
                                            variant="outline"
                                            aria-label={Lang.key('hall_create_belt')}
                                            onClick={() => setBeltDialogOpen(true)}
                                        >
                                            <Tag />
                                        </Button>
                                    </TooltipTrigger>
                                    <TooltipContent>
                                        <p>{Lang.key('hall_create_belt')}</p>
                                    </TooltipContent>
                                </Tooltip>
                                <Tooltip>
                                    <TooltipTrigger asChild>
                                        <Button
                                            size="sm"
                                            type="button"
                                            variant="outline"
                                            aria-label={Lang.key('hall_set_seat_rows')}
                                            onClick={() => setSeatRowsOpen(true)}
                                        >
                                            <RowsIcon />
                                        </Button>
                                    </TooltipTrigger>
                                    <TooltipContent>
                                        <p>{Lang.key('hall_set_seat_rows')}</p>
                                    </TooltipContent>
                                </Tooltip>
                                <Tooltip>
                                    <TooltipTrigger asChild>
                                        <Button
                                            size="sm"
                                            type="button"
                                            variant="outline"
                                            aria-label={Lang.key('hall_set_seat_numbers')}
                                            onClick={() => setSeatNumberingOpen(true)}
                                        >
                                            <Grid2X2 />
                                        </Button>
                                    </TooltipTrigger>
                                    <TooltipContent>
                                        <p>{Lang.key('hall_set_seat_numbers')}</p>
                                    </TooltipContent>
                                </Tooltip>
                                <Tooltip>
                                    <TooltipTrigger asChild>
                                        <Button
                                            size="sm"
                                            type="button"
                                            variant="outline"
                                            aria-label={Lang.key('hall_copy_selected_seats')}
                                            onClick={() => duplicateSelected()}
                                        >
                                            <CopyIcon />
                                        </Button>
                                    </TooltipTrigger>
                                    <TooltipContent>
                                        <p>{Lang.key('hall_copy_selected_seats')}</p>
                                    </TooltipContent>
                                </Tooltip>
                            </>
                        )}

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

                        <Separator orientation="vertical" />

                        <Tooltip>
                            <TooltipTrigger asChild>
                                <Button
                                    size="sm"
                                    type="button"
                                    variant="outline"
                                    aria-label={Lang.key('hall_export_json')}
                                    onClick={() => {
                                        void saveHallJsonToFile();
                                    }}
                                >
                                    <Download />
                                </Button>
                            </TooltipTrigger>
                            <TooltipContent>
                                <p>{Lang.key('hall_export_json')}</p>
                            </TooltipContent>
                        </Tooltip>

                        <Tooltip>
                            <TooltipTrigger asChild>
                                <Button
                                    size="sm"
                                    type="button"
                                    variant="outline"
                                    aria-label={Lang.key('hall_import_json')}
                                    onClick={() => setImportJsonOpen(true)}
                                >
                                    <Upload />
                                </Button>
                            </TooltipTrigger>
                            <TooltipContent>
                                <p>{Lang.key('hall_import_json')}</p>
                            </TooltipContent>
                        </Tooltip>
                        <Separator orientation="vertical" />


                        <Popover>
                            <PopoverTrigger>

                                <Tooltip>
                                    <TooltipTrigger>
                                        <Button
                                            size="sm"
                                            type="button"
                                            variant="outline"
                                            aria-label={Lang.key('hall_save_and_close')}
                                        >
                                            <GroupIcon />
                                        </Button>
                                        <TooltipContent>

                                            <p>{Lang.key('hall_belts')}</p>
                                        </TooltipContent>
                                    </TooltipTrigger>
                                </Tooltip>

                            </PopoverTrigger>
                            <PopoverContent className="w-80">
                                <BeltsPanel
                                    belts={belts}
                                    selectedSeatsCount={selectedSeats.length}
                                    onSelectBelt={selectBeltSeats}
                                    onAddSelectedToBelt={addSelectedToBelt}
                                    onRemoveSelectedFromBelt={removeSelectedFromBelt}
                                    onDeleteBelt={deleteBelt}
                                />

                            </PopoverContent>
                        </Popover>



                        <Separator orientation="vertical" />
                        {/*Сохранить и закрыть*/}
                        <Tooltip>
                            <TooltipTrigger asChild>
                                <Button
                                    onClick={() => {

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



                    </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 TypeSTWZEnum;
                                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: HALL_STAGE_SIZE * zoom,
                                    height: HALL_STAGE_SIZE * zoom,
                                    background: HALL_CANVAS.background,
                                    outline: HALL_CANVAS.outline,
                                    outlineOffset: '0px',
                                    margin: 'auto',
                                }}
                            >
                                <Stage
                                    ref={stageRef}
                                    width={HALL_STAGE_SIZE * zoom}
                                    height={HALL_STAGE_SIZE * zoom}
                                    scaleX={zoom}
                                    scaleY={zoom}
                                    onMouseDown={(e) => {
                                        const clickedOnEmpty = e.target === e.target.getStage();
                                        if (clickedOnEmpty) {
                                            if (toolForClickCreate) {
                                                const pos = getPointerPositionInStage();
                                                if (toolForClickCreate === 'seat') {
                                                    setSeatDraftRect({
                                                        startX: pos.x,
                                                        startY: pos.y,
                                                        currentX: pos.x,
                                                        currentY: pos.y,
                                                    });
                                                } else {
                                                    addElement(toolForClickCreate, pos.x, pos.y);
                                                    setToolForClickCreate(null);
                                                }
                                            } 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,
                                                });
                                            }
                                        }
                                    }}
                                    onMouseMove={() => {
                                        const pos = getPointerPositionInStage();
                                        setSeatDraftRect((prev) => {
                                            if (!prev) {
                                                return null;
                                            }
                                            return {
                                                ...prev,
                                                currentX: pos.x,
                                                currentY: pos.y,
                                            };
                                        });
                                        setSelectionRect((prev) => (prev ? {
                                            ...prev,
                                            currentX: pos.x,
                                            currentY: pos.y,
                                        } : null));
                                    }}
                                    onMouseUp={() => {
                                        if (!seatDraftRect) {
                                            if (!selectionRect || !normalizedSelectionRect) {
                                                return;
                                            }
                                            const tooSmall = normalizedSelectionRect.width < 4 || normalizedSelectionRect.height < 4;
                                            if (tooSmall) {
                                                if (!selectionRect.appendToSelection) {
                                                    setSelectedIds([]);
                                                }
                                                setSelectionRect(null);
                                                return;
                                            }
                                            const selectedInRect = elements.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 && (item.type === 'sector' || item.type === 'zone')) {
                                                    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);
                                            return;
                                        }
                                        createSeatsInRect(seatDraftRect);
                                        setSeatDraftRect(null);
                                        setToolForClickCreate(null);
                                    }}
                                >
                                    <Layer>
                                        {/* sectors always behind other elements */}
                                        {elements.filter((e) => e.type === 'sector' || e.type === 'zone').map((item) => (
                                            <React.Fragment key={item.id}>
                                                {/* sector (interactive like a normal element) */}
                                                <Rect
                                                    ref={(node) => {
                                                        if (node) {
                                                            nodeRefs.current[item.id] = node;
                                                        }
                                                    }}
                                                    x={item.x}
                                                    y={item.y}
                                                    width={item.width}
                                                    height={item.height}
                                                    rotation={item.rotation}
                                                    fill={HALL_SECTOR.fill}
                                                    stroke={HALL_SECTOR.stroke}
                                                    strokeWidth={HALL_SECTOR.strokeWidth}
                                                    cornerRadius={getHallBlockCornerRadius(item.type as TypeSTWZEnum)}
                                                    draggable
                                                    listening={!freezeSectorSelection}
                                                    onClick={(e) => {
                                                        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) => {
                                                        e.cancelBubble = true;
                                                        if (item.type === 'sector' || item.type === 'zone') {
                                                            openSectorSettings(item.id);
                                                        }
                                                    }}
                                                    onTap={() => setSelectedIds([item.id])}
                                                    onDragStart={(e) => {
                                                        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() };
                                                            }
                                                        });
                                                        // На всякий случай фиксируем старт активного узла из текущего ref.
                                                        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) => {
                                                        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) => {
                                                        if (activeDragRef.current && selectedIds.length > 1) {
                                                            const startPositions = dragGroupStartRef.current;
                                                            const prevEls = elementsRef.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 {
                                                                pushHallUndoSnapshot();
                                                                setElements(reconcileSeatSectors(proposed));
                                                            }
                                                            activeDragRef.current = null;
                                                            dragGroupStartRef.current = {};
                                                            dragGroupSelectedIdsRef.current = [];
                                                            dragGroupPointerStartRef.current = null;
                                                            return;
                                                        }
                                                        const start = singleDragStartRef.current?.id === item.id ? singleDragStartRef.current : null;
                                                        const prevElsSingle = elementsRef.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;
                                                        }
                                                        pushHallUndoSnapshot();
                                                        setElements(reconcileSeatSectors(proposedSingle));
                                                    }}
                                                    onTransformEnd={(e) => {
                                                        const node = e.target;
                                                        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 = elements.map((el) => (
                                                            el.id === item.id ? {
                                                                ...el,
                                                                x: node.x(),
                                                                y: node.y(),
                                                                rotation: node.rotation(),
                                                                width: nextWidth,
                                                                height: nextHeight,
                                                            } : el
                                                        ));
                                                        if (violatesNoSeatsSectorRule(proposed)) {
                                                            // revert via state (safe) by snapping node back to element values
                                                            const current = elements.find((el) => el.id === 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 }));
                                                    }}
                                                />
                                                <Text
                                                    x={item.x}
                                                    y={item.type === 'zone' ? (item.y + item.height / 2 - 8) : (item.y - 22)}
                                                    width={item.type === 'zone' ? item.width : item.width}
                                                    align={item.type === 'zone' ? 'center' : 'center'}
                                                    text={item.label}
                                                    fontSize={item.type === 'zone' ? 16 : 14}
                                                    fill={item.type === 'zone' ? HALL_SECTOR.labelNoSeats : HALL_SECTOR.labelRegular}
                                                    listening={false}
                                                />
                                                {item.type === 'zone' && (
                                                    <Text
                                                        x={item.x}
                                                        y={item.y + item.height / 2 + 12}
                                                        width={item.width}
                                                        align="center"
                                                        text={`Вместимость: ${Math.max(0, Math.floor(Number(item.capacity ?? 0) || 0))} человек`}
                                                        fontSize={12}
                                                        fill={HALL_SECTOR.capacityText}
                                                        listening={false}
                                                    />
                                                )}
                                            </React.Fragment>
                                        ))}
                                    </Layer>

                                    <Layer>
                                        {elements.filter((e) => e.type !== 'sector' && e.type !== 'zone').map((item) => (
                                            <React.Fragment key={item.id}>
                                                <Rect
                                                    ref={(node) => {
                                                        if (node) {
                                                            nodeRefs.current[item.id] = node;
                                                        }
                                                    }}
                                                    x={item.x}
                                                    y={item.y}
                                                    width={item.width}
                                                    height={item.height}
                                                    rotation={item.rotation}
                                                    fill={resolveHallElementFill(item, belts)}
                                                    cornerRadius={getHallBlockCornerRadius(item.type as TypeSTWZEnum)}
                                                    draggable
                                                    onClick={(e) => {
                                                        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) => {
                                                        e.cancelBubble = true;
                                                        if (item.type === 'seat') {
                                                            openSeatSettings(item.id);
                                                        } else if (item.type === 'stage') {
                                                            openStageSettings(item.id);
                                                        }
                                                    }}
                                                    onTap={() => setSelectedIds([item.id])}
                                                    onDragStart={(e) => {
                                                        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() };
                                                            }
                                                        });
                                                        // На всякий случай фиксируем старт активного узла из текущего ref.
                                                        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) => {
                                                        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) => {
                                                        if (activeDragRef.current && selectedIds.length > 1) {
                                                            const startPositions = dragGroupStartRef.current;
                                                            const prevEls = elementsRef.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 {
                                                                pushHallUndoSnapshot();
                                                                setElements(reconcileSeatSectors(proposed));
                                                            }
                                                            activeDragRef.current = null;
                                                            dragGroupStartRef.current = {};
                                                            dragGroupSelectedIdsRef.current = [];
                                                            dragGroupPointerStartRef.current = null;
                                                            return;
                                                        }
                                                        const start = singleDragStartRef.current?.id === item.id ? singleDragStartRef.current : null;
                                                        const prevElsSingle = elementsRef.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;
                                                        }
                                                        pushHallUndoSnapshot();
                                                        setElements(reconcileSeatSectors(proposedSingle));
                                                    }}
                                                    onTransformEnd={(e) => {
                                                        const node = e.target;
                                                        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,
                                                        }));
                                                    }}
                                                />
                                                <Text
                                                    x={item.x - 18}
                                                    y={item.y + item.height / 2 - 6}
                                                    width={16}
                                                    align="right"
                                                    text={item.type === 'seat' ? (item.seatRow ?? '') : ''}
                                                    fontSize={12}
                                                    fill={HALL_LABELS.seatRow}
                                                    listening={false}
                                                    visible={item.type === 'seat' && Boolean(item.seatRow)}
                                                />
                                                <Text
                                                    x={item.type === 'seat' ? item.x : item.x + 6}
                                                    y={item.type === 'seat' ? item.y + item.height / 2 - 6 : item.y + 6}
                                                    width={item.type === 'seat' ? item.width : undefined}
                                                    align={item.type === 'seat' ? 'center' : undefined}
                                                    text={item.type === 'seat' ? (item.seatNumber ?? '') : (item.type === 'wall' ? '' : item.label)}
                                                    fontSize={12}
                                                    fill={getHallBlockOnShapeTextFill(item.type as TypeSTWZEnum)}
                                                    listening={false}
                                                    visible={(item.type !== 'seat' && item.type !== 'stage') || Boolean(item.seatNumber)}
                                                />
                                                <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}
                                                        width={previewSeat.width}
                                                        height={previewSeat.height}
                                                        cornerRadius={previewSeat.radius}
                                                        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={`Rows: ${seatDraftStats.rows}  Seats in row: ${seatDraftStats.cols}  Total: ${seatDraftStats.total}`}
                                                    fontSize={12}
                                                    fill={HALL_SEAT_DRAFT.statsText}
                                                    listening={false}
                                                />
                                            </>
                                        )}
                                        {seatDraftRect && !seatDraftStats && (
                                            <Rect
                                                x={Math.min(seatDraftRect.startX, seatDraftRect.currentX)}
                                                y={Math.min(seatDraftRect.startY, seatDraftRect.currentY)}
                                                width={Math.abs(seatDraftRect.currentX - seatDraftRect.startX)}
                                                height={Math.abs(seatDraftRect.currentY - seatDraftRect.startY)}
                                                fill={HALL_SEAT_DRAFT.rectFill}
                                                stroke={HALL_SEAT_DRAFT.rectStroke}
                                                dash={[6, 6]}
                                                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}
                                            />
                                        )}

                                        {/* Drag by visible selection frame (multi-select) */}
                                        {selectionBounds && !seatDraftRect && !selectionRect && (
                                            <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 = elementsRef.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 {
                                                        pushHallUndoSnapshot();
                                                        setElements(reconcileSeatSectors(proposed));
                                                    }
                                                    selectionFrameDragStartRef.current = {};
                                                    selectionFramePointerStartRef.current = null;
                                                }}
                                            />
                                        )}
                                        {hallCheckFlash?.ids.map((id) => {
                                            const el = elements.find((e) => e.id === id);
                                            if (!el) {
                                                return null;
                                            }
                                            const o = hallCheckOverlayOpacity(hallCheckPulseMs);
                                            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 TypeSTWZEnum)}
                                                    stroke={HALL_DUPLICATE_HIGHLIGHT_STROKE}
                                                    strokeWidth={5}
                                                    fillEnabled={false}
                                                    opacity={o}
                                                    listening={false}
                                                />
                                            );
                                        })}
                                        <Transformer
                                            ref={trRef}
                                            rotateEnabled
                                            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;
                                            }}
                                        />
                                    </Layer>
                                </Stage>
                            </div>
                        </div>
                    </GridRow>



                </GridCol>
            </Grid>}



            <SeatNumberingDialog
                open={seatNumberingOpen}
                onOpenChange={setSeatNumberingOpen}
                startFrom={seatNumberStart}
                direction={seatNumberDirection}
                onStartFromChange={setSeatNumberStart}
                onDirectionChange={setSeatNumberDirection}
                onApply={applySeatNumbering}
            />
            <BeltCreateDialog
                open={beltDialogOpen}
                onOpenChange={setBeltDialogOpen}
                beltName={beltName}
                nextBeltColor={getAutoBeltColorForIndex(belts.length)}
                onBeltNameChange={setBeltName}
                onCreate={createBeltFromSelected}
            />
            <SeatSettingsDialog
                open={seatSettingsOpen}
                onOpenChange={setSeatSettingsOpen}
                belts={belts}
                seatBeltId={seatSettingsBeltId}
                onSeatBeltIdChange={setSeatSettingsBeltId}
                seatNumber={seatSettingsSeatNumber}
                onSeatNumberChange={setSeatSettingsSeatNumber}
                seatRow={seatSettingsSeatRow}
                onSeatRowChange={setSeatSettingsSeatRow}
                onApply={saveSeatSettings}
            />
            <SectorSettingsDialog
                open={sectorSettingsOpen}
                onOpenChange={setSectorSettingsOpen}
                name={sectorSettingsName}
                onNameChange={setSectorSettingsName}
                capacity={sectorSettingsIsNoSeats ? sectorSettingsCapacity : undefined}
                onCapacityChange={sectorSettingsIsNoSeats ? setSectorSettingsCapacity : undefined}
                onApply={saveSectorSettings}
            />
            <StageSettingsDialog
                open={stageSettingsOpen}
                onOpenChange={setStageSettingsOpen}
                name={stageSettingsName}
                onNameChange={setStageSettingsName}
                onApply={saveStageSettings}
            />
            <input
                ref={importHallFileInputRef}
                type="file"
                accept=".json,application/json"
                className="hidden"
                onChange={onImportHallFileChange}
            />
            <Dialog open={importJsonOpen} onOpenChange={setImportJsonOpen}>
                <DialogContent className="max-w-md">
                    <div className="space-y-2">
                        <div className="text-sm font-semibold">{Lang.key('hall_import_json_title')}</div>
                        <div className="text-sm text-slate-600">
                            {Lang.key('hall_import_json_hint')}
                        </div>
                    </div>
                    <DialogFooter className="gap-2 sm:gap-0">
                        <Button type="button" variant="outline" onClick={() => setImportJsonOpen(false)}>
                            {Lang.key('cancel')}
                        </Button>
                        <Button
                            type="button"
                            onClick={() => importHallFileInputRef.current?.click()}
                        >
                            {Lang.key('hall_import_json_choose_file')}
                        </Button>
                    </DialogFooter>
                </DialogContent>
            </Dialog>
            <SeatRowsDialog
                open={seatRowsOpen}
                onOpenChange={setSeatRowsOpen}
                startFrom={seatRowStart}
                direction={seatRowDirection}
                onStartFromChange={setSeatRowStart}
                onDirectionChange={setSeatRowDirection}
                onApply={applySeatRows}
            />


            <DialogFooter>
                <Button onClick={() => {
                    setOpen(false);
                }}>
                    {Lang.key('cancel')}
                </Button>
                <Button onClick={() => {

                    setOpen(false);
                }}>
                    {Lang.key('save')}
                </Button>
            </DialogFooter>
        </DialogContent>

    </Dialog>

});

export default DialogHallEditor;