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

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

import {
    HallBeltType,
    HallElementType,
    HallSeatType,
    HallSectorType,
    HallStageType,
    HallTableType,
    HallType,
    HallWallType,
    HallZoneType,
    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 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 {
    HALL_SEAT_FILL_DIRECTION_DEFAULT_ROWS,
    HALL_SEAT_FILL_DIRECTION_DEFAULT_SEAT_NUMBERS,
    type HallSeatFillDirection,
    hallSeatsGroupedLinesOrdered,
} from './hallSeatOrder';
import {
    hallSeatLabelAt,
    parseHallSeatStart,
} from './hallSeatStartLabel';
import {
    formatHallSeatRowSlashNumber,
    getAutoBeltColorForIndex,
    getHallBlockCornerRadius,
    getHallBlockOnShapeTextFill,
    HALL_CANVAS,
    HALL_CHECK_FLASH_MS,
    HALL_DUPLICATE_HIGHLIGHT_STROKE,
    HALL_EDITOR_GRID,
    HALL_ELEMENT_DEFAULTS,
    HALL_LEGACY_BELT_COLOR,
    HALL_SEAT_DRAFT,
    HALL_SEAT_GAP,
    HALL_SEAT_SIZE,
    HALL_SECTOR,
    HALL_SELECTION,
    HALL_STAGE_SIZE,
    HALL_ZOOM,
    type HallElementPaletteDefaults,
    hallSeatLabelFill,
    hallSeatLabelFontSizeForRect,
    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;

/** Как в `HallType`: отдельные массивы по смыслу блока. */
type HallLayoutBuckets = {
    seats: HallElementType[];
    tables: HallElementType[];
    stages: HallElementType[];
    walls: HallElementType[];
    sectors: HallElementType[];
    zones: HallElementType[];
};

const emptyHallLayout = (): HallLayoutBuckets => ({
    seats: [],
    tables: [],
    stages: [],
    walls: [],
    sectors: [],
    zones: [],
});

const HALL_MERGE_BUCKET_ORDER: (keyof HallLayoutBuckets)[] = ['sectors', 'zones', 'stages', 'tables', 'walls', 'seats'];

const mergeHallLayoutBuckets = (b: HallLayoutBuckets): HallElementType[] => (
    HALL_MERGE_BUCKET_ORDER.flatMap((k) => b[k])
);

const splitHallElements = (items: HallElementType[]): HallLayoutBuckets => {
    const out = emptyHallLayout();
    for (const e of items) {
        switch (e.type) {
            case 'seat': out.seats.push(e); break;
            case 'table': out.tables.push(e); break;
            case 'stage': out.stages.push(e); break;
            case 'wall': out.walls.push(e); break;
            case 'sector': out.sectors.push(e); break;
            case 'zone': out.zones.push(e); break;
            default: break;
        }
    }
    return out;
};

const hallSplitCount = (h: HallType | null | undefined): number => {
    if (!h) {
        return 0;
    }
    return (
        (h.seats?.length ?? 0)
        + (h.tables?.length ?? 0)
        + (h.stages?.length ?? 0)
        + (h.walls?.length ?? 0)
        + (h.zones?.length ?? 0)
        + (h.sectors?.length ?? 0)
    );
};

const apiSeatToEditor = (s: HallSeatType): HallElementType => ({
    id: s.id,
    type: 'seat',
    x: s.x,
    y: s.y,
    width: 0,
    height: 0,
    rotation: s.rotation ?? 0,
    label: '',
    radius: s.radius ?? HALL_SEAT_SIZE / 2,
    seatNumber: s.seatNumber,
    seatRow: s.seatRow,
    beltId: s.beltId,
    sectorId: s.sectorId,
    sectorName: s.sectorName,
});

const apiTableToEditor = (t: HallTableType): HallElementType => ({
    id: t.id,
    type: 'table',
    x: t.x,
    y: t.y,
    width: t.width,
    height: t.height,
    rotation: t.rotation ?? 0,
    label: t.label ?? '',
});

const apiStageToEditor = (s: HallStageType): HallElementType => ({
    id: s.id,
    type: 'stage',
    x: s.x,
    y: s.y,
    width: s.width,
    height: s.height,
    rotation: s.rotation ?? 0,
    label: s.label ?? 'Сцена',
});

const apiWallToEditor = (w: HallWallType): HallElementType => ({
    id: w.id,
    type: 'wall',
    x: w.x,
    y: w.y,
    width: w.width,
    height: w.height,
    rotation: w.rotation ?? 0,
    label: '',
});

const apiSectorToEditor = (s: HallSectorType): HallElementType => ({
    id: s.id,
    type: 'sector',
    x: s.x,
    y: s.y,
    width: s.width,
    height: s.height,
    rotation: s.rotation ?? 0,
    label: s.label ?? '',
});

const apiZoneToEditor = (z: HallZoneType): HallElementType => ({
    id: z.id,
    type: 'zone',
    x: z.x,
    y: z.y,
    width: z.width,
    height: z.height,
    rotation: z.rotation ?? 0,
    label: z.label ?? '',
    capacity: z.capacity ?? 0,
});

const apiHallToEditorBuckets = (h: HallType): HallLayoutBuckets => ({
    sectors: (h.sectors ?? []).map(apiSectorToEditor),
    zones: (h.zones ?? []).map(apiZoneToEditor),
    stages: (h.stages ?? []).map(apiStageToEditor),
    tables: (h.tables ?? []).map(apiTableToEditor),
    walls: (h.walls ?? []).map(apiWallToEditor),
    seats: (h.seats ?? []).map(apiSeatToEditor),
});

const findInLayout = (b: HallLayoutBuckets, id: string): HallElementType | undefined => {
    for (const k of HALL_MERGE_BUCKET_ORDER) {
        const hit = b[k].find((e) => e.id === id);
        if (hit) {
            return hit;
        }
    }
    return undefined;
};

const mapItemInLayout = (
    b: HallLayoutBuckets,
    id: string,
    fn: (e: HallElementType) => HallElementType,
): HallLayoutBuckets => {
    const mapArr = (arr: HallElementType[]) => arr.map((e) => (e.id === id ? fn(e) : e));
    return {
        seats: mapArr(b.seats),
        tables: mapArr(b.tables),
        stages: mapArr(b.stages),
        walls: mapArr(b.walls),
        sectors: mapArr(b.sectors),
        zones: mapArr(b.zones),
    };
};

const filterIdsFromLayout = (b: HallLayoutBuckets, drop: Set<string>): HallLayoutBuckets => {
    const f = (arr: HallElementType[]) => arr.filter((e) => !drop.has(e.id));
    return {
        seats: f(b.seats),
        tables: f(b.tables),
        stages: f(b.stages),
        walls: f(b.walls),
        sectors: f(b.sectors),
        zones: f(b.zones),
    };
};

const appendToLayoutByType = (b: HallLayoutBuckets, el: HallElementType): HallLayoutBuckets => {
    switch (el.type) {
        case 'seat': return { ...b, seats: [...b.seats, el] };
        case 'table': return { ...b, tables: [...b.tables, el] };
        case 'stage': return { ...b, stages: [...b.stages, el] };
        case 'wall': return { ...b, walls: [...b.walls, el] };
        case 'sector': return { ...b, sectors: [...b.sectors, el] };
        case 'zone': return { ...b, zones: [...b.zones, el] };
        default: return b;
    }
};

const appendManyToLayout = (b: HallLayoutBuckets, els: HallElementType[]): HallLayoutBuckets => (
    els.reduce((acc, el) => appendToLayoutByType(acc, el), b)
);

const mapAllItemsInLayout = (
    b: HallLayoutBuckets,
    fn: (e: HallElementType) => HallElementType,
): HallLayoutBuckets => ({
    seats: b.seats.map(fn),
    tables: b.tables.map(fn),
    stages: b.stages.map(fn),
    walls: b.walls.map(fn),
    sectors: b.sectors.map(fn),
    zones: b.zones.map(fn),
});

const reconcileSeatSectorsLayout = (layout: HallLayoutBuckets): HallLayoutBuckets => {
    const sectors = layout.sectors;
    if (!sectors.length) {
        return {
            ...layout,
            seats: layout.seats.map((e) => ({ ...e, sectorId: undefined, sectorName: undefined })),
        };
    }
    const sectorsByPriority = [...sectors];
    const nextSeats = layout.seats.map((e) => {
        const cx = e.x;
        const cy = e.y;
        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 };
    });
    return { ...layout, seats: nextSeats };
};

type HallEditorUndoSnapshot = {
    layout: HallLayoutBuckets;
    belts: HallBeltType[];
};

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




/** Геометрия и визуальные поля элемента в JSON (без логики места / вместимости). */
export type HallJsonLayoutFields = {
    id: string;
    x: number;
    y: number;
    width: number;
    height: number;
    /** Для `seat`: центр круга `(x,y)` и радиус; `width`/`height` в данных — 0. */
    radius?: number;
    rotation: number;
    label: string;
};


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

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

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

/** Значения по умолчанию для мест: явные `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 SEAT_DEFAULT_RADIUS = HALL_SEAT_SIZE / 2;

function hallSeatRadius(el: HallElementType): number {
    if (el.type !== 'seat') {
        return 0;
    }
    const r = el.radius;
    if (typeof r === 'number' && Number.isFinite(r) && r > 0) {
        return r;
    }
    return SEAT_DEFAULT_RADIUS;
}

/** Bounding box для hit-test и группового выделения (место — круг вокруг центра `x,y`). */
function hallElementBounds(el: HallElementType): { x: number; y: number; width: number; height: number } {
    if (el.type === 'seat') {
        const r = hallSeatRadius(el);
        return { x: el.x - r, y: el.y - r, width: 2 * r, height: 2 * r };
    }
    return { x: el.x, y: el.y, width: el.width, height: el.height };
}

function circleIntersectsRect(
    cx: number,
    cy: number,
    r: number,
    rect: { x: number; y: number; width: number; height: number },
): boolean {
    const closestX = Math.max(rect.x, Math.min(cx, rect.x + rect.width));
    const closestY = Math.max(rect.y, Math.min(cy, rect.y + rect.height));
    const dx = cx - closestX;
    const dy = cy - closestY;
    return dx * dx + dy * dy <= r * r;
}

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

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

    const bySeatSig = new Map<string, string[]>();
    b.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 = [...b.sectors, ...b.zones];
    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 collectSeatIdsWithoutBeltInLayout = (seats: HallElementType[]): string[] => (
    seats.filter((e) => !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 collectZoneIdsWithoutCapacity = (zones: HallElementType[]): string[] => (
    zones.filter((e) => !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 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 isSeat = type === 'seat';
    const el: HallElementType = {
        id: o.id,
        type,
        x: numField(o.x, 0),
        y: numField(o.y, 0),
        width: isSeat ? 0 : numField(o.width, 1),
        height: isSeat ? 0 : numField(o.height, 1),
        rotation: isSeat ? 0 : 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') {
        el.radius = numField(o.radius, SEAT_DEFAULT_RADIUS);
        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));
};

type LegacyHallExport = HallType & { elements?: unknown };

const layoutFromHallPayload = (h: HallType | null | undefined): HallLayoutBuckets => {
    if (!h || typeof h !== 'object') {
        return emptyHallLayout();
    }
    if (hallSplitCount(h) > 0) {
        return reconcileSeatSectorsLayout(apiHallToEditorBuckets(h));
    }
    const legacy = h as LegacyHallExport;
    if (Array.isArray(legacy.elements) && legacy.elements.length) {
        return reconcileSeatSectorsLayout(splitHallElements(normalizeHallElementsFromJson(legacy.elements)));
    }
    return emptyHallLayout();
};

const editorSeatToApi = (e: HallElementType): HallSeatType => {
    const out: HallSeatType = {
        id: e.id,
        x: e.x,
        y: e.y,
    };
    if (e.radius != null) {
        out.radius = e.radius;
    }
    if (e.seatNumber != null) {
        out.seatNumber = e.seatNumber;
    }
    if (e.seatRow != null) {
        out.seatRow = e.seatRow;
    }
    if (e.beltId != null) {
        out.beltId = e.beltId;
    }
    if (e.sectorId != null) {
        out.sectorId = e.sectorId;
    }
    if (e.sectorName != null) {
        out.sectorName = e.sectorName;
    }
    if (e.rotation != null) {
        out.rotation = e.rotation;
    }
    return out;
};

const editorTableToApi = (e: HallElementType): HallTableType => {
    const out: HallTableType = {
        id: e.id,
        x: e.x,
        y: e.y,
        width: e.width,
        height: e.height,
        rotation: e.rotation,
    };
    if (e.label) {
        out.label = e.label;
    }
    return out;
};

const editorStageToApi = (e: HallElementType): HallStageType => ({
    id: e.id,
    x: e.x,
    y: e.y,
    width: e.width,
    height: e.height,
    rotation: e.rotation,
    label: (e.label && e.label.trim()) ? e.label : 'Сцена',
});

const editorWallToApi = (e: HallElementType): HallWallType => ({
    id: e.id,
    x: e.x,
    y: e.y,
    width: e.width,
    height: e.height,
    rotation: e.rotation,
});

const editorZoneToApi = (e: HallElementType): HallZoneType => ({
    id: e.id,
    x: e.x,
    y: e.y,
    width: e.width,
    height: e.height,
    rotation: e.rotation,
    capacity: e.capacity,
    label: e.label,
});

const editorSectorToApi = (e: HallElementType): HallSectorType => ({
    id: e.id,
    x: e.x,
    y: e.y,
    width: e.width,
    height: e.height,
    rotation: e.rotation,
    label: e.label,
});

const buildHallTypeFromEditorLayout = (params: { belts: HallBeltType[]; layout: HallLayoutBuckets; version: number }): HallType => {
    const { belts, layout, version } = params;
    return {
        version,
        belts,
        seats: layout.seats.map(editorSeatToApi),
        tables: layout.tables.map(editorTableToApi),
        stages: layout.stages.map(editorStageToApi),
        walls: layout.walls.map(editorWallToApi),
        zones: layout.zones.map(editorZoneToApi),
        sectors: layout.sectors.map(editorSectorToApi),
    };
};

const createElement = (type: TypeSTWZEnum, x: number, y: number): HallElementType => {
    const base = PALETTE[type];
    const isSeat = type === 'seat';
    return {
        id: makeId(),
        type,
        x,
        y,
        width: isSeat ? 0 : base.width,
        height: isSeat ? 0 : base.height,
        ...(isSeat ? { radius: base.width / 2 } : {}),
        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 [layout, setLayout] = useState<HallLayoutBuckets>(emptyHallLayout);
    const [selectedIds, setSelectedIds] = useState<string[]>([]);
    const [toolForClickCreate, setToolForClickCreate] = useState<TypeSTWZEnum | null>(null);
    const [zoom, setZoom] = useState(1);
    const [rectPlacementDraft, setRectPlacementDraft] = useState<RectPlacementDraft | null>(null);
    const [selectionRect, setSelectionRect] = useState<SelectionRect | null>(null);
    const [seatNumberingOpen, setSeatNumberingOpen] = useState(false);
    const [seatNumberStart, setSeatNumberStart] = useState('1');
    const [seatNumberFillDirection, setSeatNumberFillDirection] = useState<HallSeatFillDirection>(
        HALL_SEAT_FILL_DIRECTION_DEFAULT_SEAT_NUMBERS,
    );
    const [seatRowsOpen, setSeatRowsOpen] = useState(false);
    const [seatRowStart, setSeatRowStart] = useState('1');
    const [seatRowFillDirection, setSeatRowFillDirection] = useState<HallSeatFillDirection>(
        HALL_SEAT_FILL_DIRECTION_DEFAULT_ROWS,
    );
    const [belts, setBelts] = useState<HallBeltType[]>([]);
    const layoutRef = useRef(layout);
    layoutRef.current = layout;
    const beltsRef = useRef(belts);
    beltsRef.current = belts;

    const patchLayout = useCallback((
        fn: (b: HallLayoutBuckets) => HallLayoutBuckets,
        reconcileAfter = true,
    ) => {
        setLayout((prev) => {
            let next = fn(prev);
            if (reconcileAfter) {
                next = reconcileSeatSectorsLayout(next);
            }
            return next;
        });
    }, []);

    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,
                }
            }) ?? []);
            setLayout(layoutFromHallPayload(params.initValue ?? null));
            setSelectedIds([]);
            hallUndoRef.current = [];
        },
        close: () => {
            setOpen(false);
        }
    }));

    const handleSave = () => {



        if (!runHallCheck()) {
            return;
        }

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

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

        const radii = seats.map((s) => hallSeatRadius(s)).filter((n) => Number.isFinite(n) && n > 0);
        const medR = median(radii) ?? SEAT_DEFAULT_RADIUS;

        const yTol = medR;
        const xTol = medR;

        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 pitch = b.x - a.x;
                const gap = pitch - hallSeatRadius(a) - hallSeatRadius(b);
                if (Number.isFinite(gap) && gap >= 0 && gap < medR * 6) {
                    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 pitch = b.y - a.y;
                const gap = pitch - hallSeatRadius(a) - hallSeatRadius(b);
                if (Number.isFinite(gap) && gap >= 0 && gap < medR * 6) {
                    gapYs.push(gap);
                }
            }
        });

        return {
            radius: medR,
            gapX: median(gapXs) ?? HALL_SEAT_GAP,
            gapY: median(gapYs) ?? HALL_SEAT_GAP,
        };
    }, [layout.seats]);

    const seatDraftStats = useMemo(() => {
        if (!rectPlacementDraft || rectPlacementDraft.mode !== 'seats') {
            return null;
        }
        const d = rectPlacementDraft;
        const width = Math.abs(d.currentX - d.startX);
        const height = Math.abs(d.currentY - d.startY);
        const r = seatDefaults.radius;
        const pitchX = 2 * r + seatDefaults.gapX;
        const pitchY = 2 * r + seatDefaults.gapY;
        const cols = Math.max(1, Math.floor((width - 2 * r + seatDefaults.gapX) / pitchX) + 1);
        const rows = Math.max(1, Math.floor((height - 2 * r + seatDefaults.gapY) / pitchY) + 1);
        return {
            cols,
            rows,
            total: cols * rows,
            x: Math.min(d.startX, d.currentX),
            y: Math.min(d.startY, d.currentY),
            width,
            height,
        };
    }, [rectPlacementDraft, seatDefaults]);
    const blockPlacementPreview = useMemo(() => {
        if (!rectPlacementDraft || rectPlacementDraft.mode !== 'block') {
            return null;
        }
        const d = rectPlacementDraft;
        const left = Math.min(d.startX, d.currentX);
        const top = Math.min(d.startY, d.currentY);
        let width = Math.abs(d.currentX - d.startX);
        let height = Math.abs(d.currentY - d.startY);
        const base = PALETTE[d.blockType];
        if (width < 10) {
            width = base.width;
        }
        if (height < 10) {
            height = base.height;
        }
        return { x: left, y: top, width, height, blockType: d.blockType };
    }, [rectPlacementDraft]);
    const seatDraftPreviewSeats = useMemo(() => {
        if (!seatDraftStats) {
            return [];
        }
        const r = seatDefaults.radius;
        const pitchX = 2 * r + seatDefaults.gapX;
        const pitchY = 2 * r + seatDefaults.gapY;
        const left = seatDraftStats.x;
        const top = seatDraftStats.y;
        const items: Array<{ x: number; y: number; radius: number }> = [];
        for (let row = 0; row < seatDraftStats.rows; row++) {
            for (let col = 0; col < seatDraftStats.cols; col++) {
                items.push({
                    x: left + r + col * pitchX,
                    y: top + r + row * pitchY,
                    radius: r,
                });
            }
        }
        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(
        () => layout.seats.filter((item) => selectedIds.includes(item.id)),
        [layout.seats, selectedIds]
    );
    const selectedSingleElement = useMemo(() => {
        if (selectedIds.length !== 1) {
            return null;
        }
        return findInLayout(layout, selectedIds[0]!) ?? null;
    }, [layout, selectedIds]);
    const showHallEditStage = selectedSingleElement?.type === 'stage';
    const showHallEditSector = selectedSingleElement?.type === 'sector' || selectedSingleElement?.type === 'zone';
    const showHallSeatActions = selectedSeats.length > 0;
    const selectedElements = useMemo(
        () => selectedIds.map((id) => findInLayout(layout, id)).filter((x): x is HallElementType => x != null),
        [layout, 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) => {
            const b = hallElementBounds(el);
            minX = Math.min(minX, b.x);
            minY = Math.min(minY, b.y);
            maxX = Math.max(maxX, b.x + b.width);
            maxY = Math.max(maxY, b.y + b.height);
        });
        if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(maxX) || !Number.isFinite(maxY)) {
            return null;
        }
        return {
            x: minX,
            y: minY,
            width: Math.max(1, maxX - minX),
            height: Math.max(1, maxY - minY),
        };
    }, [selectedElements]);

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

    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, layout]);

    // Auto-remove belts that have no seats.
    useEffect(() => {
        const usedBeltIds = new Set(
            layout.seats
                .filter((e) => 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;
        });
    }, [layout.seats]);

    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 violatesNoSeatsSectorRuleLayout = (next: HallLayoutBuckets) => {
        const seats = next.seats;
        const noSeatSectors = next.zones;
        if (!seats.length || !noSeatSectors.length) {
            return false;
        }
        for (const s of seats) {
            const r = hallSeatRadius(s);
            for (const z of noSeatSectors) {
                const zoneRect = { x: z.x, y: z.y, width: z.width, height: z.height };
                if (circleIntersectsRect(s.x, s.y, r, zoneRect)) {
                    return true;
                }
            }
        }
        return false;
    };

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

    const runHallCheck = (): boolean => {
        const dupIds = collectHallDuplicateIdsFromLayout(layout);
        const noBeltIds = collectSeatIdsWithoutBeltInLayout(layout.seats);
        const noCapacityIds = collectZoneIdsWithoutCapacity(layout.zones);
        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({
            layout: structuredClone(layoutRef.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);
        setLayout(structuredClone(snap.layout));
        setBelts(structuredClone(snap.belts));
        setSelectedIds((ids) => ids.filter((id) => mergeHallLayoutBuckets(snap.layout).some((e) => e.id === id)));
    }, []);

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

    const deleteSelected = useCallback(() => {
        if (!selectedIds.length) {
            return;
        }
        const ids = new Set(selectedIds);
        pushHallUndoSnapshot();
        patchLayout((b) => filterIdsFromLayout(b, ids), true);
        setSelectedIds([]);
    }, [selectedIds, pushHallUndoSnapshot, patchLayout]);

    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 (type === 'seat') {
            newElement.radius = seatDefaults.radius;
        }
        pushHallUndoSnapshot();
        patchLayout((b) => appendToLayoutByType(b, newElement), true);
        setSelectedIds([newElement.id]);
    };

    const createSeatsInRect = (draft: Extract<RectPlacementDraft, { mode: 'seats' }>) => {
        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 r = seatDefaults.radius;
        const pitchX = 2 * r + seatDefaults.gapX;
        const pitchY = 2 * r + seatDefaults.gapY;
        const cols = Math.max(1, Math.floor((width - 2 * r + seatDefaults.gapX) / pitchX) + 1);
        const rows = Math.max(1, Math.floor((height - 2 * r + seatDefaults.gapY) / pitchY) + 1);

        const seats: HallElementType[] = [];
        for (let row = 0; row < rows; row++) {
            for (let col = 0; col < cols; col++) {
                const seat = createElement(
                    'seat',
                    left + r + col * pitchX,
                    top + r + row * pitchY,
                );
                seat.radius = r;
                seats.push(seat);
            }
        }

        if (!seats.length) {
            return;
        }
        pushHallUndoSnapshot();
        patchLayout((b) => appendManyToLayout(b, seats), true);
        setSelectedIds(seats.map((seat) => seat.id));
    };

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

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

        const tol = seatDefaults.radius;
        const lines = hallSeatsGroupedLinesOrdered(selectedSeats, seatNumberFillDirection, tol, 'seat_numbers');
        const seatNumbers = new Map<string, string>();
        lines.forEach((line) => {
            line.forEach((seat, idxInLine) => {
                seatNumbers.set(seat.id, hallSeatLabelAt(parsed, idxInLine));
            });
        });

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

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

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

    const openSeatSettings = (seatId: string) => {
        const seat = layout.seats.find((s) => s.id === seatId);
        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();
        patchLayout((b) => mapAllItemsInLayout(b, (item) => {
            if (item.id !== seatSettingsSeatId || item.type !== 'seat') {
                return item;
            }
            return {
                ...item,
                beltId: belt ? belt.id : undefined,
                seatNumber: nextSeatNumber,
                seatRow: nextSeatRow,
            };
        }), true);
        setSeatSettingsOpen(false);
    };

    const openSectorSettings = (sectorId: string) => {
        const sector = layout.sectors.find((s) => s.id === sectorId)
            ?? layout.zones.find((z) => z.id === sectorId);
        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();
        patchLayout((b) => mapAllItemsInLayout(b, (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;
        }), true);
        setSectorSettingsOpen(false);
    };

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

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

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

    const getHallExportJsonText = () => JSON.stringify(
        buildHallTypeFromEditorLayout({ belts, layout, version: 4 }),
        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 ?? []);
            setLayout(layoutFromHallPayload(payload));
            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]);
        patchLayout((b) => ({
            ...b,
            seats: b.seats.map((item) => (
                selectedSeatIds.has(item.id) ? { ...item, beltId } : item
            )),
        }), false);
        setBeltDialogOpen(false);
        setBeltName('');
    };
    const selectBeltSeats = (beltId: string) => {
        const ids = layout.seats
            .filter((item) => item.beltId === beltId)
            .map((item) => item.id);
        setSelectedIds(ids);
    };
    const deleteBelt = (beltId: string) => {
        pushHallUndoSnapshot();
        setBelts((prev) => prev.filter((belt) => belt.id !== beltId));
        patchLayout((b) => ({
            ...b,
            seats: b.seats.map((item) => (
                item.beltId === beltId ? { ...item, beltId: undefined } : item
            )),
        }), false);
    };
    const addSelectedToBelt = (belt: HallBeltType) => {
        if (!selectedSeats.length) {
            return;
        }
        const selectedSeatIds = new Set(selectedSeats.map((seat) => seat.id));
        pushHallUndoSnapshot();
        patchLayout((b) => ({
            ...b,
            seats: b.seats.map((item) => (
                selectedSeatIds.has(item.id) ? { ...item, beltId: belt.id } : item
            )),
        }), false);
    };
    const removeSelectedFromBelt = (beltId: string) => {
        if (!selectedSeats.length) {
            return;
        }
        const selectedSeatIds = new Set(selectedSeats.map((seat) => seat.id));
        pushHallUndoSnapshot();
        patchLayout((b) => ({
            ...b,
            seats: b.seats.map((item) => (
                selectedSeatIds.has(item.id) && item.beltId === beltId
                    ? { ...item, beltId: undefined }
                    : item
            )),
        }), false);
    };

    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 justify-between">

                        <div className='flex items-center gap-2'>
                            <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={!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>
                            <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>




                        </div>



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






                            {showHallEditStage && (
                                <Tooltip>
                                    <TooltipTrigger asChild>
                                        <Button
                                            size="sm"
                                            type="button"
                                            variant="outline"
                                            aria-label={Lang.key('hall_edit_selected_stage')}
                                            onClick={() => openStageSettings(selectedIds[0])}
                                        >
                                            <Edit2Icon />
                                        </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])}
                                        >
                                            <Edit2Icon />
                                        </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={() => {
                                                    setSeatRowFillDirection(HALL_SEAT_FILL_DIRECTION_DEFAULT_ROWS);
                                                    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={() => {
                                                    setSeatNumberFillDirection(HALL_SEAT_FILL_DIRECTION_DEFAULT_SEAT_NUMBERS);
                                                    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>
                            )}





                        </div>


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

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

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

                            <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>
                            <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>
                        </div>

                        <div></div>


                    </GridRow>
                    <GridRow>

                        <div
                            ref={wrapperRef}
                            className="flex-1 border rounded-md overflow-auto bg-white flex"
                            onDragOver={(e) => e.preventDefault()}
                            onDrop={(e) => {
                                e.preventDefault();
                                const type = e.dataTransfer.getData('hall-element-type') as 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') {
                                                    setRectPlacementDraft({
                                                        mode: 'seats',
                                                        startX: pos.x,
                                                        startY: pos.y,
                                                        currentX: pos.x,
                                                        currentY: pos.y,
                                                    });
                                                } else if (
                                                    toolForClickCreate === 'stage'
                                                    || toolForClickCreate === 'table'
                                                    || toolForClickCreate === 'zone'
                                                    || toolForClickCreate === 'wall'
                                                ) {
                                                    setRectPlacementDraft({
                                                        mode: 'block',
                                                        blockType: toolForClickCreate,
                                                        startX: pos.x,
                                                        startY: pos.y,
                                                        currentX: pos.x,
                                                        currentY: pos.y,
                                                    });
                                                } else {
                                                    addElement(toolForClickCreate, pos.x, pos.y);
                                                    setToolForClickCreate(null);
                                                }
                                            } else {
                                                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();
                                        setRectPlacementDraft((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 (rectPlacementDraft) {
                                            if (rectPlacementDraft.mode === 'seats') {
                                                createSeatsInRect(rectPlacementDraft);
                                            } else {
                                                createBlockInRect(rectPlacementDraft);
                                            }
                                            setRectPlacementDraft(null);
                                            setToolForClickCreate(null);
                                            return;
                                        }
                                        if (!selectionRect || !normalizedSelectionRect) {
                                            return;
                                        }
                                        const tooSmall = normalizedSelectionRect.width < 4 || normalizedSelectionRect.height < 4;
                                        if (tooSmall) {
                                            if (!selectionRect.appendToSelection) {
                                                setSelectedIds([]);
                                            }
                                            setSelectionRect(null);
                                            return;
                                        }
                                        const selectedInRect = [
                                            ...layout.sectors,
                                            ...layout.zones,
                                            ...layout.stages,
                                            ...layout.tables,
                                            ...layout.walls,
                                            ...layout.seats,
                                        ].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);
                                    }}
                                >
                                    <Layer listening={false}>
                                        {hallEditorGridLines}
                                    </Layer>
                                    <Layer>
                                        {/* sectors always behind other elements */}
                                        {[...layout.sectors, ...layout.zones].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 = mergeHallLayoutBuckets(layoutRef.current);
                                                            const proposed = prevEls.map((old) => {
                                                                const nodeRef = nodeRefs.current[old.id];
                                                                if (!selectedIds.includes(old.id) || !nodeRef) {
                                                                    return old;
                                                                }
                                                                return { ...old, x: nodeRef.x(), y: nodeRef.y() };
                                                            });
                                                            if (violatesNoSeatsSectorRule(proposed)) {
                                                                selectedIds.forEach((id) => {
                                                                    const nodeRef = nodeRefs.current[id];
                                                                    const start = startPositions[id];
                                                                    if (nodeRef && start) {
                                                                        nodeRef.x(start.x);
                                                                        nodeRef.y(start.y);
                                                                    }
                                                                });
                                                                e.target.getLayer()?.batchDraw();
                                                            } else {
                                                                pushHallUndoSnapshot();
                                                                setLayout(reconcileSeatSectorsLayout(splitHallElements(proposed)));
                                                            }
                                                            activeDragRef.current = null;
                                                            dragGroupStartRef.current = {};
                                                            dragGroupSelectedIdsRef.current = [];
                                                            dragGroupPointerStartRef.current = null;
                                                            return;
                                                        }
                                                        const start = singleDragStartRef.current?.id === item.id ? singleDragStartRef.current : null;
                                                        const prevElsSingle = mergeHallLayoutBuckets(layoutRef.current);
                                                        const proposedSingle = prevElsSingle.map((el) => (
                                                            el.id === item.id ? { ...el, x: e.target.x(), y: e.target.y() } : el
                                                        ));
                                                        if (violatesNoSeatsSectorRule(proposedSingle)) {
                                                            if (start) {
                                                                e.target.x(start.x);
                                                                e.target.y(start.y);
                                                                e.target.getLayer()?.batchDraw();
                                                            }
                                                            return;
                                                        }
                                                        pushHallUndoSnapshot();
                                                        setLayout(reconcileSeatSectorsLayout(splitHallElements(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 = mergeHallLayoutBuckets(layoutRef.current).map((el) => (
                                                            el.id === item.id ? {
                                                                ...el,
                                                                x: node.x(),
                                                                y: node.y(),
                                                                rotation: node.rotation(),
                                                                width: nextWidth,
                                                                height: nextHeight,
                                                            } : el
                                                        ));
                                                        if (violatesNoSeatsSectorRule(proposed)) {
                                                            // revert via state (safe) by snapping node back to element values
                                                            const current = findInLayout(layout, item.id);
                                                            if (current) {
                                                                node.x(current.x);
                                                                node.y(current.y);
                                                                node.rotation(current.rotation);
                                                                node.width(current.width);
                                                                node.height(current.height);
                                                                node.getLayer()?.batchDraw();
                                                            }
                                                            return;
                                                        }
                                                        updateElement(item.id, (old) => ({ ...old, x: node.x(), y: node.y(), rotation: node.rotation(), width: nextWidth, height: nextHeight }));
                                                    }}
                                                />
                                                <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>
                                        {[...layout.stages, ...layout.tables, ...layout.walls, ...layout.seats].map((item) => {
                                            const seatLabel = item.type === 'seat'
                                                ? formatHallSeatRowSlashNumber(item.seatRow, item.seatNumber)
                                                : '';
                                            const seatR = item.type === 'seat' ? hallSeatRadius(item) : 0;
                                            const shapeCommon = {
                                                ref: (node: Konva.Shape | null) => {
                                                    if (node) {
                                                        nodeRefs.current[item.id] = node;
                                                    }
                                                },
                                                fill: resolveHallElementFill(item, belts),
                                                draggable: true as const,
                                                rotation: item.type === 'seat' ? 0 : item.rotation,
                                                onClick: (e: Konva.KonvaEventObject<MouseEvent>) => {
                                                    const multi = e.evt.shiftKey || e.evt.metaKey || e.evt.ctrlKey;
                                                    if (multi) {
                                                        setSelectedIds((prev) => (
                                                            prev.includes(item.id)
                                                                ? prev.filter((id) => id !== item.id)
                                                                : [...prev, item.id]
                                                        ));
                                                        return;
                                                    }
                                                    setSelectedIds([item.id]);
                                                },
                                                onDblClick: (e: Konva.KonvaEventObject<MouseEvent>) => {
                                                    e.cancelBubble = true;
                                                    if (item.type === 'seat') {
                                                        openSeatSettings(item.id);
                                                    } else if (item.type === 'stage') {
                                                        openStageSettings(item.id);
                                                    }
                                                },
                                                onTap: () => setSelectedIds([item.id]),
                                                onDragStart: (e: Konva.KonvaEventObject<DragEvent>) => {
                                                    const node = e.target;
                                                    singleDragStartRef.current = { id: item.id, x: node.x(), y: node.y() };
                                                    if (!selectedIds.includes(item.id)) {
                                                        setSelectedIds([item.id]);
                                                        return;
                                                    }
                                                    if (selectedIds.length < 2) {
                                                        return;
                                                    }
                                                    const startPositions: Record<string, { x: number; y: number }> = {};
                                                    selectedIds.forEach((id) => {
                                                        const nodeRef = nodeRefs.current[id];
                                                        if (nodeRef) {
                                                            startPositions[id] = { x: nodeRef.x(), y: nodeRef.y() };
                                                        }
                                                    });
                                                    const activeStart = startPositions[item.id] ?? { x: node.x(), y: node.y() };
                                                    startPositions[item.id] = activeStart;
                                                    dragGroupStartRef.current = startPositions;
                                                    dragGroupSelectedIdsRef.current = [...selectedIds];
                                                    const pointerPos = getPointerPositionInStage();
                                                    dragGroupPointerStartRef.current = pointerPos ? { x: pointerPos.x, y: pointerPos.y } : null;
                                                    activeDragRef.current = {
                                                        id: item.id,
                                                        startX: activeStart.x,
                                                        startY: activeStart.y,
                                                    };
                                                },
                                                onDragMove: (e: Konva.KonvaEventObject<DragEvent>) => {
                                                    if (!activeDragRef.current || selectedIds.length < 2) {
                                                        return;
                                                    }
                                                    const activeNode = e.target;
                                                    const pointerPos = getPointerPositionInStage();
                                                    const pointerStart = dragGroupPointerStartRef.current;
                                                    if (!pointerPos || !pointerStart) {
                                                        return;
                                                    }
                                                    const dx = pointerPos.x - pointerStart.x;
                                                    const dy = pointerPos.y - pointerStart.y;

                                                    const activeId = activeDragRef.current.id;
                                                    dragGroupSelectedIdsRef.current.forEach((id) => {
                                                        if (id === activeId) {
                                                            return;
                                                        }
                                                        const nodeRef = nodeRefs.current[id];
                                                        const startPos = dragGroupStartRef.current[id];
                                                        if (nodeRef && startPos) {
                                                            nodeRef.x(startPos.x + dx);
                                                            nodeRef.y(startPos.y + dy);
                                                        }
                                                    });
                                                    activeNode.getLayer()?.batchDraw();
                                                },
                                                onDragEnd: (e: Konva.KonvaEventObject<DragEvent>) => {
                                                    if (activeDragRef.current && selectedIds.length > 1) {
                                                        const startPositions = dragGroupStartRef.current;
                                                        const prevEls = mergeHallLayoutBuckets(layoutRef.current);
                                                        const proposed = prevEls.map((old) => {
                                                            const nodeRef = nodeRefs.current[old.id];
                                                            if (!selectedIds.includes(old.id) || !nodeRef) {
                                                                return old;
                                                            }
                                                            return { ...old, x: nodeRef.x(), y: nodeRef.y() };
                                                        });
                                                        if (violatesNoSeatsSectorRule(proposed)) {
                                                            selectedIds.forEach((id) => {
                                                                const nodeRef = nodeRefs.current[id];
                                                                const start = startPositions[id];
                                                                if (nodeRef && start) {
                                                                    nodeRef.x(start.x);
                                                                    nodeRef.y(start.y);
                                                                }
                                                            });
                                                            e.target.getLayer()?.batchDraw();
                                                        } else {
                                                            pushHallUndoSnapshot();
                                                            setLayout(reconcileSeatSectorsLayout(splitHallElements(proposed)));
                                                        }
                                                        activeDragRef.current = null;
                                                        dragGroupStartRef.current = {};
                                                        dragGroupSelectedIdsRef.current = [];
                                                        dragGroupPointerStartRef.current = null;
                                                        return;
                                                    }
                                                    const start = singleDragStartRef.current?.id === item.id ? singleDragStartRef.current : null;
                                                    const prevElsSingle = mergeHallLayoutBuckets(layoutRef.current);
                                                    const proposedSingle = prevElsSingle.map((el) => (
                                                        el.id === item.id ? { ...el, x: e.target.x(), y: e.target.y() } : el
                                                    ));
                                                    if (violatesNoSeatsSectorRule(proposedSingle)) {
                                                        if (start) {
                                                            e.target.x(start.x);
                                                            e.target.y(start.y);
                                                            e.target.getLayer()?.batchDraw();
                                                        }
                                                        return;
                                                    }
                                                    pushHallUndoSnapshot();
                                                    setLayout(reconcileSeatSectorsLayout(splitHallElements(proposedSingle)));
                                                },
                                                onTransformEnd: (e: Konva.KonvaEventObject<Event>) => {
                                                    const node = e.target;
                                                    if (item.type === 'seat') {
                                                        const circ = node as Konva.Circle;
                                                        const sx = circ.scaleX();
                                                        const sy = circ.scaleY();
                                                        const s = Math.max(sx, sy);
                                                        const nextR = Math.max(7, circ.radius() * s);
                                                        circ.scaleX(1);
                                                        circ.scaleY(1);
                                                        circ.radius(nextR);
                                                        updateElement(item.id, (old) => ({
                                                            ...old,
                                                            x: circ.x(),
                                                            y: circ.y(),
                                                            rotation: 0,
                                                            radius: nextR,
                                                        }));
                                                        return;
                                                    }
                                                    const scaleX = node.scaleX();
                                                    const scaleY = node.scaleY();
                                                    const nextWidth = Math.max(14, node.width() * scaleX);
                                                    const nextHeight = Math.max(14, node.height() * scaleY);
                                                    node.scaleX(1);
                                                    node.scaleY(1);
                                                    updateElement(item.id, (old) => ({
                                                        ...old,
                                                        x: node.x(),
                                                        y: node.y(),
                                                        rotation: node.rotation(),
                                                        width: nextWidth,
                                                        height: nextHeight,
                                                    }));
                                                },
                                            };
                                            return (
                                                <React.Fragment key={item.id}>
                                                    {item.type === 'seat' ? (
                                                        <Circle
                                                            {...shapeCommon}
                                                            x={item.x}
                                                            y={item.y}
                                                            radius={seatR}
                                                        />
                                                    ) : (
                                                        <Rect
                                                            {...shapeCommon}
                                                            x={item.x}
                                                            y={item.y}
                                                            width={item.width}
                                                            height={item.height}
                                                            cornerRadius={getHallBlockCornerRadius(item.type as TypeSTWZEnum)}
                                                        />
                                                    )}
                                                    {item.type === 'seat' && (
                                                        <Text
                                                            x={item.x - seatR}
                                                            y={item.y - seatR}
                                                            width={2 * seatR}
                                                            height={2 * seatR}
                                                            align="center"
                                                            verticalAlign="middle"
                                                            wrap="none"
                                                            text={seatLabel}
                                                            fontSize={hallSeatLabelFontSizeForRect(
                                                                seatLabel,
                                                                2 * seatR,
                                                                2 * seatR,
                                                            )}
                                                            fontStyle="bold"
                                                            fontFamily="Arial,sans-serif"
                                                            fill={hallSeatLabelFill(item, belts)}
                                                            listening={false}
                                                        />
                                                    )}
                                                    {item.type !== 'seat' && item.type !== 'stage' && (
                                                        <Text
                                                            x={item.x + 6}
                                                            y={item.y + 6}
                                                            text={item.type === 'wall' ? '' : item.label}
                                                            fontSize={12}
                                                            fill={getHallBlockOnShapeTextFill(item.type as TypeSTWZEnum)}
                                                            listening={false}
                                                        />
                                                    )}
                                                    <Text
                                                        x={item.x}
                                                        y={item.y + item.height / 2 - 8}
                                                        width={item.width}
                                                        align="center"
                                                        text={item.type === 'stage' ? item.label : ''}
                                                        fontSize={16}
                                                        fill={getHallBlockOnShapeTextFill('stage')}
                                                        listening={false}
                                                        visible={item.type === 'stage'}
                                                    />
                                                </React.Fragment>
                                            );
                                        })}
                                        {seatDraftStats && (
                                            <>
                                                <Rect
                                                    x={seatDraftStats.x}
                                                    y={seatDraftStats.y}
                                                    width={seatDraftStats.width}
                                                    height={seatDraftStats.height}
                                                    fill={HALL_SEAT_DRAFT.rectFill}
                                                    stroke={HALL_SEAT_DRAFT.rectStroke}
                                                    dash={[6, 6]}
                                                    listening={false}
                                                />
                                                {seatDraftPreviewSeats.map((previewSeat, idx) => (
                                                    <Circle
                                                        key={`seat-preview-${idx}`}
                                                        x={previewSeat.x}
                                                        y={previewSeat.y}
                                                        radius={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={`${Lang.key('hall_seat_draft_rows')}: ${seatDraftStats.rows}  ${Lang.key('hall_seat_draft_seats_in_line')}: ${seatDraftStats.cols}  ${Lang.key('hall_seat_draft_total')}: ${seatDraftStats.total}`}
                                                    fontSize={12}
                                                    fill={HALL_SEAT_DRAFT.statsText}
                                                    listening={false}
                                                />
                                            </>
                                        )}
                                        {blockPlacementPreview && (
                                            <>
                                                <Rect
                                                    x={blockPlacementPreview.x}
                                                    y={blockPlacementPreview.y}
                                                    width={blockPlacementPreview.width}
                                                    height={blockPlacementPreview.height}
                                                    fill={HALL_SEAT_DRAFT.rectFill}
                                                    stroke={HALL_SEAT_DRAFT.rectStroke}
                                                    dash={[6, 6]}
                                                    listening={false}
                                                />
                                                <Text
                                                    x={blockPlacementPreview.x + 8}
                                                    y={blockPlacementPreview.y + 8}
                                                    text={`${Math.round(blockPlacementPreview.width)} × ${Math.round(blockPlacementPreview.height)}`}
                                                    fontSize={12}
                                                    fill={HALL_SEAT_DRAFT.statsText}
                                                    listening={false}
                                                />
                                            </>
                                        )}
                                        {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 && !rectPlacementDraft && !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 = mergeHallLayoutBuckets(layoutRef.current);
                                                    const proposed = prevEls.map((old) => {
                                                        const nodeRef = nodeRefs.current[old.id];
                                                        if (!selectedIds.includes(old.id) || !nodeRef) {
                                                            return old;
                                                        }
                                                        return { ...old, x: nodeRef.x(), y: nodeRef.y() };
                                                    });
                                                    if (violatesNoSeatsSectorRule(proposed)) {
                                                        selectedIds.forEach((id) => {
                                                            const nodeRef = nodeRefs.current[id];
                                                            const start = startPositions[id];
                                                            if (nodeRef && start) {
                                                                nodeRef.x(start.x);
                                                                nodeRef.y(start.y);
                                                            }
                                                        });
                                                        stageRef.current?.getLayers?.()?.forEach((l: any) => l.batchDraw());
                                                    } else {
                                                        pushHallUndoSnapshot();
                                                        setLayout(reconcileSeatSectorsLayout(splitHallElements(proposed)));
                                                    }
                                                    selectionFrameDragStartRef.current = {};
                                                    selectionFramePointerStartRef.current = null;
                                                }}
                                            />
                                        )}
                                        {hallCheckFlash?.ids.map((id) => {
                                            const el = findInLayout(layout, id);
                                            if (!el) {
                                                return null;
                                            }
                                            const o = hallCheckOverlayOpacity(hallCheckPulseMs);
                                            if (el.type === 'seat') {
                                                const r = hallSeatRadius(el);
                                                return (
                                                    <Circle
                                                        key={`hall-check-${id}`}
                                                        x={el.x}
                                                        y={el.y}
                                                        radius={r}
                                                        stroke={HALL_DUPLICATE_HIGHLIGHT_STROKE}
                                                        strokeWidth={5}
                                                        fillEnabled={false}
                                                        opacity={o}
                                                        listening={false}
                                                    />
                                                );
                                            }
                                            return (
                                                <Rect
                                                    key={`hall-check-${id}`}
                                                    x={el.x}
                                                    y={el.y}
                                                    width={el.width}
                                                    height={el.height}
                                                    rotation={el.rotation}
                                                    cornerRadius={getHallBlockCornerRadius(el.type as 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}
                fillDirection={seatNumberFillDirection}
                onStartFromChange={setSeatNumberStart}
                onFillDirectionChange={setSeatNumberFillDirection}
                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}
                fillDirection={seatRowFillDirection}
                onStartFromChange={setSeatRowStart}
                onFillDirectionChange={setSeatRowFillDirection}
                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;