import { router } from '@inertiajs/react';
import { Suspense, lazy, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
import AppLayout from '@/layouts/AppLayout';
import { useTranslator } from '@/lib/i18n';

// 3D viewer is heavy (Three.js) — load on demand only.
const FloorPlan3D = lazy(() => import('./floorplan-3d'));

// ── Constants ────────────────────────────────────────────────────────────────

const PPM = 40; // pixels per metre — both dimensions

const ELEMENT_TYPES: { type: ElementType; label: string; defaultW: number; defaultH: number; defaultSeats?: number }[] = [
    { type: 'round_table',      label: 'Round Table',      defaultW: 1.5, defaultH: 1.5, defaultSeats: 8 },
    { type: 'rect_table',       label: 'Rectangular Table', defaultW: 2.4, defaultH: 0.9, defaultSeats: 8 },
    { type: 'sweetheart_table', label: 'Sweetheart Table', defaultW: 1.6, defaultH: 0.8, defaultSeats: 2 },
    { type: 'buffet_table',     label: 'Buffet Table',     defaultW: 3.0, defaultH: 0.8 },
    { type: 'gift_table',       label: 'Gift Table',       defaultW: 1.8, defaultH: 0.6 },
    { type: 'cake_table',       label: 'Cake Table',       defaultW: 1.0, defaultH: 1.0 },
    { type: 'dance_floor',      label: 'Dance Floor',      defaultW: 4.0, defaultH: 4.0 },
    { type: 'dj_booth',         label: 'DJ Booth',         defaultW: 1.5, defaultH: 0.8 },
    { type: 'stage',            label: 'Stage',            defaultW: 4.0, defaultH: 2.0 },
    { type: 'bar',              label: 'Bar',              defaultW: 3.0, defaultH: 0.8 },
    { type: 'lounge',           label: 'Lounge',           defaultW: 2.5, defaultH: 1.5 },
    { type: 'photo_booth',      label: 'Photo Booth',      defaultW: 1.5, defaultH: 1.5 },
    { type: 'altar',            label: 'Altar',            defaultW: 1.5, defaultH: 0.8 },
    { type: 'arch',             label: 'Arch',             defaultW: 2.5, defaultH: 0.4 },
];

const ELEMENT_COLORS: Record<ElementType, { fill: string; stroke: string }> = {
    round_table:      { fill: '#fde2e3', stroke: '#c97b84' },
    rect_table:       { fill: '#fde2e3', stroke: '#c97b84' },
    sweetheart_table: { fill: '#fbd5d8', stroke: '#a25b65' },
    buffet_table:     { fill: '#fde9d3', stroke: '#b78540' },
    gift_table:       { fill: '#f5e9d4', stroke: '#a8884a' },
    cake_table:       { fill: '#ffeef5', stroke: '#cc7a99' },
    dance_floor:      { fill: '#e8e0f5', stroke: '#7a5fb5' },
    dj_booth:         { fill: '#202023', stroke: '#3b3b40' },
    stage:            { fill: '#dde8da', stroke: '#5a8a52' },
    bar:              { fill: '#3a2a2a', stroke: '#1a0e0e' },
    lounge:           { fill: '#f0e5dd', stroke: '#a78060' },
    photo_booth:      { fill: '#f9f5e8', stroke: '#9b8e62' },
    altar:            { fill: '#fff7ec', stroke: '#a8865a' },
    arch:             { fill: '#fae6e8', stroke: '#9d6671' },
};

// ── Types ────────────────────────────────────────────────────────────────────

export type ElementType =
    | 'round_table' | 'rect_table' | 'sweetheart_table' | 'buffet_table'
    | 'gift_table' | 'cake_table' | 'dance_floor' | 'dj_booth' | 'stage'
    | 'bar' | 'lounge' | 'photo_booth' | 'altar' | 'arch';

export interface FpElement {
    id: string;
    type: ElementType;
    x: number;          // metres from left
    y: number;          // metres from top
    width: number;      // metres
    height: number;     // metres
    rotation: number;   // degrees
    label: string;
    seats: number;
}

export interface FloorPlanRecord {
    id: string;
    name: string;
    elements: FpElement[];
    room_width: number;
    room_height: number;
    is_primary: boolean;
}

interface Props {
    wedding: { id: string; partner_a_name: string; partner_b_name: string };
    plans: FloorPlanRecord[];
    seatingTableCount?: number;
}

// Floor-plan element types that count against the "tables created in Seating" cap.
const TABLE_ELEMENT_TYPES = new Set<ElementType>([
    'round_table', 'rect_table', 'sweetheart_table',
]);

// ── Helpers ──────────────────────────────────────────────────────────────────

function makeId(): string {
    return `el_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
}

function clamp(v: number, lo: number, hi: number) {
    return Math.max(lo, Math.min(hi, v));
}

// ── Component ────────────────────────────────────────────────────────────────

export default function FloorPlan({ wedding, plans, seatingTableCount = 0 }: Props) {
    const t = useTranslator();
    const [activeId, setActiveId] = useState<string | null>(plans.find(p => p.is_primary)?.id ?? plans[0]?.id ?? null);
    const active = plans.find(p => p.id === activeId) ?? null;

    const [name, setName] = useState(active?.name ?? '');
    const [roomW, setRoomW] = useState(active?.room_width ?? 20);
    const [roomH, setRoomH] = useState(active?.room_height ?? 15);
    const [elements, setElements] = useState<FpElement[]>(active?.elements ?? []);
    const [selectedId, setSelectedId] = useState<string | null>(null);
    const [zoom, setZoom] = useState(1);
    const [showGrid, setShowGrid] = useState(true);
    const [view, setView] = useState<'2d' | '3d'>('2d');
    const [dirty, setDirty] = useState(false);
    const [saving, setSaving] = useState(false);

    // Reset local state when active plan changes.
    useEffect(() => {
        if (!active) {
            setName(''); setRoomW(20); setRoomH(15); setElements([]); setSelectedId(null); setDirty(false);
            return;
        }
        setName(active.name);
        setRoomW(active.room_width);
        setRoomH(active.room_height);
        setElements(active.elements ?? []);
        setSelectedId(null);
        setDirty(false);
    }, [activeId]); // eslint-disable-line react-hooks/exhaustive-deps

    // #6: The SVG grid <pattern> can paint against a zero-sized viewBox on the
    // first mount — a known Chromium/WebKit quirk where a pattern fill doesn't
    // repaint without a layout pass, which is why it only appeared after the
    // user left the tab and came back (that navigation forced a fresh mount).
    //
    // The v1.0.1 fix bumped a `renderTick` state value that was never read in
    // the JSX, so React produced byte-identical DOM, no node was mutated, and
    // the browser never repainted — the tick did nothing. Instead, remount the
    // <svg> node itself one layout pass after mount / plan switch by changing
    // its `key`. useLayoutEffect runs after the DOM commit but BEFORE the
    // browser paints, so the remount lands with no visible flash and the
    // pattern attaches to the now-measured, correctly-sized viewBox.
    const patternId = useId();
    const [svgEpoch, setSvgEpoch] = useState(0);
    useLayoutEffect(() => {
        setSvgEpoch(e => e + 1);
    }, [activeId]); // eslint-disable-line react-hooks/exhaustive-deps

    // #14: Warn before navigating away or refreshing while there are unsaved
    // floor plan edits. Covers both real browser unload AND Inertia route
    // changes (which don't fire beforeunload).
    useEffect(() => {
        if (!dirty) return;

        const onBeforeUnload = (e: BeforeUnloadEvent) => {
            e.preventDefault();
            e.returnValue = '';
        };
        window.addEventListener('beforeunload', onBeforeUnload);

        const removeInertiaGuard = router.on('before', (event) => {
            if (!confirm(t('floorplan.unsaved_warning'))) {
                event.preventDefault();
            }
        });

        return () => {
            window.removeEventListener('beforeunload', onBeforeUnload);
            removeInertiaGuard();
        };
    }, [dirty]);

    const selected = elements.find(e => e.id === selectedId) ?? null;

    function markDirty(next: FpElement[]) {
        setElements(next);
        setDirty(true);
    }

    // #21: Tables are owned by Seating. Each seating table auto-creates a
    // matching floor-plan element (id prefixed `el_table_`); the palette below
    // only offers décor/features, so couples can't create a parallel set of
    // tables here (which also retires the old #7 "7th table" over-creation).
    const currentTableCount = elements.filter(e => TABLE_ELEMENT_TYPES.has(e.type)).length;
    const isManagedTable = (id: string) => id.startsWith('el_table_');

    function addElement(type: ElementType) {
        const def = ELEMENT_TYPES.find(t => t.type === type)!;
        const el: FpElement = {
            id: makeId(),
            type,
            x: roomW / 2 - def.defaultW / 2,
            y: roomH / 2 - def.defaultH / 2,
            width: def.defaultW,
            height: def.defaultH,
            rotation: 0,
            label: t(`floorplan.el_${type}`),
            seats: def.defaultSeats ?? 0,
        };
        markDirty([...elements, el]);
        setSelectedId(el.id);
    }

    function updateElement(id: string, patch: Partial<FpElement>) {
        markDirty(elements.map(e => e.id === id ? { ...e, ...patch } : e));
    }

    function removeElement(id: string) {
        if (isManagedTable(id)) {
            alert(t('floorplan.managed_table_alert'));
            return;
        }
        markDirty(elements.filter(e => e.id !== id));
        if (selectedId === id) setSelectedId(null);
    }

    function save() {
        if (!active) return;
        setSaving(true);
        router.put(`/weddings/${wedding.id}/floorplan/${active.id}`,
            { name, elements: elements as never, room_width: roomW, room_height: roomH },
            {
                preserveScroll: true,
                onFinish: () => setSaving(false),
                onSuccess: () => setDirty(false),
            });
    }

    function createPlan() {
        const planName = prompt(t('floorplan.prompt_name_label'), t('floorplan.new_plan_default'));
        if (!planName) return;
        router.post(`/weddings/${wedding.id}/floorplan`,
            { name: planName, elements: [], room_width: 20, room_height: 15 },
            { preserveScroll: true });
    }

    function deletePlan() {
        if (!active) return;
        if (!confirm(t('floorplan.delete_confirm', { name: active.name }))) return;
        router.delete(`/weddings/${wedding.id}/floorplan/${active.id}`, {
            preserveScroll: true,
            onSuccess: () => {
                const next = plans.find(p => p.id !== active.id);
                setActiveId(next?.id ?? null);
            },
        });
    }

    function setPrimary() {
        if (!active || active.is_primary) return;
        router.post(`/weddings/${wedding.id}/floorplan/${active.id}/primary`, {}, { preserveScroll: true });
    }

    async function exportPdf() {
        if (!active) return;
        try {
            const { jsPDF } = await import('jspdf');
            const svg = document.getElementById('fp-svg') as SVGSVGElement | null;
            if (!svg) return;
            const xml = new XMLSerializer().serializeToString(svg);
            const blob = new Blob([xml], { type: 'image/svg+xml' });
            const url = URL.createObjectURL(blob);
            const img = new Image();
            await new Promise((resolve, reject) => {
                img.onload = resolve;
                img.onerror = reject;
                img.src = url;
            });
            const canvas = document.createElement('canvas');
            canvas.width = roomW * PPM;
            canvas.height = roomH * PPM;
            const ctx = canvas.getContext('2d')!;
            ctx.fillStyle = '#fff';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(img, 0, 0);
            URL.revokeObjectURL(url);

            const pdf = new jsPDF({ orientation: roomW >= roomH ? 'l' : 'p', unit: 'mm', format: 'a4' });
            const pw = pdf.internal.pageSize.getWidth();
            const ph = pdf.internal.pageSize.getHeight();
            const margin = 15;
            const aw = pw - margin * 2;
            const ah = ph - margin * 2 - 15;
            const ratio = canvas.width / canvas.height;
            let w = aw, h = aw / ratio;
            if (h > ah) { h = ah; w = ah * ratio; }
            pdf.setFontSize(14);
            pdf.text(`${active.name} — ${wedding.partner_a_name} & ${wedding.partner_b_name}`, margin, margin);
            pdf.addImage(canvas.toDataURL('image/png'), 'PNG', margin, margin + 8, w, h);
            pdf.save(`${active.name.replace(/[^\w-]+/g, '_')}.pdf`);
        } catch (err) {
            console.error(err);
            alert(t('floorplan.pdf_failed'));
        }
    }

    // ── Drag & drop ──────────────────────────────────────────────────────────
    const svgRef = useRef<SVGSVGElement>(null);
    const dragRef = useRef<{ id: string; offsetX: number; offsetY: number } | null>(null);

    function pointFromEvent(e: React.MouseEvent): { x: number; y: number } {
        const svg = svgRef.current;
        if (!svg) return { x: 0, y: 0 };
        const rect = svg.getBoundingClientRect();
        return {
            x: (e.clientX - rect.left) / (PPM * zoom),
            y: (e.clientY - rect.top)  / (PPM * zoom),
        };
    }

    function onMouseDown(e: React.MouseEvent, el: FpElement) {
        if (e.button !== 0) return; // only left-click drags
        e.stopPropagation();
        const p = pointFromEvent(e);
        dragRef.current = { id: el.id, offsetX: p.x - el.x, offsetY: p.y - el.y };
    }

    function onMouseMove(e: React.MouseEvent) {
        if (!dragRef.current) return;
        const { id, offsetX, offsetY } = dragRef.current;
        const p = pointFromEvent(e);
        const el = elements.find(x => x.id === id);
        if (!el) return;
        const x = clamp(p.x - offsetX, 0, roomW - el.width);
        const y = clamp(p.y - offsetY, 0, roomH - el.height);
        updateElement(id, { x, y });
    }

    function onMouseUp() {
        dragRef.current = null;
    }

    function onContextMenu(e: React.MouseEvent, el: FpElement) {
        e.preventDefault();
        e.stopPropagation();
        setSelectedId(el.id);
    }

    return (
        <AppLayout wedding={wedding as any}>
            <div className="flex flex-col h-[calc(100vh-3.5rem)] lg:h-screen">
                {/* Top bar */}
                <div className="flex flex-wrap items-center gap-2 px-4 py-3 border-b border-border bg-card">
                    <select
                        value={activeId ?? ''}
                        onChange={(e) => setActiveId(e.target.value || null)}
                        className="px-3 py-1.5 rounded-lg border border-border bg-background text-sm"
                    >
                        {plans.length === 0 && <option value="">{t('floorplan.no_plans_option')}</option>}
                        {plans.map(p => (
                            <option key={p.id} value={p.id}>
                                {p.name}{p.is_primary ? ' ★' : ''}
                            </option>
                        ))}
                    </select>
                    <button onClick={createPlan} className="text-sm px-3 py-1.5 rounded-lg border border-border hover:bg-muted">+ {t('floorplan.new_plan')}</button>

                    {active && (
                        <>
                            <input
                                value={name}
                                onChange={(e) => { setName(e.target.value); setDirty(true); }}
                                className="px-3 py-1.5 rounded-lg border border-border bg-background text-sm font-medium"
                            />
                            <button
                                onClick={setPrimary}
                                disabled={active.is_primary}
                                className={`text-sm px-3 py-1.5 rounded-lg border ${active.is_primary ? 'border-primary text-primary bg-primary/10' : 'border-border hover:bg-muted'}`}
                            >
                                {active.is_primary ? `✓ ${t('floorplan.assigned')}` : t('floorplan.assign')}
                            </button>
                            <button
                                onClick={save}
                                disabled={!dirty || saving}
                                className="text-sm px-3 py-1.5 rounded-lg bg-primary text-primary-foreground disabled:opacity-50"
                            >
                                {saving ? t('common.saving') : dirty ? t('common.save') : t('common.saved')}
                            </button>
                            <button onClick={exportPdf} className="text-sm px-3 py-1.5 rounded-lg border border-border hover:bg-muted">PDF</button>
                            <button onClick={deletePlan} className="text-sm px-3 py-1.5 rounded-lg text-red-500 hover:bg-red-50">{t('common.delete')}</button>

                            <div className="ml-auto flex items-center gap-2">
                                <div className="flex border border-border rounded-lg overflow-hidden text-sm">
                                    <button onClick={() => setView('2d')} className={`px-3 py-1.5 ${view === '2d' ? 'bg-primary text-primary-foreground' : ''}`}>2D</button>
                                    <button onClick={() => setView('3d')} className={`px-3 py-1.5 ${view === '3d' ? 'bg-primary text-primary-foreground' : ''}`}>3D</button>
                                </div>
                                {view === '2d' && (
                                    <>
                                        <button onClick={() => setShowGrid(g => !g)} className="text-xs px-2 py-1 rounded border border-border">{showGrid ? t('floorplan.grid') : t('floorplan.no_grid')}</button>
                                        <button onClick={() => setZoom(z => Math.max(0.25, z - 0.1))} className="text-sm px-2 py-1 rounded border border-border">−</button>
                                        <span className="text-xs text-muted-foreground w-10 text-center">{Math.round(zoom * 100)}%</span>
                                        <button onClick={() => setZoom(z => Math.min(2, z + 0.1))} className="text-sm px-2 py-1 rounded border border-border">+</button>
                                    </>
                                )}
                            </div>
                        </>
                    )}
                </div>

                {!active ? (
                    <div className="flex-1 flex items-center justify-center">
                        <div className="text-center">
                            <p className="text-muted-foreground mb-4">{t('floorplan.empty_title')}</p>
                            <button onClick={createPlan} className="px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm">{t('floorplan.create_first')}</button>
                        </div>
                    </div>
                ) : (
                    <div className="flex-1 flex overflow-hidden">
                        {/* Element palette */}
                        <aside className="w-48 border-r border-border bg-muted/30 overflow-y-auto p-3">
                            <p className="text-[10px] uppercase tracking-wide text-muted-foreground mb-2">{t('floorplan.palette_heading')}</p>
                            <div className="grid grid-cols-1 gap-1.5">
                                {ELEMENT_TYPES.filter(et => !TABLE_ELEMENT_TYPES.has(et.type)).map(et => (
                                    <button
                                        key={et.type}
                                        onClick={() => addElement(et.type)}
                                        className="text-xs text-left px-2.5 py-1.5 rounded-md border border-border bg-card hover:border-primary"
                                    >{t(`floorplan.el_${et.type}`)}</button>
                                ))}
                            </div>
                            <p className="mt-3 text-[10px] text-muted-foreground leading-relaxed">
                                {currentTableCount > 0
                                    ? <>{currentTableCount} {currentTableCount === 1 ? t('floorplan.table_one') : t('floorplan.table_other')} {t('floorplan.from')} <span className="text-foreground/80">{t('nav.seating')}</span> {t('floorplan.drag_arrange')}</>
                                    : <>{t('floorplan.add_tables_in')} <span className="text-foreground/80">{t('nav.seating')}</span> {t('floorplan.appear_here')}</>}
                            </p>

                            <div className="mt-6">
                                <p className="text-[10px] uppercase tracking-wide text-muted-foreground mb-2">{t('floorplan.room_label')}</p>
                                <div className="flex gap-1.5">
                                    <input
                                        type="number" step="0.5" min="2" max="100"
                                        value={roomW}
                                        onChange={(e) => { setRoomW(Number(e.target.value) || 1); setDirty(true); }}
                                        className="w-full px-2 py-1 text-xs rounded border border-border bg-background"
                                    />
                                    <span className="text-muted-foreground text-xs flex items-center">×</span>
                                    <input
                                        type="number" step="0.5" min="2" max="100"
                                        value={roomH}
                                        onChange={(e) => { setRoomH(Number(e.target.value) || 1); setDirty(true); }}
                                        className="w-full px-2 py-1 text-xs rounded border border-border bg-background"
                                    />
                                </div>
                            </div>
                        </aside>

                        {/* Canvas */}
                        <div className="flex-1 overflow-auto bg-muted/20 relative">
                            {view === '2d' ? (
                                <div className="p-6">
                                    <svg
                                        key={`fp-${activeId}-${svgEpoch}`}
                                        id="fp-svg"
                                        ref={svgRef}
                                        width={roomW * PPM * zoom}
                                        height={roomH * PPM * zoom}
                                        viewBox={`0 0 ${roomW * PPM} ${roomH * PPM}`}
                                        onMouseMove={onMouseMove}
                                        onMouseUp={onMouseUp}
                                        onMouseLeave={onMouseUp}
                                        onClick={() => setSelectedId(null)}
                                        style={{ background: '#fff', border: '1px solid #ddd', borderRadius: 8 }}
                                    >
                                        {showGrid && <GridPattern id={patternId} w={roomW * PPM} h={roomH * PPM} />}
                                        {elements.map(el => (
                                            <ElementShape
                                                key={el.id}
                                                el={el}
                                                selected={el.id === selectedId}
                                                onMouseDown={(e) => onMouseDown(e, el)}
                                                onContextMenu={(e) => onContextMenu(e, el)}
                                                onClick={(e) => { e.stopPropagation(); setSelectedId(el.id); }}
                                            />
                                        ))}
                                    </svg>
                                    <p className="text-[11px] text-muted-foreground mt-3">
                                        {t('floorplan.canvas_hint')}
                                    </p>
                                </div>
                            ) : (
                                <Suspense fallback={<div className="flex items-center justify-center h-full text-muted-foreground text-sm">{t('floorplan.loading_3d')}</div>}>
                                    <FloorPlan3D elements={elements} roomWidth={roomW} roomHeight={roomH} />
                                </Suspense>
                            )}
                        </div>

                        {/* Properties panel */}
                        {selected && view === '2d' && (
                            <aside className="w-64 border-l border-border bg-card overflow-y-auto p-4">
                                <div className="flex items-center justify-between mb-3">
                                    <h3 className="font-semibold text-sm">{t('floorplan.properties')}</h3>
                                    {!isManagedTable(selected.id) && (
                                        <button onClick={() => removeElement(selected.id)} className="text-xs text-red-500 hover:underline">{t('common.delete')}</button>
                                    )}
                                </div>
                                {isManagedTable(selected.id) && (
                                    <p className="mb-3 text-[11px] text-muted-foreground leading-relaxed bg-muted/50 rounded-md px-2.5 py-2">
                                        {t('floorplan.managed_props_1')} <span className="text-foreground/80">{t('nav.seating')}</span>{t('floorplan.managed_props_2')}
                                    </p>
                                )}
                                <div className="space-y-3 text-sm">
                                    <Field label={t('floorplan.field_label')}>
                                        <input
                                            value={selected.label}
                                            onChange={(e) => updateElement(selected.id, { label: e.target.value })}
                                            disabled={isManagedTable(selected.id)}
                                            className="w-full px-2 py-1 rounded border border-border bg-background text-sm disabled:opacity-60 disabled:cursor-not-allowed"
                                        />
                                    </Field>
                                    <Field label={t('floorplan.field_seats')}>
                                        <input
                                            type="number" min="0" max="50"
                                            value={selected.seats}
                                            onChange={(e) => updateElement(selected.id, { seats: Number(e.target.value) || 0 })}
                                            disabled={isManagedTable(selected.id)}
                                            className="w-full px-2 py-1 rounded border border-border bg-background text-sm disabled:opacity-60 disabled:cursor-not-allowed"
                                        />
                                    </Field>
                                    <div className="grid grid-cols-2 gap-2">
                                        <Field label={t('floorplan.field_width')}>
                                            <input
                                                type="number" step="0.1" min="0.2"
                                                value={selected.width}
                                                onChange={(e) => updateElement(selected.id, { width: Number(e.target.value) || 0.2 })}
                                                className="w-full px-2 py-1 rounded border border-border bg-background text-sm"
                                            />
                                        </Field>
                                        <Field label={t('floorplan.field_height')}>
                                            <input
                                                type="number" step="0.1" min="0.2"
                                                value={selected.height}
                                                onChange={(e) => updateElement(selected.id, { height: Number(e.target.value) || 0.2 })}
                                                className="w-full px-2 py-1 rounded border border-border bg-background text-sm"
                                            />
                                        </Field>
                                    </div>
                                    <Field label={t('floorplan.field_rotation', { deg: selected.rotation })}>
                                        <input
                                            type="range" min="0" max="359" step="1"
                                            value={selected.rotation}
                                            onChange={(e) => updateElement(selected.id, { rotation: Number(e.target.value) })}
                                            className="w-full"
                                        />
                                    </Field>
                                </div>
                            </aside>
                        )}
                    </div>
                )}
            </div>
        </AppLayout>
    );
}

// ── Sub-components ───────────────────────────────────────────────────────────

function Field({ label, children }: { label: string; children: React.ReactNode }) {
    return (
        <div>
            <label className="block text-xs text-muted-foreground mb-1">{label}</label>
            {children}
        </div>
    );
}

function GridPattern({ id, w, h }: { id: string; w: number; h: number }) {
    // A unique pattern id per mount keeps `url(#…)` from resolving to a stale or
    // duplicate pattern elsewhere in the document (the cause of a grid that
    // paints empty until the SVG is remounted).
    // useId() emits colons (e.g. ":r0:") which break SVG url(#…) funcIRI refs;
    // strip them to a selector-safe token.
    const patternId = `grid-${id.replace(/[^a-zA-Z0-9_-]/g, '')}`;
    return (
        <>
            <defs>
                <pattern id={patternId} width={PPM} height={PPM} patternUnits="userSpaceOnUse">
                    <path d={`M ${PPM} 0 L 0 0 0 ${PPM}`} fill="none" stroke="#eee" strokeWidth="0.5" />
                </pattern>
            </defs>
            <rect width={w} height={h} fill={`url(#${patternId})`} />
        </>
    );
}

interface ShapeProps {
    el: FpElement;
    selected: boolean;
    onMouseDown: (e: React.MouseEvent) => void;
    onClick: (e: React.MouseEvent) => void;
    onContextMenu: (e: React.MouseEvent) => void;
}

function ElementShape({ el, selected, onMouseDown, onClick, onContextMenu }: ShapeProps) {
    const colors = ELEMENT_COLORS[el.type];
    const x = el.x * PPM;
    const y = el.y * PPM;
    const w = el.width * PPM;
    const h = el.height * PPM;
    const isRound = el.type === 'round_table';
    const stroke = selected ? '#c97b84' : colors.stroke;
    const strokeWidth = selected ? 2.5 : 1.2;

    const transform = `rotate(${el.rotation} ${x + w / 2} ${y + h / 2})`;
    const labelText = el.seats > 0 ? `${el.label} (${el.seats})` : el.label;

    return (
        <g
            transform={transform}
            onMouseDown={onMouseDown}
            onClick={onClick}
            onContextMenu={onContextMenu}
            style={{ cursor: 'move' }}
        >
            {isRound ? (
                <ellipse
                    cx={x + w / 2} cy={y + h / 2} rx={w / 2} ry={h / 2}
                    fill={colors.fill} stroke={stroke} strokeWidth={strokeWidth}
                />
            ) : (
                <rect
                    x={x} y={y} width={w} height={h}
                    fill={colors.fill} stroke={stroke} strokeWidth={strokeWidth}
                    rx={4}
                />
            )}
            <text
                x={x + w / 2} y={y + h / 2}
                textAnchor="middle" dominantBaseline="middle"
                fontSize={Math.min(13, Math.max(9, w / 7))}
                fill={['dj_booth', 'bar'].includes(el.type) ? '#fff' : '#444'}
                pointerEvents="none"
                style={{ userSelect: 'none' }}
            >
                {labelText}
            </text>
        </g>
    );
}
