@model FloorPlanEditorViewModel
@{
    ViewData["Title"] = Model.FloorPlan.FloorPlanID == 0 ? "New Floor Plan" : Model.FloorPlan.Name;
    ViewData["ProjectSection"] = "Floor Plans";
    var activeFloor = Model.Floors.OrderBy(x => x.SortOrder).ThenBy(x => x.Name).FirstOrDefault();
    var blocksByFloor = Model.Blocks.GroupBy(x => x.FloorPlanFloorID).ToDictionary(x => x.Key, x => x.ToList());
    var transitionsByFloor = Model.Transitions.GroupBy(x => x.FloorPlanFloorID).ToDictionary(x => x.Key, x => x.ToList());
    var firstBlockIdByFloorLocation = Model.Blocks
        .Where(x => x.LocationID.HasValue)
        .GroupBy(x => $"{x.FloorPlanFloorID}:{x.LocationID!.Value}")
        .ToDictionary(x => x.Key, x => x.OrderBy(block => block.Y).ThenBy(block => block.X).First().FloorPlanBlockID);
    var occupancyTokensByFloorLocation = Model.OccupancyTokens
        .GroupBy(x => $"{x.SceneID}:{x.FloorPlanFloorID}:{x.LocationID}")
        .ToDictionary(x => x.Key, x => x.OrderBy(token => token.EntityType == "Character" ? 0 : 1).ThenBy(token => token.DisplayName).ToList());
    var occupancyTokenCountByScene = Model.OccupancyTokens
        .GroupBy(x => x.SceneID)
        .ToDictionary(x => x.Key, x => x.Count());
    var occupancyTokenCountBySceneFloor = Model.OccupancyTokens
        .GroupBy(x => $"{x.SceneID}:{x.FloorPlanFloorID}")
        .ToDictionary(x => x.Key, x => x.Count());
    var activeOccupancyScene = Model.OccupancyScenes.FirstOrDefault();
    var selectedBlockId = int.TryParse(Context.Request.Query["selectedBlockId"], out var parsedSelectedBlockId) ? parsedSelectedBlockId : 0;
    string BlockClass(string blockType) => $"floor-plan-block floor-plan-block--{blockType.ToLowerInvariant()}";
    string CssNumber(decimal value) => value.ToString("0.###", System.Globalization.CultureInfo.InvariantCulture);
    string TransitionDetails(FloorPlanTransitionEditViewModel transition)
    {
        var details = $"{transition.FromLocationName} -> {transition.ToLocationName}\n{transition.TransitionType}";
        return string.IsNullOrWhiteSpace(transition.DisplayLabel) ? details : $"{details}\n{transition.DisplayLabel}";
    }

    string TransitionMarkerType(string? transitionType)
    {
        var normalized = (transitionType ?? string.Empty)
            .Trim()
            .Replace(" ", string.Empty, StringComparison.OrdinalIgnoreCase)
            .Replace("-", string.Empty, StringComparison.OrdinalIgnoreCase)
            .ToLowerInvariant();

        return normalized switch
        {
            "door" => "door",
            "doubledoor" => "double-door",
            "frenchdoor" or "frenchdoors" => "french-door",
            "slidingdoor" or "slidingdoors" => "sliding-door",
            "window" => "window",
            "archway" or "opening" => "archway",
            "secretdoor" or "hiddendoor" => "secret-door",
            "blockeddoor" => "blocked-door",
            "exteriordoor" => "exterior-door",
            "stairs" or "stair" or "stairsup" or "stairsdown" => "stairs",
            "ladder" or "ladders" => "ladder",
            "passage" or "openpassage" => "passage",
            "hiddenpassage" or "secretpassage" => "secret-route",
            _ => "other"
        };
    }

    string TransitionMarkerSvg(string markerType) => markerType switch
    {
        "door" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-line"" d=""M2 9h6""/><path class=""marker-line"" d=""M10 9h6""/><path class=""marker-arc"" d=""M8 9a6 6 0 0 1 6-6""/></svg>",
        "double-door" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-line"" d=""M2 9h4""/><path class=""marker-line"" d=""M12 9h4""/><path class=""marker-arc"" d=""M6 9a4 4 0 0 1 4-4""/><path class=""marker-arc"" d=""M12 9a4 4 0 0 0-4-4""/></svg>",
        "french-door" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-line marker-strong"" d=""M2 9h3""/><path class=""marker-line marker-strong"" d=""M13 9h3""/><path class=""marker-arc"" d=""M5 9a4 4 0 0 1 4-4""/><path class=""marker-arc"" d=""M13 9a4 4 0 0 0-4-4""/><path class=""marker-line"" d=""M9 5v8""/></svg>",
        "sliding-door" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-line"" d=""M2 9h14""/><path class=""marker-line marker-track"" d=""M4 6h10""/><path class=""marker-line marker-track"" d=""M9 6l3 3""/></svg>",
        "window" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-window"" d=""M3 7h12""/><path class=""marker-window"" d=""M3 11h12""/><path class=""marker-line"" d=""M5 9h8""/></svg>",
        "archway" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-line"" d=""M2 14h4""/><path class=""marker-line"" d=""M12 14h4""/><path class=""marker-arc"" d=""M6 14a3 3 0 0 1 6 0""/></svg>",
        "secret-door" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-line marker-dotted"" d=""M2 9h6""/><path class=""marker-line marker-dotted"" d=""M10 9h6""/><path class=""marker-arc marker-dotted"" d=""M8 9a6 6 0 0 1 6-6""/></svg>",
        "blocked-door" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-line"" d=""M2 9h14""/><path class=""marker-blocked"" d=""M6 5l6 8""/><path class=""marker-blocked"" d=""M12 5l-6 8""/></svg>",
        "exterior-door" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-line marker-strong"" d=""M2 9h6""/><path class=""marker-line marker-strong"" d=""M10 9h6""/><path class=""marker-arc marker-strong"" d=""M8 9a6 6 0 0 1 6-6""/></svg>",
        "stairs" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-line"" d=""M4 13h10""/><path class=""marker-line"" d=""M5 10h8""/><path class=""marker-line"" d=""M6 7h6""/><path class=""marker-line"" d=""M7 4h4""/></svg>",
        "ladder" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-line"" d=""M6 3v12""/><path class=""marker-line"" d=""M12 3v12""/><path class=""marker-line"" d=""M6 5h6""/><path class=""marker-line"" d=""M6 8h6""/><path class=""marker-line"" d=""M6 11h6""/><path class=""marker-line"" d=""M6 14h6""/></svg>",
        "passage" => @"<svg viewBox=""0 0 18 18"" aria-hidden=""true"" focusable=""false""><path class=""marker-line"" d=""M3 7h12""/><path class=""marker-line"" d=""M3 11h12""/><path class=""marker-line"" d=""M11 5l4 4-4 4""/><path class=""marker-line"" d=""M7 5L3 9l4 4""/></svg>",
        _ => @"<span aria-hidden=""true"">x</span>"
    };

    var transitionPositions = FloorPlanTransitionPositions.All
        .Select(x => new SelectListItem(x, x))
        .ToList();
}

<nav class="breadcrumb-trail" aria-label="Breadcrumb">
    <a asp-controller="Projects" asp-action="Index">Projects</a>
    <a asp-controller="Projects" asp-action="Details" asp-route-id="@Model.Project.ProjectID">@Model.Project.ProjectName</a>
    <a asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Floor Plans</a>
    <span>@Model.FloorPlan.Name</span>
</nav>

<partial name="_ProjectSectionNav" model="Model.Project" />

<div class="page-heading compact">
    <div>
        <p class="eyebrow">Location floor plan</p>
        <h1>@Model.FloorPlan.Name <help-icon key="floorPlans.overview" /></h1>
    </div>
    <a class="btn btn-outline-secondary" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID">Back to floor plans</a>
</div>

@if (TempData["FloorPlanMessage"] is string message)
{
    <div class="alert alert-success">@message</div>
}
@if (TempData["FloorPlanError"] is string error)
{
    <div class="alert alert-danger">@error</div>
}

<div data-floor-plan-workspace data-floor-plan-id="@Model.FloorPlan.FloorPlanID">
    @if (!Model.Floors.Any())
    {
        <section class="empty-panel">
        <h2>No floors yet</h2>
        <p>Add a floor to start arranging linked locations.</p>
        <div class="floor-plan-tool-card mt-3">
            <h3>Add floor <help-icon key="floorPlans.addFloor" mode="inline" /></h3>
            <input asp-for="NewFloor.FloorPlanID" type="hidden" name="FloorPlanID" form="floor-plan-add-floor-form" />
            <div class="row g-2 align-items-end">
                <div class="col-md-4">
                    <label class="form-label">New floor <help-icon key="floorPlans.fields.floorName" mode="inline" /></label>
                    <input asp-for="NewFloor.Name" class="form-control form-control-sm" name="Name" form="floor-plan-add-floor-form" />
                </div>
                <div class="col-md-2">
                    <label class="form-label">Level <help-icon key="floorPlans.fields.level" mode="inline" /></label>
                    <input asp-for="NewFloor.SortOrder" class="form-control form-control-sm" name="SortOrder" min="-10" max="50" step="1" form="floor-plan-add-floor-form" />
                </div>
                <div class="col-md-2">
                    <label class="form-label">Width <help-icon key="floorPlans.fields.gridWidth" mode="inline" /></label>
                    <input asp-for="NewFloor.GridWidth" class="form-control form-control-sm" name="GridWidth" min="8" max="80" form="floor-plan-add-floor-form" />
                </div>
                <div class="col-md-2">
                    <label class="form-label">Height <help-icon key="floorPlans.fields.gridHeight" mode="inline" /></label>
                    <input asp-for="NewFloor.GridHeight" class="form-control form-control-sm" name="GridHeight" min="8" max="80" form="floor-plan-add-floor-form" />
                </div>
                <div class="col-md-2">
                    <button class="btn btn-outline-primary btn-sm w-100" type="submit" form="floor-plan-add-floor-form">Add floor</button>
                </div>
            </div>
            <p class="form-text mb-0">Used to order floors and later to connect stairs up/down.</p>
        </div>
        </section>

        <form id="floor-plan-add-floor-form" asp-action="AddFloor" method="post"></form>
    }
    else
    {
        <form asp-action="SaveLayout" method="post" class="floor-plan-editor" data-floor-plan-editor>
        <input asp-for="FloorPlan.FloorPlanID" type="hidden" />
        <input type="hidden" name="floorPlanId" value="@Model.FloorPlan.FloorPlanID" />
        <input asp-for="FloorPlan.ProjectID" type="hidden" />
        <input asp-for="FloorPlan.LocationID" type="hidden" />
        <input asp-for="FloorPlan.Name" type="hidden" />
        <input asp-for="FloorPlan.Description" type="hidden" />

        <section class="floor-plan-editor-toolbar">
            <div class="floor-plan-toolbar-main">
                <div class="floor-plan-toolbar-floor-group">
                    <div class="floor-plan-active-floor">
                        <span class="muted">Active floor</span>
                        <strong data-current-floor-name>@activeFloor?.Name</strong>
                    </div>
                    <div class="floor-plan-tabs" role="tablist" aria-label="Floors">
                        @foreach (var floor in Model.Floors.OrderBy(x => x.SortOrder).ThenBy(x => x.Name))
                        {
                            var isActive = floor.FloorPlanFloorID == activeFloor?.FloorPlanFloorID;
                            <button type="button" class="floor-plan-tab @(isActive ? "active" : string.Empty)" data-floor-tab="@floor.FloorPlanFloorID" data-floor-name="@floor.Name" data-floor-order="@floor.SortOrder" data-floor-location-id="@floor.LocationID">@floor.Name</button>
                        }
                    </div>
                </div>
                <div class="floor-plan-toolbar-control-group">
                    <label class="floor-plan-toolbar-check">
                        <input type="checkbox" data-grid-coordinates-toggle />
                        Show grid coordinates <help-icon key="floorPlans.fields.gridCoordinates" mode="inline" />
                    </label>
                    <label class="floor-plan-reference-control">
                        <span class="muted">Reference floor <help-icon key="floorPlans.fields.referenceFloor" mode="inline" /></span>
                        <select class="form-select form-select-sm" data-reference-floor-select>
                            <option value="none">None</option>
                            <option value="below">Floor below</option>
                            <option value="above">Floor above</option>
                        </select>
                    </label>
                </div>
                <div class="floor-plan-toolbar-action-group">
                    <span class="floor-plan-save-status" data-save-status>Saved</span>
                    <help-icon key="floorPlans.structureMode" mode="inline" />
                    <div class="btn-group btn-group-sm" role="group" aria-label="Floor plan mode">
                        <button class="btn btn-outline-secondary active" type="button" data-floor-plan-mode="structure">Structure</button>
                        <button class="btn btn-outline-secondary" type="button" data-floor-plan-mode="occupancy">Occupancy</button>
                    </div>
                    <help-icon key="floorPlans.occupancyMode" mode="inline" />
                    <button class="btn btn-primary btn-sm" type="submit" data-save-layout-button>Save layout</button>
                </div>
            </div>
            <div class="floor-plan-occupancy-scene-row" data-occupancy-controls hidden>
                <label class="floor-plan-reference-control">
                    <span class="muted">Scene <help-icon key="floorPlans.occupancy.sceneSelector" mode="inline" /></span>
                    <select class="form-select form-select-sm" data-occupancy-scene-select>
                        @foreach (var scene in Model.OccupancyScenes)
                        {
                            <option value="@scene.SceneID" data-initial-floor-id="@scene.InitialFloorPlanFloorID" selected="@(scene.SceneID == activeOccupancyScene?.SceneID)">@scene.Label</option>
                        }
                    </select>
                </label>
                <button class="btn btn-outline-secondary btn-sm" type="button" data-occupancy-prev>Previous Scene</button>
                <button class="btn btn-outline-secondary btn-sm" type="button" data-occupancy-next>Next Scene</button>
                <help-icon key="floorPlans.occupancy.navigation" mode="inline" />
                <help-icon key="floorPlans.occupancy.tokens" mode="inline" />
                <div class="floor-plan-occupancy-warning-panel" data-occupancy-warning-panel hidden>
                    <strong>Occupancy Warnings <help-icon key="floorPlans.occupancy.warnings" mode="inline" /></strong>
                    <ul>
                        @foreach (var warning in Model.OccupancyWarnings)
                        {
                            if (string.Equals(warning.Message, "Some occupants are on other floors.", StringComparison.OrdinalIgnoreCase))
                            {
                                continue;
                            }

                            <li class="floor-plan-occupancy-warning floor-plan-occupancy-warning--@warning.Severity"
                                data-occupancy-warning
                                data-occupancy-scene="@warning.SceneID"
                                data-occupancy-floor="@warning.FloorPlanFloorID">
                                @warning.Message
                            </li>
                        }
                    </ul>
                </div>
            </div>
        </section>

        <div class="floor-plan-layout">
            <section class="floor-plan-stage">
                @for (var i = 0; i < Model.Floors.Count; i++)
                {
                    var floor = Model.Floors[i];
                    var isActive = floor.FloorPlanFloorID == activeFloor?.FloorPlanFloorID;
                    var floorBlocks = blocksByFloor.GetValueOrDefault(floor.FloorPlanFloorID) ?? [];
                    var floorTransitions = transitionsByFloor.GetValueOrDefault(floor.FloorPlanFloorID) ?? [];
                    <div class="floor-plan-floor @(isActive ? "active" : string.Empty)" data-floor-panel="@floor.FloorPlanFloorID" data-floor-order="@floor.SortOrder">
                        <div class="floor-plan-grid-wrap">
                            <div class="floor-plan-grid"
                                 style="--grid-width:@floor.GridWidth;--grid-height:@floor.GridHeight;"
                                 data-floor-grid="@floor.FloorPlanFloorID"
                                 data-grid-width="@floor.GridWidth"
                                 data-grid-height="@floor.GridHeight">
                                @if (!string.IsNullOrWhiteSpace(floor.BackgroundImagePath))
                                {
                                    <img class="floor-plan-background-image @(floor.BackgroundLocked ? "is-locked" : string.Empty)"
                                         src="@floor.BackgroundImagePath"
                                         alt=""
                                         style="--bg-opacity:@CssNumber(floor.BackgroundOpacity);--bg-scale:@CssNumber(floor.BackgroundScale);--bg-offset-x:@CssNumber(floor.BackgroundOffsetX)px;--bg-offset-y:@CssNumber(floor.BackgroundOffsetY)px;"
                                         data-background-image="@floor.FloorPlanFloorID" />
                                }
                                @foreach (var block in floorBlocks)
                                {
                                    var blockIndex = Model.Blocks.FindIndex(x => x.FloorPlanBlockID == block.FloorPlanBlockID);
                                    <button type="button"
                                            class="@BlockClass(block.BlockType)"
                                            style="--x:@block.X;--y:@block.Y;--w:@block.WidthCells;--h:@block.HeightCells;"
                                            data-block="@block.FloorPlanBlockID"
                                            data-block-index="@blockIndex"
                                            data-floor-id="@block.FloorPlanFloorID"
                                            data-location-id="@block.LocationID"
                                            data-location-name="@block.LocationName"
                                            data-location-path="@block.LocationPath"
                                            data-location-title="@block.LocationPath"
                                            title="@block.LocationPath">
                                        <span class="floor-plan-block-label">@block.DisplayLabel</span>
                                        <span class="floor-plan-resize-handle" data-resize-handle aria-hidden="true"></span>
                                    </button>
                                    if (block.LocationID.HasValue && firstBlockIdByFloorLocation.GetValueOrDefault($"{block.FloorPlanFloorID}:{block.LocationID.Value}") == block.FloorPlanBlockID)
                                    {
                                        var locationTokens = Model.OccupancyScenes
                                            .SelectMany(scene => occupancyTokensByFloorLocation.GetValueOrDefault($"{scene.SceneID}:{block.FloorPlanFloorID}:{block.LocationID.Value}") ?? [])
                                            .ToList();
                                        if (locationTokens.Any())
                                        {
                                            foreach (var group in locationTokens.GroupBy(x => x.SceneID))
                                            {
                                                var tokens = group.ToList();
                                                var tokenTitle = string.Join(", ", tokens.Select(x => x.DisplayName));
                                                <div class="floor-plan-occupancy-tray"
                                                     style="--x:@block.X;--y:@block.Y;--w:@block.WidthCells;--h:@block.HeightCells;"
                                                     data-occupancy-tray
                                                     data-occupancy-floor="@block.FloorPlanFloorID"
                                                     data-occupancy-location="@block.LocationID"
                                                     data-occupancy-scene="@group.Key"
                                                     title="@tokenTitle">
                                                    @foreach (var token in tokens.Take(3))
                                                    {
                                                        var imagePath = !string.IsNullOrWhiteSpace(token.ThumbnailPath) ? token.ThumbnailPath : token.ImagePath;
                                                        <span class="floor-plan-occupancy-token floor-plan-occupancy-token--@token.EntityType.ToLowerInvariant()" title="@token.DisplayName">
                                                            @if (!string.IsNullOrWhiteSpace(imagePath))
                                                            {
                                                                <img src="@imagePath" alt="@token.DisplayName" loading="lazy" />
                                                            }
                                                            else
                                                            {
                                                                <span>@AvatarHelper.Initials(token.DisplayName)</span>
                                                            }
                                                        </span>
                                                    }
                                                    @if (tokens.Count > 3)
                                                    {
                                                        <span class="floor-plan-occupancy-more">+@(tokens.Count - 3)</span>
                                                    }
                                                    <span class="visually-hidden">Occupancy tokens help: characters and assets placed in this scene.</span>
                                                </div>
                                            }
                                        }
                                    }
                                }
                                @foreach (var scene in Model.OccupancyScenes)
                                {
                                    var totalTokens = occupancyTokenCountByScene.GetValueOrDefault(scene.SceneID);
                                    var floorTokens = occupancyTokenCountBySceneFloor.GetValueOrDefault($"{scene.SceneID}:{floor.FloorPlanFloorID}");
                                    if (totalTokens == 0)
                                    {
                                        <div class="floor-plan-occupancy-message" data-occupancy-message data-occupancy-scene="@scene.SceneID" data-occupancy-floor="@floor.FloorPlanFloorID" hidden>No occupancy has been set for this scene.</div>
                                    }
                                    else if (floorTokens == 0)
                                    {
                                        <div class="floor-plan-occupancy-message" data-occupancy-message data-occupancy-scene="@scene.SceneID" data-occupancy-floor="@floor.FloorPlanFloorID" hidden>Scene has occupancy, but none of the assigned locations are placed on this floor.</div>
                                    }
                                    else if (floorTokens < totalTokens)
                                    {
                                        <div class="floor-plan-occupancy-message" data-occupancy-message data-occupancy-scene="@scene.SceneID" data-occupancy-floor="@floor.FloorPlanFloorID" hidden>Some occupants are on other floors. <help-icon key="floorPlans.occupancy.otherFloors" mode="inline" /></div>
                                    }
                                }
                                @foreach (var transition in floorTransitions)
                                {
                                    var transitionDetails = TransitionDetails(transition);
                                    var markerType = TransitionMarkerType(transition.TransitionType);
                                    <div class="floor-plan-transition-marker floor-plan-transition-marker--@markerType"
                                         data-transition-marker="@transition.FloorPlanTransitionID"
                                         data-from-location-id="@transition.FromLocationID"
                                         data-to-location-id="@transition.ToLocationID"
                                         data-transition-type="@markerType"
                                         data-transition-kind="@transition.TransitionType"
                                         data-position-within-location="@transition.PositionWithinLocation"
                                         title="@transitionDetails"
                                         aria-label="@transitionDetails">
                                        @Html.Raw(TransitionMarkerSvg(markerType))
                                    </div>
                                }
                            </div>
                        </div>
                    </div>
                }
            </section>

            <aside class="floor-plan-side-panel">
                <div class="floor-plan-toolbox" data-toolbox>
                    <div class="floor-plan-toolbox-tabs" role="tablist" aria-label="Floor plan tools">
                        <button type="button" class="floor-plan-toolbox-tab active" id="floor-plan-tool-tab-add" role="tab" aria-selected="true" aria-controls="floor-plan-tool-add" data-toolbox-tab="add">Add block</button>
                        <button type="button" class="floor-plan-toolbox-tab" id="floor-plan-tool-tab-selected" role="tab" aria-selected="false" aria-controls="floor-plan-tool-selected" data-toolbox-tab="selected">Selected block</button>
                        <button type="button" class="floor-plan-toolbox-tab" id="floor-plan-tool-tab-background" role="tab" aria-selected="false" aria-controls="floor-plan-tool-background" data-toolbox-tab="background">Background</button>
                        <button type="button" class="floor-plan-toolbox-tab" id="floor-plan-tool-tab-transitions" role="tab" aria-selected="false" aria-controls="floor-plan-tool-transitions" data-toolbox-tab="transitions">Transitions</button>
                        <button type="button" class="floor-plan-toolbox-tab" id="floor-plan-tool-tab-floors" role="tab" aria-selected="false" aria-controls="floor-plan-tool-floors" data-toolbox-tab="floors">Floors</button>
                    </div>
                    <div class="floor-plan-toolbox-body">
                <section class="floor-plan-tool-card active" id="floor-plan-tool-add" role="tabpanel" aria-labelledby="floor-plan-tool-tab-add" data-toolbox-panel="add">
                    <h2>Add block <help-icon key="floorPlans.addBlock" /></h2>
                    <p class="muted">Adds a block to <span data-current-floor-name>@activeFloor?.Name</span>. Use a child Location or create a new room under this floor.</p>
                    <input type="hidden" name="FloorPlanBlockID" value="0" form="floor-plan-add-block-form" />
                    <input type="hidden" name="FloorPlanFloorID" value="@activeFloor?.FloorPlanFloorID" data-add-block-floor-id form="floor-plan-add-block-form" />
                    <input type="hidden" name="X" value="0" form="floor-plan-add-block-form" />
                    <input type="hidden" name="Y" value="0" form="floor-plan-add-block-form" />
                    <input type="hidden" name="WidthCells" value="1" data-add-block-width form="floor-plan-add-block-form" />
                    <input type="hidden" name="HeightCells" value="1" data-add-block-height form="floor-plan-add-block-form" />
                    <div class="floor-plan-choice-row" role="group" aria-label="Add block location mode">
                        <label><input type="radio" name="AddBlockLocationMode" value="Existing" checked form="floor-plan-add-block-form" data-add-block-mode /> Use existing location <help-icon key="floorPlans.fields.locationMode" mode="inline" /></label>
                        <label><input type="radio" name="AddBlockLocationMode" value="Create" form="floor-plan-add-block-form" data-add-block-mode /> Create new room/location <help-icon key="floorPlans.fields.locationMode" mode="inline" /></label>
                    </div>
                    <div class="mb-2" data-existing-location-panel>
                        <label class="form-label" for="add-block-location">Existing child Location <help-icon key="floorPlans.fields.location" mode="inline" /></label>
                        <select id="add-block-location" class="form-select form-select-sm" name="LocationID" form="floor-plan-add-block-form" data-add-block-location>
                            <option value="">Choose a child Location</option>
                            @foreach (var location in Model.Locations)
                            {
                                <option value="@location.LocationID" data-parent-location-id="@location.ParentLocationID">@location.LocationPath</option>
                            }
                        </select>
                    </div>
                    <div class="mb-2 d-none" data-new-location-panel>
                        <label class="form-label" for="add-block-new-location">New room/location name <help-icon key="floorPlans.fields.location" mode="inline" /></label>
                        <input id="add-block-new-location" class="form-control form-control-sm" name="NewLocationName" form="floor-plan-add-block-form" />
                    </div>
                    <div class="mb-2">
                        <label class="form-label" for="add-block-type">Block type <help-icon key="floorPlans.fields.blockType" mode="inline" /></label>
                        <select id="add-block-type" class="form-select form-select-sm" name="BlockType" asp-items="Model.BlockTypeOptions" form="floor-plan-add-block-form"></select>
                    </div>
                    <div class="mb-2">
                        <label class="form-label" for="add-block-size-preset">Size preset <help-icon key="floorPlans.fields.sizePreset" mode="inline" /></label>
                        <select id="add-block-size-preset" class="form-select form-select-sm" data-size-preset>
                            <option value="1x1">Small Room 1 x 1</option>
                            <option value="2x1" selected>Medium Room 2 x 1</option>
                            <option value="2x2">Large Room 2 x 2</option>
                            <option value="1x3">Corridor 1 x 3</option>
                            <option value="3x2">Wide Hall 3 x 2</option>
                        </select>
                    </div>
                    <div class="mb-3">
                        <label class="form-label" for="add-block-label">Optional label override <help-icon key="floorPlans.fields.labelOverride" mode="inline" /></label>
                        <input id="add-block-label" class="form-control form-control-sm" name="LabelOverride" form="floor-plan-add-block-form" />
                    </div>
                    <button class="btn btn-primary btn-sm w-100" type="submit" form="floor-plan-add-block-form">Add block</button>
                </section>

                <section class="floor-plan-tool-card" id="floor-plan-tool-selected" role="tabpanel" aria-labelledby="floor-plan-tool-tab-selected" data-toolbox-panel="selected" hidden>
                    <h2>Selected block <help-icon key="floorPlans.addBlock" /></h2>
                    <p class="muted" data-no-selection>Select a block on the grid to edit its details.</p>
                    <div class="floor-plan-overlap-warning d-none" data-overlap-warning>Some blocks overlap on this floor.</div>

                    @for (var i = 0; i < Model.Blocks.Count; i++)
                    {
                        <div class="floor-plan-block-editor d-none" data-block-editor="@Model.Blocks[i].FloorPlanBlockID">
                            <input asp-for="Blocks[i].FloorPlanBlockID" type="hidden" />
                            <input asp-for="Blocks[i].FloorPlanFloorID" type="hidden" data-block-field="floor" />
                            <input asp-for="Blocks[i].LocationName" type="hidden" />
                            <input asp-for="Blocks[i].LocationPath" type="hidden" />
                            <div class="mb-2">
                                <label class="form-label">Location</label>
                                <select asp-for="Blocks[i].LocationID" asp-items="Model.LocationOptions" class="form-select form-select-sm" data-block-field="location"></select>
                            </div>
                            <div class="mb-2">
                                <label class="form-label">Type <help-icon key="floorPlans.fields.blockType" mode="inline" /></label>
                                <select asp-for="Blocks[i].BlockType" asp-items="Model.BlockTypeOptions" class="form-select form-select-sm" data-block-field="type"></select>
                            </div>
                            <div class="row g-2">
                                <div class="col-6">
                                    <label class="form-label">X <help-icon key="floorPlans.fields.xCoordinate" mode="inline" /></label>
                                    <select class="form-select form-select-sm"
                                            id="Blocks_@(i)__X"
                                            name="Blocks[@i].X"
                                            data-block-field="x"
                                            data-coordinate-axis="x"
                                            data-coordinate-value="@Model.Blocks[i].X"></select>
                                </div>
                                <div class="col-6">
                                    <label class="form-label">Y <help-icon key="floorPlans.fields.yCoordinate" mode="inline" /></label>
                                    <select class="form-select form-select-sm"
                                            id="Blocks_@(i)__Y"
                                            name="Blocks[@i].Y"
                                            data-block-field="y"
                                            data-coordinate-axis="y"
                                            data-coordinate-value="@Model.Blocks[i].Y"></select>
                                </div>
                                <div class="col-6">
                                    <label class="form-label">Width <help-icon key="floorPlans.fields.dimensions" mode="inline" /></label>
                                    <input asp-for="Blocks[i].WidthCells" class="form-control form-control-sm" min="1" max="80" data-block-field="w" />
                                </div>
                                <div class="col-6">
                                    <label class="form-label">Height <help-icon key="floorPlans.fields.dimensions" mode="inline" /></label>
                                    <input asp-for="Blocks[i].HeightCells" class="form-control form-control-sm" min="1" max="80" data-block-field="h" />
                                </div>
                            </div>
                            <div class="mt-2">
                                <label class="form-label">Label override <help-icon key="floorPlans.fields.labelOverride" mode="inline" /></label>
                                <input asp-for="Blocks[i].LabelOverride" class="form-control form-control-sm" />
                            </div>
                            <div class="mt-2">
                                <label class="form-label">Notes</label>
                                <textarea asp-for="Blocks[i].Notes" class="form-control form-control-sm" rows="3"></textarea>
                            </div>
                            <div class="button-row mt-3">
                                <button class="btn btn-outline-danger btn-sm"
                                        type="button"
                                        data-bs-toggle="modal"
                                        data-bs-target="#delete-block-@Model.Blocks[i].FloorPlanBlockID">Delete block</button>
                            </div>
                        </div>
                    }
                </section>

                <section class="floor-plan-tool-card" id="floor-plan-tool-background" role="tabpanel" aria-labelledby="floor-plan-tool-tab-background" data-toolbox-panel="background" hidden>
                    <h2>Background <help-icon key="floorPlans.referenceFloorsBackgroundsAndGrid" /></h2>
                    <p class="muted">Settings affect only <span data-current-floor-name>@activeFloor?.Name</span>.</p>
                    @for (var i = 0; i < Model.Floors.Count; i++)
                    {
                        var floor = Model.Floors[i];
                        var isActive = floor.FloorPlanFloorID == activeFloor?.FloorPlanFloorID;
                        <div class="floor-plan-background-editor @(isActive ? string.Empty : "d-none")" data-background-editor="@floor.FloorPlanFloorID">
                            @if (string.IsNullOrWhiteSpace(floor.BackgroundImagePath))
                            {
                                <p class="muted">Upload a map, sketch, or floor-plan image to trace over. The image sits behind the grid and blocks.</p>
                                <div class="mb-3">
                                    <label class="form-label" for="background-image-@floor.FloorPlanFloorID">Background image <help-icon key="floorPlans.fields.backgroundImage" mode="inline" /></label>
                                    <input id="background-image-@floor.FloorPlanFloorID"
                                           class="form-control form-control-sm"
                                           type="file"
                                           name="backgroundImage"
                                           accept=".png,.jpg,.jpeg,.webp,image/png,image/jpeg,image/webp"
                                           form="floor-plan-background-upload-@floor.FloorPlanFloorID" />
                                </div>
                                <button class="btn btn-primary btn-sm w-100" type="submit" form="floor-plan-background-upload-@floor.FloorPlanFloorID">Upload background</button>
                            }
                            else
                            {
                                <p class="floor-plan-background-file">Background: <strong>@floor.BackgroundImageOriginalFileName</strong></p>
                                <div class="mb-2">
                                    <label class="form-label" for="background-opacity-@floor.FloorPlanFloorID">Opacity <help-icon key="floorPlans.fields.backgroundControls" mode="inline" /></label>
                                    <div class="floor-plan-background-range">
                                        <input id="background-opacity-@floor.FloorPlanFloorID"
                                               class="form-range"
                                               type="range"
                                               name="BackgroundOpacity"
                                               min="0.05"
                                               max="1"
                                               step="0.05"
                                               value="@CssNumber(floor.BackgroundOpacity)"
                                               data-background-field="opacity"
                                               data-background-floor="@floor.FloorPlanFloorID"
                                               form="floor-plan-background-settings-@floor.FloorPlanFloorID" />
                                        <span data-background-opacity-value="@floor.FloorPlanFloorID">@CssNumber(floor.BackgroundOpacity)</span>
                                    </div>
                                </div>
                                <div class="row g-2">
                                    <div class="col-6">
                                        <label class="form-label" for="background-scale-@floor.FloorPlanFloorID">Scale <help-icon key="floorPlans.fields.backgroundControls" mode="inline" /></label>
                                        <input id="background-scale-@floor.FloorPlanFloorID"
                                               class="form-control form-control-sm"
                                               type="number"
                                               name="BackgroundScale"
                                               min="0.1"
                                               max="5"
                                               step="0.05"
                                               value="@CssNumber(floor.BackgroundScale)"
                                               data-background-field="scale"
                                               data-background-floor="@floor.FloorPlanFloorID"
                                               form="floor-plan-background-settings-@floor.FloorPlanFloorID" />
                                    </div>
                                    <div class="col-6">
                                        <label class="form-label" for="background-offset-x-@floor.FloorPlanFloorID">Offset X <help-icon key="floorPlans.fields.backgroundControls" mode="inline" /></label>
                                        <input id="background-offset-x-@floor.FloorPlanFloorID"
                                               class="form-control form-control-sm"
                                               type="number"
                                               name="BackgroundOffsetX"
                                               min="-10000"
                                               max="10000"
                                               step="1"
                                               value="@CssNumber(floor.BackgroundOffsetX)"
                                               data-background-field="offsetX"
                                               data-background-floor="@floor.FloorPlanFloorID"
                                               form="floor-plan-background-settings-@floor.FloorPlanFloorID" />
                                    </div>
                                    <div class="col-6">
                                        <label class="form-label" for="background-offset-y-@floor.FloorPlanFloorID">Offset Y <help-icon key="floorPlans.fields.backgroundControls" mode="inline" /></label>
                                        <input id="background-offset-y-@floor.FloorPlanFloorID"
                                               class="form-control form-control-sm"
                                               type="number"
                                               name="BackgroundOffsetY"
                                               min="-10000"
                                               max="10000"
                                               step="1"
                                               value="@CssNumber(floor.BackgroundOffsetY)"
                                               data-background-field="offsetY"
                                               data-background-floor="@floor.FloorPlanFloorID"
                                               form="floor-plan-background-settings-@floor.FloorPlanFloorID" />
                                    </div>
                                    <div class="col-6 d-flex align-items-end">
                                        <label class="floor-plan-checkbox">
                                            <input type="checkbox"
                                                   name="BackgroundLocked"
                                                   value="true"
                                                   @(floor.BackgroundLocked ? "checked" : string.Empty)
                                                   data-background-field="locked"
                                                   data-background-floor="@floor.FloorPlanFloorID"
                                                   form="floor-plan-background-settings-@floor.FloorPlanFloorID" />
                                            Locked <help-icon key="floorPlans.fields.backgroundControls" mode="inline" />
                                        </label>
                                    </div>
                                </div>
                                <div class="button-row mt-3">
                                    <button class="btn btn-outline-secondary btn-sm" type="submit" form="floor-plan-background-reset-@floor.FloorPlanFloorID">Reset position</button>
                                    <button class="btn btn-outline-danger btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#remove-background-@floor.FloorPlanFloorID">Remove background</button>
                                </div>
                            }
                        </div>
                    }
                </section>

                <section class="floor-plan-tool-card" id="floor-plan-tool-transitions" role="tabpanel" aria-labelledby="floor-plan-tool-tab-transitions" data-toolbox-panel="transitions" hidden>
                    <h2>Transitions <help-icon key="floorPlans.transitions" /></h2>
                    <p class="muted">Create doors, archways, stairs, and other movement paths for <span data-current-floor-name>@activeFloor?.Name</span>.</p>
                    @foreach (var floor in Model.Floors)
                    {
                        var isActive = floor.FloorPlanFloorID == activeFloor?.FloorPlanFloorID;
                        var floorBlocks = blocksByFloor.GetValueOrDefault(floor.FloorPlanFloorID) ?? [];
                        var floorLocationOptions = floorBlocks
                            .Where(x => x.LocationID.HasValue)
                            .GroupBy(x => x.LocationID!.Value)
                            .Select(x => x.First())
                            .OrderBy(x => x.LocationName)
                            .ToList();
                        var floorTransitions = transitionsByFloor.GetValueOrDefault(floor.FloorPlanFloorID) ?? [];
                        <div class="floor-plan-transition-editor @(isActive ? string.Empty : "d-none")" data-transition-editor="@floor.FloorPlanFloorID">
                            <div class="accordion floor-plan-transition-accordion" id="transition-accordion-@floor.FloorPlanFloorID">
                                <div class="accordion-item">
                                    <h3 class="accordion-header" id="transition-create-heading-@floor.FloorPlanFloorID">
                                        <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#transition-create-@floor.FloorPlanFloorID" aria-expanded="true" aria-controls="transition-create-@floor.FloorPlanFloorID">
                                            Create transition
                                        </button>
                                        <help-icon key="floorPlans.createTransition" mode="inline" />
                                    </h3>
                                    <div id="transition-create-@floor.FloorPlanFloorID" class="accordion-collapse collapse show" aria-labelledby="transition-create-heading-@floor.FloorPlanFloorID" data-bs-parent="#transition-accordion-@floor.FloorPlanFloorID">
                                        <div class="accordion-body">
                                            @if (floorLocationOptions.Count < 1)
                                            {
                                                <p class="muted mb-0">Add at least one placed Location on this floor before creating a transition.</p>
                                            }
                                            else
                                            {
                                                <input type="hidden" name="FloorPlanTransitionID" value="0" form="floor-plan-transition-add-@floor.FloorPlanFloorID" />
                                                <input type="hidden" name="FloorPlanFloorID" value="@floor.FloorPlanFloorID" form="floor-plan-transition-add-@floor.FloorPlanFloorID" />
                                                <div class="mb-2">
                                                    <label class="form-label" for="transition-type-@floor.FloorPlanFloorID">Transition Type <help-icon key="floorPlans.fields.transitionType" mode="inline" /></label>
                                                    <select id="transition-type-@floor.FloorPlanFloorID" class="form-select form-select-sm" name="TransitionType" asp-items="Model.TransitionTypeOptions" form="floor-plan-transition-add-@floor.FloorPlanFloorID" data-transition-type-select="@floor.FloorPlanFloorID"></select>
                                                    <div class="form-text">
                                                        Hidden doors <help-icon key="floorPlans.transitions.hiddenDoor" mode="inline" />
                                                        Secret passages <help-icon key="floorPlans.transitions.secretPassage" mode="inline" />
                                                        Stairs <help-icon key="floorPlans.transitions.stairs" mode="inline" />
                                                        Ladders <help-icon key="floorPlans.transitions.ladder" mode="inline" />
                                                    </div>
                                                </div>
                                                <div class="mb-2">
                                                    <label class="form-label" for="transition-from-@floor.FloorPlanFloorID">From Location <help-icon key="floorPlans.fields.fromLocation" mode="inline" /></label>
                                                    <select id="transition-from-@floor.FloorPlanFloorID" class="form-select form-select-sm" name="FromLocationID" form="floor-plan-transition-add-@floor.FloorPlanFloorID" data-transition-from-select="@floor.FloorPlanFloorID">
                                                        <option value="">Choose a Location</option>
                                                        @foreach (var location in floorLocationOptions)
                                                        {
                                                            <option value="@location.LocationID">@location.LocationPath</option>
                                                        }
                                                    </select>
                                                </div>
                                                <div class="mb-2">
                                                    <label class="form-label" for="transition-to-@floor.FloorPlanFloorID">To Location <help-icon key="floorPlans.fields.toLocation" mode="inline" /></label>
                                                    <select id="transition-to-@floor.FloorPlanFloorID" class="form-select form-select-sm" name="ToLocationID" form="floor-plan-transition-add-@floor.FloorPlanFloorID" data-transition-to-select="@floor.FloorPlanFloorID">
                                                        <option value="">Choose a From Location first</option>
                                                    </select>
                                                    <div class="form-text" data-transition-to-message="@floor.FloorPlanFloorID"></div>
                                                    <button class="btn btn-link btn-sm px-0 d-none" type="button" data-transition-stair-fallback="@floor.FloorPlanFloorID">Show all locations on adjacent floor</button>
                                                </div>
                                                <div class="mb-2 d-none" data-transition-position-panel="@floor.FloorPlanFloorID">
                                                    <label class="form-label" for="transition-position-@floor.FloorPlanFloorID">Position Within Location <help-icon key="floorPlans.fields.positionWithinLocation" mode="inline" /></label>
                                                    <select id="transition-position-@floor.FloorPlanFloorID" class="form-select form-select-sm" name="PositionWithinLocation" asp-items="transitionPositions" form="floor-plan-transition-add-@floor.FloorPlanFloorID"></select>
                                                </div>
                                                <div class="mb-2">
                                                    <label class="form-label" for="transition-label-@floor.FloorPlanFloorID">Display Label <help-icon key="floorPlans.fields.labelOverride" mode="inline" /></label>
                                                    <input id="transition-label-@floor.FloorPlanFloorID" class="form-control form-control-sm" name="DisplayLabel" form="floor-plan-transition-add-@floor.FloorPlanFloorID" />
                                                </div>
                                                <div class="mb-2">
                                                    <label class="form-label" for="transition-notes-@floor.FloorPlanFloorID">Notes <help-icon key="floorPlans.fields.notes" mode="inline" /></label>
                                                    <textarea id="transition-notes-@floor.FloorPlanFloorID" class="form-control form-control-sm" name="Notes" rows="2" form="floor-plan-transition-add-@floor.FloorPlanFloorID"></textarea>
                                                </div>
                                                <label class="floor-plan-checkbox">
                                                    <input type="checkbox" name="IsLocked" value="true" form="floor-plan-transition-add-@floor.FloorPlanFloorID" />
                                                    Locked
                                                </label>
                                                <button class="btn btn-primary btn-sm w-100 mt-2" type="submit" form="floor-plan-transition-add-@floor.FloorPlanFloorID">Create transition</button>
                                            }
                                        </div>
                                    </div>
                                </div>
                                <div class="accordion-item">
                                    <h3 class="accordion-header" id="transition-existing-heading-@floor.FloorPlanFloorID">
                                        <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#transition-existing-@floor.FloorPlanFloorID" aria-expanded="false" aria-controls="transition-existing-@floor.FloorPlanFloorID">
                                            Existing transitions
                                        </button>
                                        <help-icon key="floorPlans.existingTransitions" mode="inline" />
                                    </h3>
                                    <div id="transition-existing-@floor.FloorPlanFloorID" class="accordion-collapse collapse" aria-labelledby="transition-existing-heading-@floor.FloorPlanFloorID" data-bs-parent="#transition-accordion-@floor.FloorPlanFloorID">
                                        <div class="accordion-body">
                                            @if (!floorTransitions.Any())
                                            {
                                                <p class="muted mb-0">No transitions on this floor yet.</p>
                                            }
                                            else
                                            {
                                                <div class="floor-plan-transition-list">
                                                    @foreach (var transition in floorTransitions)
                                                    {
                                                        <article class="floor-plan-transition-item">
                                                            <div>
                                                                <strong>@transition.FromLocationName -> @transition.ToLocationName</strong>
                                                                <span>@transition.TransitionType</span>
                                                                @if (!string.IsNullOrWhiteSpace(transition.DisplayLabel))
                                                                {
                                                                    <span>@transition.DisplayLabel</span>
                                                                }
                                                            </div>
                                                            <div class="button-row">
                                                                <button class="btn btn-outline-secondary btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#edit-transition-@transition.FloorPlanTransitionID">Edit</button>
                                                                <button class="btn btn-outline-danger btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#delete-transition-@transition.FloorPlanTransitionID">Delete</button>
                                                            </div>
                                                        </article>
                                                    }
                                                </div>
                                            }
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    }
                </section>

                <section class="floor-plan-tool-card" id="floor-plan-tool-floors" role="tabpanel" aria-labelledby="floor-plan-tool-tab-floors" data-toolbox-panel="floors" hidden>
                    <h2>Floors <help-icon key="floorPlans.creatingFloorsAndRooms" /></h2>
                    <div class="floor-plan-tool-subsection">
                        <h3>Add floor <help-icon key="floorPlans.addFloor" mode="inline" /></h3>
                        <input asp-for="NewFloor.FloorPlanID" type="hidden" name="FloorPlanID" form="floor-plan-add-floor-form" />
                        <div class="mb-2">
                            <label class="form-label">New floor <help-icon key="floorPlans.fields.floorName" mode="inline" /></label>
                            <input asp-for="NewFloor.Name" class="form-control form-control-sm" name="Name" form="floor-plan-add-floor-form" />
                        </div>
                        <div class="mb-2">
                            <label class="form-label">Level <help-icon key="floorPlans.fields.level" mode="inline" /></label>
                            <input asp-for="NewFloor.SortOrder" class="form-control form-control-sm" name="SortOrder" min="-10" max="50" step="1" form="floor-plan-add-floor-form" />
                            <div class="form-text">Used to order floors and later to connect stairs up/down.</div>
                        </div>
                        <div class="row g-2">
                            <div class="col-6">
                                <label class="form-label">Width <help-icon key="floorPlans.fields.gridWidth" mode="inline" /></label>
                                <input asp-for="NewFloor.GridWidth" class="form-control form-control-sm" name="GridWidth" min="8" max="80" form="floor-plan-add-floor-form" />
                            </div>
                            <div class="col-6">
                                <label class="form-label">Height <help-icon key="floorPlans.fields.gridHeight" mode="inline" /></label>
                                <input asp-for="NewFloor.GridHeight" class="form-control form-control-sm" name="GridHeight" min="8" max="80" form="floor-plan-add-floor-form" />
                            </div>
                        </div>
                        <button class="btn btn-outline-primary btn-sm w-100 mt-2" type="submit" form="floor-plan-add-floor-form">Add floor</button>
                    </div>
                    <div class="floor-plan-tool-subsection">
                        <h3>Current floor <help-icon key="floorPlans.creatingFloorsAndRooms" mode="inline" /></h3>
                        @for (var i = 0; i < Model.Floors.Count; i++)
                        {
                            var floor = Model.Floors[i];
                            var isActive = floor.FloorPlanFloorID == activeFloor?.FloorPlanFloorID;
                            <div class="floor-plan-floor-editor @(isActive ? string.Empty : "d-none")" data-floor-editor="@floor.FloorPlanFloorID">
                                <input asp-for="Floors[i].FloorPlanFloorID" type="hidden" />
                                <input asp-for="Floors[i].FloorPlanID" type="hidden" />
                                <div class="mb-2">
                                    <label class="form-label">Floor name <help-icon key="floorPlans.fields.floorName" mode="inline" /></label>
                                    <input asp-for="Floors[i].Name" class="form-control form-control-sm" />
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Description</label>
                                    <textarea asp-for="Floors[i].Description" class="form-control form-control-sm" rows="2"></textarea>
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Root Location <help-icon key="floorPlans.fields.rootLocation" mode="inline" /></label>
                                    <select asp-for="Floors[i].LocationID" asp-items="Model.LocationOptions" class="form-select form-select-sm" data-floor-location-select="@floor.FloorPlanFloorID"></select>
                                </div>
                                <div class="mb-2">
                                    <label class="form-label">Level <help-icon key="floorPlans.fields.level" mode="inline" /></label>
                                    <input asp-for="Floors[i].SortOrder" class="form-control form-control-sm" min="-10" max="50" step="1" data-floor-level-input="@floor.FloorPlanFloorID" />
                                    <div class="form-text">Used to order floors and later to connect stairs up/down.</div>
                                </div>
                                <p class="muted">Location: @(string.IsNullOrWhiteSpace(floor.LocationPath) ? "A child Location will be created or linked on save." : floor.LocationPath)</p>
                                <div class="row g-2">
                                    <div class="col-6">
                                        <label class="form-label">Grid width <help-icon key="floorPlans.fields.gridWidth" mode="inline" /></label>
                                        <input asp-for="Floors[i].GridWidth" class="form-control form-control-sm" min="8" max="80" data-floor-width="@floor.FloorPlanFloorID" />
                                    </div>
                                    <div class="col-6">
                                        <label class="form-label">Grid height <help-icon key="floorPlans.fields.gridHeight" mode="inline" /></label>
                                        <input asp-for="Floors[i].GridHeight" class="form-control form-control-sm" min="8" max="80" data-floor-height="@floor.FloorPlanFloorID" />
                                    </div>
                                </div>
                                <button class="btn btn-outline-danger btn-sm w-100 mt-2"
                                        type="button"
                                        data-bs-toggle="modal"
                                        data-bs-target="#delete-floor-@floor.FloorPlanFloorID">Delete floor</button>
                            </div>
                        }
                    </div>
                </section>
                    </div>
                </div>
            </aside>
        </div>

        <div class="button-row mt-3">
            <button class="btn btn-primary" type="submit" data-save-layout-button>Save layout</button>
        </div>
        </form>

        <form id="floor-plan-add-block-form" asp-action="AddBlock" method="post">
        <input type="hidden" name="floorPlanId" value="@Model.FloorPlan.FloorPlanID" />
        </form>

        <form id="floor-plan-add-floor-form" asp-action="AddFloor" method="post"></form>

        @foreach (var floor in Model.Floors)
        {
        <form id="floor-plan-background-upload-@floor.FloorPlanFloorID" asp-action="UploadBackground" method="post" enctype="multipart/form-data">
            <input type="hidden" name="floorPlanId" value="@Model.FloorPlan.FloorPlanID" />
            <input type="hidden" name="floorPlanFloorId" value="@floor.FloorPlanFloorID" />
        </form>

        <form id="floor-plan-background-settings-@floor.FloorPlanFloorID" asp-action="SaveBackgroundSettings" method="post" data-background-settings-form="@floor.FloorPlanFloorID">
            <input type="hidden" name="FloorPlanID" value="@Model.FloorPlan.FloorPlanID" />
            <input type="hidden" name="FloorPlanFloorID" value="@floor.FloorPlanFloorID" />
        </form>

        <form id="floor-plan-background-reset-@floor.FloorPlanFloorID" asp-action="ResetBackground" method="post">
            <input type="hidden" name="floorPlanId" value="@Model.FloorPlan.FloorPlanID" />
            <input type="hidden" name="floorPlanFloorId" value="@floor.FloorPlanFloorID" />
        </form>

        <form id="floor-plan-transition-add-@floor.FloorPlanFloorID" asp-action="SaveTransition" method="post">
            <input type="hidden" name="floorPlanId" value="@Model.FloorPlan.FloorPlanID" />
        </form>
        }

        @foreach (var floor in Model.Floors)
        {
        var placedBlockCount = Model.Blocks.Count(x => x.FloorPlanFloorID == floor.FloorPlanFloorID);
        <div class="modal fade" id="delete-floor-@floor.FloorPlanFloorID" tabindex="-1" aria-labelledby="delete-floor-title-@floor.FloorPlanFloorID" aria-hidden="true">
            <div class="modal-dialog modal-dialog-centered">
                <div class="modal-content plotline-confirm-modal">
                    <div class="modal-header">
                        <h2 class="modal-title fs-5" id="delete-floor-title-@floor.FloorPlanFloorID">Delete floor?</h2>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <p>Delete <strong>@floor.Name</strong>?</p>
                        <p>This deletes @placedBlockCount placed block(s) on this floor. Linked Locations will not be deleted.</p>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
                        <form asp-action="DeleteFloor" method="post" data-no-delete-confirm="true">
                            <input type="hidden" name="floorPlanId" value="@Model.FloorPlan.FloorPlanID" />
                            <input type="hidden" name="floorPlanFloorId" value="@floor.FloorPlanFloorID" />
                            <button class="btn btn-danger" type="submit">Delete floor</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
        }

        @foreach (var floor in Model.Floors.Where(x => !string.IsNullOrWhiteSpace(x.BackgroundImagePath)))
        {
        <div class="modal fade" id="remove-background-@floor.FloorPlanFloorID" tabindex="-1" aria-labelledby="remove-background-title-@floor.FloorPlanFloorID" aria-hidden="true">
            <div class="modal-dialog modal-dialog-centered">
                <div class="modal-content plotline-confirm-modal">
                    <div class="modal-header">
                        <h2 class="modal-title fs-5" id="remove-background-title-@floor.FloorPlanFloorID">Remove background?</h2>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <p>Remove the background image for <strong>@floor.Name</strong>?</p>
                        <p>This removes only the tracing image from this floor. Blocks and linked Locations will not be deleted.</p>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
                        <form asp-action="RemoveBackground" method="post" data-no-delete-confirm="true">
                            <input type="hidden" name="floorPlanId" value="@Model.FloorPlan.FloorPlanID" />
                            <input type="hidden" name="floorPlanFloorId" value="@floor.FloorPlanFloorID" />
                            <button class="btn btn-danger" type="submit">Remove background</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
        }

        @foreach (var transition in Model.Transitions)
        {
        var transitionLocationOptions = Model.Blocks
            .Where(x => x.LocationID.HasValue)
            .GroupBy(x => x.LocationID!.Value)
            .Select(x => x.First())
            .OrderBy(x => x.LocationName)
            .ToList();
        <div class="modal fade" id="edit-transition-@transition.FloorPlanTransitionID" tabindex="-1" aria-labelledby="edit-transition-title-@transition.FloorPlanTransitionID" aria-hidden="true">
            <div class="modal-dialog modal-dialog-centered">
                <div class="modal-content">
                    <div class="modal-header">
                        <h2 class="modal-title fs-5" id="edit-transition-title-@transition.FloorPlanTransitionID">Edit transition</h2>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <form asp-action="SaveTransition" method="post">
                        <div class="modal-body">
                            <input type="hidden" name="floorPlanId" value="@Model.FloorPlan.FloorPlanID" />
                            <input type="hidden" name="FloorPlanTransitionID" value="@transition.FloorPlanTransitionID" />
                            <input type="hidden" name="FloorPlanFloorID" value="@transition.FloorPlanFloorID" />
                            <div class="mb-2">
                                <label class="form-label">From Location</label>
                                <select class="form-select form-select-sm" name="FromLocationID">
                                    @foreach (var location in transitionLocationOptions)
                                    {
                                        <option value="@location.LocationID" selected="@(location.LocationID == transition.FromLocationID)">@location.LocationPath</option>
                                    }
                                </select>
                            </div>
                            <div class="mb-2">
                                <label class="form-label">To Location</label>
                                <select class="form-select form-select-sm" name="ToLocationID">
                                    @foreach (var location in transitionLocationOptions)
                                    {
                                        <option value="@location.LocationID" selected="@(location.LocationID == transition.ToLocationID)">@location.LocationPath</option>
                                    }
                                </select>
                            </div>
                            <div class="mb-2">
                                <label class="form-label">Transition Type</label>
                                <select class="form-select form-select-sm" name="TransitionType" data-edit-transition-type>
                                    @foreach (var option in Model.TransitionTypeOptions)
                                    {
                                        <option value="@option.Value" selected="@(option.Value == transition.TransitionType)">@option.Text</option>
                                    }
                                </select>
                            </div>
                            <div class="mb-2" data-edit-transition-position-panel>
                                <label class="form-label">Position Within Location</label>
                                <select class="form-select form-select-sm" name="PositionWithinLocation">
                                    @foreach (var option in transitionPositions)
                                    {
                                        <option value="@option.Value" selected="@(option.Value == transition.PositionWithinLocation)">@option.Text</option>
                                    }
                                </select>
                            </div>
                            <div class="mb-2">
                                <label class="form-label">Display Label</label>
                                <input class="form-control form-control-sm" name="DisplayLabel" value="@transition.DisplayLabel" />
                            </div>
                            <div class="mb-2">
                                <label class="form-label">Notes</label>
                                <textarea class="form-control form-control-sm" name="Notes" rows="3">@transition.Notes</textarea>
                            </div>
                            <label class="floor-plan-checkbox">
                                <input type="checkbox" name="IsLocked" value="true" @(transition.IsLocked ? "checked" : string.Empty) />
                                Locked
                            </label>
                        </div>
                        <div class="modal-footer">
                            <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
                            <button class="btn btn-primary" type="submit">Save transition</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>

        <div class="modal fade" id="delete-transition-@transition.FloorPlanTransitionID" tabindex="-1" aria-labelledby="delete-transition-title-@transition.FloorPlanTransitionID" aria-hidden="true">
            <div class="modal-dialog modal-dialog-centered">
                <div class="modal-content plotline-confirm-modal">
                    <div class="modal-header">
                        <h2 class="modal-title fs-5" id="delete-transition-title-@transition.FloorPlanTransitionID">Delete transition?</h2>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <p>Delete the <strong>@transition.TransitionType</strong> transition from <strong>@transition.FromLocationName</strong> to <strong>@transition.ToLocationName</strong>?</p>
                        <p>Locations and blocks will not be deleted.</p>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
                        <form asp-action="DeleteTransition" method="post" data-no-delete-confirm="true">
                            <input type="hidden" name="floorPlanId" value="@Model.FloorPlan.FloorPlanID" />
                            <input type="hidden" name="floorPlanTransitionId" value="@transition.FloorPlanTransitionID" />
                            <button class="btn btn-danger" type="submit">Delete transition</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
        }

        @foreach (var block in Model.Blocks)
        {
        <div class="modal fade" id="delete-block-@block.FloorPlanBlockID" tabindex="-1" aria-labelledby="delete-block-title-@block.FloorPlanBlockID" aria-hidden="true">
            <div class="modal-dialog modal-dialog-centered">
                <div class="modal-content plotline-confirm-modal">
                    <div class="modal-header">
                        <h2 class="modal-title fs-5" id="delete-block-title-@block.FloorPlanBlockID">Delete placed block?</h2>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <p>Remove the placed block for <strong>@block.DisplayLabel</strong>?</p>
                        <p>Only this block is removed from the floor plan. The linked Location will not be deleted.</p>
                    </div>
                    <div class="modal-footer">
                        <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
                        <form asp-action="DeleteBlock" method="post" data-no-delete-confirm="true">
                            <input type="hidden" name="floorPlanId" value="@Model.FloorPlan.FloorPlanID" />
                            <input type="hidden" name="floorPlanBlockId" value="@block.FloorPlanBlockID" />
                            <button class="btn btn-danger" type="submit">Delete block</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
        }
    }
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
    <script>
        window.initializeFloorPlanEditor = (options = {}) => {
            const editor = document.querySelector("[data-floor-plan-editor]");
            if (!editor) return;
            if (editor.dataset.floorPlanInitialized === "true") return;
            editor.dataset.floorPlanInitialized = "true";
            const workspace = editor.closest("[data-floor-plan-workspace]");

            const tabs = [...editor.querySelectorAll("[data-floor-tab]")];
            const panels = [...editor.querySelectorAll("[data-floor-panel]")];
            const floorEditors = [...editor.querySelectorAll("[data-floor-editor]")];
            const backgroundEditors = [...editor.querySelectorAll("[data-background-editor]")];
            const transitionEditors = [...editor.querySelectorAll("[data-transition-editor]")];
            let blocks = [...editor.querySelectorAll("[data-block]")];
            const noSelection = editor.querySelector("[data-no-selection]");
            const warning = editor.querySelector("[data-overlap-warning]");
            const addBlockFloor = document.querySelector("[data-add-block-floor-id]");
            const addBlockFloorNames = [...document.querySelectorAll("[data-current-floor-name]")];
            const addBlockLocation = document.querySelector("[data-add-block-location]");
            const addBlockModes = [...document.querySelectorAll("[data-add-block-mode]")];
            const addBlockType = document.querySelector("#add-block-type");
            const existingLocationPanel = document.querySelector("[data-existing-location-panel]");
            const newLocationPanel = document.querySelector("[data-new-location-panel]");
            const sizePreset = document.querySelector("[data-size-preset]");
            const addBlockWidth = document.querySelector("[data-add-block-width]");
            const addBlockHeight = document.querySelector("[data-add-block-height]");
            const addBlockForm = document.querySelector("#floor-plan-add-block-form");
            const toolboxTabs = [...editor.querySelectorAll("[data-toolbox-tab]")];
            const toolboxPanels = [...editor.querySelectorAll("[data-toolbox-panel]")];
            const saveStatus = editor.querySelector("[data-save-status]");
            const saveButtons = [...editor.querySelectorAll("[data-save-layout-button]")];
            const gridCoordinatesToggle = editor.querySelector("[data-grid-coordinates-toggle]");
            const referenceFloorSelect = editor.querySelector("[data-reference-floor-select]");
            const modeButtons = [...editor.querySelectorAll("[data-floor-plan-mode]")];
            const occupancyControls = [...editor.querySelectorAll("[data-occupancy-controls]")];
            const occupancySceneSelect = editor.querySelector("[data-occupancy-scene-select]");
            const occupancyPrev = editor.querySelector("[data-occupancy-prev]");
            const occupancyNext = editor.querySelector("[data-occupancy-next]");
            const occupancyTrays = [...editor.querySelectorAll("[data-occupancy-tray]")];
            const occupancyMessages = [...editor.querySelectorAll("[data-occupancy-message]")];
            const occupancyWarningPanel = editor.querySelector("[data-occupancy-warning-panel]");
            const occupancyWarnings = [...editor.querySelectorAll("[data-occupancy-warning]")];
            const initialSelectedBlockId = options.selectedBlockId || "@selectedBlockId";
            const saveStatusText = {
                saved: "Saved",
                dirty: "Unsaved changes",
                saving: "Saving...",
                failed: "Save failed"
            };
            const sizePresets = {
                Room: [
                    ["1x1", "Small Room 1 x 1"],
                    ["2x1", "Medium Room 2 x 1"],
                    ["2x2", "Large Room 2 x 2"],
                    ["3x1", "Long Room 3 x 1"],
                    ["3x2", "Grand Room 3 x 2"]
                ],
                Corridor: [
                    ["1x2", "Short Corridor 1 x 2"],
                    ["1x4", "Long Corridor 1 x 4"],
                    ["2x3", "Wide Corridor 2 x 3"],
                    ["4x1", "Horizontal Corridor 4 x 1"],
                    ["5x1", "Gallery 5 x 1"]
                ],
                OpenSpace: [
                    ["4x4", "Courtyard 4 x 4"],
                    ["6x5", "Large Courtyard 6 x 5"],
                    ["6x4", "Garden Area 6 x 4"],
                    ["2x2", "Lightwell 2 x 2"]
                ],
                Exterior: [
                    ["3x3", "Yard 3 x 3"],
                    ["4x2", "Drive 4 x 2"],
                    ["6x4", "Garden 6 x 4"],
                    ["4x1", "Terrace 4 x 1"]
                ],
                Other: [
                    ["1x1", "Small 1 x 1"],
                    ["2x1", "Medium 2 x 1"],
                    ["2x2", "Large 2 x 2"],
                    ["3x2", "Wide 3 x 2"]
                ]
            };
            let selectedBlock = null;
            let pointerState = null;
            let saveTimer = null;
            let saveInFlight = false;
            let saveAgain = false;
            let refreshAfterSave = false;
            let geometryRefreshQueued = false;
            const backgroundTimers = new Map();
            const gridCoordinatesStorageKey = "floorPlan.showGridCoordinates";
            const referenceFloorStorageKey = "floorPlan.referenceFloor";

            function setToolboxTab(tabName) {
                toolboxTabs.forEach(tab => {
                    const active = tab.dataset.toolboxTab === tabName;
                    tab.classList.toggle("active", active);
                    tab.setAttribute("aria-selected", active ? "true" : "false");
                });
                toolboxPanels.forEach(panel => {
                    const active = panel.dataset.toolboxPanel === tabName;
                    panel.classList.toggle("active", active);
                    panel.hidden = !active;
                });
            }

            function setSaveStatus(state, message) {
                if (!saveStatus) return;
                saveStatus.dataset.saveState = state;
                saveStatus.textContent = message || saveStatusText[state] || state;
            }

            function selectedOccupancySceneId() {
                return occupancySceneSelect?.value || "";
            }

            function refreshOccupancyTrays() {
                const sceneId = selectedOccupancySceneId();
                const showOccupancy = editor.classList.contains("floor-plan-editor--occupancy");
                const activeFloorId = tabs.find(tab => tab.classList.contains("active"))?.dataset.floorTab || "";
                occupancyTrays.forEach(tray => {
                    tray.hidden = !showOccupancy || tray.dataset.occupancyScene !== sceneId || tray.dataset.occupancyFloor !== activeFloorId;
                });
                occupancyMessages.forEach(message => {
                    message.hidden = !showOccupancy || message.dataset.occupancyScene !== sceneId || message.dataset.occupancyFloor !== activeFloorId;
                });
                let visibleWarnings = 0;
                occupancyWarnings.forEach(warning => {
                    const warningFloorId = warning.dataset.occupancyFloor || "";
                    const visible = showOccupancy
                        && warning.dataset.occupancyScene === sceneId
                        && (!warningFloorId || warningFloorId === activeFloorId);
                    warning.hidden = !visible;
                    if (visible) visibleWarnings++;
                });
                if (occupancyWarningPanel) {
                    occupancyWarningPanel.hidden = !showOccupancy || visibleWarnings === 0;
                }
            }

            function setFloorPlanMode(mode) {
                const isOccupancy = mode === "occupancy";
                editor.classList.toggle("floor-plan-editor--occupancy", isOccupancy);
                modeButtons.forEach(button => {
                    const active = button.dataset.floorPlanMode === mode;
                    button.classList.toggle("active", active);
                    button.setAttribute("aria-pressed", String(active));
                });
                occupancyControls.forEach(control => {
                    control.hidden = !isOccupancy;
                });
                saveButtons.forEach(button => {
                    button.hidden = isOccupancy;
                });
                if (isOccupancy) {
                    pointerState = null;
                    blocks.forEach(item => item.classList.remove("selected"));
                    if (noSelection) noSelection.classList.remove("d-none");
                }
                refreshOccupancyTrays();
            }

            function moveOccupancyScene(direction) {
                if (!occupancySceneSelect || occupancySceneSelect.options.length === 0) {
                    return;
                }

                const nextIndex = Math.max(0, Math.min(occupancySceneSelect.options.length - 1, occupancySceneSelect.selectedIndex + direction));
                occupancySceneSelect.selectedIndex = nextIndex;
                applyOccupancySceneFloor();
                refreshOccupancyTrays();
            }

            function applyOccupancySceneFloor() {
                const option = occupancySceneSelect?.selectedOptions?.[0];
                const initialFloorId = option?.dataset.initialFloorId;
                if (initialFloorId) {
                    setActiveFloor(initialFloorId);
                }
            }

            function gridColumnLabel(index) {
                let label = "";
                let value = index + 1;
                while (value > 0) {
                    value--;
                    label = String.fromCharCode(65 + (value % 26)) + label;
                    value = Math.floor(value / 26);
                }
                return label;
            }

            function populateCoordinateSelect(select, count, labelForValue, selectedValue) {
                if (!select) return;
                const selected = String(Math.max(0, Math.min(Number(selectedValue || 0), Math.max(count - 1, 0))));
                const existingValues = [...select.options].map(option => option.value).join("|");
                const nextValues = Array.from({ length: Math.max(count, 1) }, (_, index) => String(index)).join("|");
                if (existingValues !== nextValues) {
                    select.replaceChildren(...Array.from({ length: Math.max(count, 1) }, (_, index) => {
                        const option = document.createElement("option");
                        option.value = String(index);
                        option.textContent = labelForValue(index);
                        return option;
                    }));
                }
                select.value = selected;
                select.dataset.coordinateValue = selected;
            }

            function refreshGridCoordinateLabels() {
                const show = gridCoordinatesToggle?.checked === true;
                panels.forEach(panel => {
                    const grid = panel.querySelector("[data-floor-grid]");
                    if (!grid) return;
                    grid.classList.toggle("show-grid-coordinates", show);
                    grid.querySelectorAll("[data-grid-coordinate-label]").forEach(label => label.remove());
                    if (!show) return;

                    const width = Number(grid.dataset.gridWidth || 0);
                    const height = Number(grid.dataset.gridHeight || 0);
                    for (let x = 0; x < width; x++) {
                        const label = document.createElement("span");
                        label.dataset.gridCoordinateLabel = "column";
                        label.className = "floor-plan-coordinate-label floor-plan-coordinate-label--column";
                        label.style.setProperty("--coord-x", x);
                        label.textContent = gridColumnLabel(x);
                        grid.append(label);
                    }
                    for (let y = 0; y < height; y++) {
                        const label = document.createElement("span");
                        label.dataset.gridCoordinateLabel = "row";
                        label.className = "floor-plan-coordinate-label floor-plan-coordinate-label--row";
                        label.style.setProperty("--coord-y", y);
                        label.textContent = String(y + 1);
                        grid.append(label);
                    }
                });
            }

            function floorForLevel(currentFloorId, offset) {
                const current = tabs.find(tab => tab.dataset.floorTab === String(currentFloorId));
                if (!current) return null;
                const targetLevel = Number(current.dataset.floorOrder || 0) + offset;
                return tabs.find(tab => Number(tab.dataset.floorOrder || 0) === targetLevel) || null;
            }

            function referenceFloorForActivePanel() {
                const activePanel = panels.find(panel => panel.classList.contains("active"));
                if (!activePanel || !referenceFloorSelect || referenceFloorSelect.value === "none") return null;
                return floorForLevel(activePanel.dataset.floorPanel, referenceFloorSelect.value === "above" ? 1 : -1);
            }

            function refreshReferenceFloorOptions() {
                if (!referenceFloorSelect) return;
                const activePanel = panels.find(panel => panel.classList.contains("active"));
                const belowOption = [...referenceFloorSelect.options].find(option => option.value === "below");
                const aboveOption = [...referenceFloorSelect.options].find(option => option.value === "above");
                const belowFloor = activePanel ? floorForLevel(activePanel.dataset.floorPanel, -1) : null;
                const aboveFloor = activePanel ? floorForLevel(activePanel.dataset.floorPanel, 1) : null;
                if (belowOption) {
                    belowOption.disabled = !belowFloor;
                    belowOption.textContent = belowFloor ? `Floor below (${belowFloor.textContent.trim()})` : "No floor below";
                }
                if (aboveOption) {
                    aboveOption.disabled = !aboveFloor;
                    aboveOption.textContent = aboveFloor ? `Floor above (${aboveFloor.textContent.trim()})` : "No floor above";
                }
                if (referenceFloorSelect.selectedOptions[0]?.disabled) {
                    referenceFloorSelect.value = "none";
                    window.localStorage?.setItem(referenceFloorStorageKey, "none");
                }
            }

            function refreshReferenceOverlay() {
                refreshReferenceFloorOptions();
                panels.forEach(panel => {
                    panel.querySelectorAll("[data-reference-floor-overlay], [data-reference-grid-warning]").forEach(item => item.remove());
                });

                const activePanel = panels.find(panel => panel.classList.contains("active"));
                const grid = activePanel?.querySelector("[data-floor-grid]");
                const referenceFloor = referenceFloorForActivePanel();
                if (!activePanel || !grid || !referenceFloor) return;

                const referenceGrid = editor.querySelector(`[data-floor-grid="${referenceFloor.dataset.floorTab}"]`);
                const overlay = document.createElement("div");
                overlay.className = "floor-plan-reference-overlay";
                overlay.dataset.referenceFloorOverlay = referenceFloor.dataset.floorTab;
                overlay.setAttribute("aria-hidden", "true");
                blockItemsForFloor(referenceFloor.dataset.floorTab).forEach(item => {
                    const block = document.createElement("span");
                    block.className = "floor-plan-reference-block";
                    block.style.setProperty("--x", item.x);
                    block.style.setProperty("--y", item.y);
                    block.style.setProperty("--w", item.w);
                    block.style.setProperty("--h", item.h);
                    block.textContent = item.block.dataset.locationName || item.block.textContent.trim();
                    overlay.append(block);
                });
                grid.prepend(overlay);

                if (referenceGrid && (referenceGrid.dataset.gridWidth !== grid.dataset.gridWidth || referenceGrid.dataset.gridHeight !== grid.dataset.gridHeight)) {
                    const warning = document.createElement("div");
                    warning.className = "floor-plan-reference-warning";
                    warning.dataset.referenceGridWarning = "true";
                    warning.textContent = "Reference floor uses a different grid size.";
                    grid.append(warning);
                }
            }

            function markDirty() {
                setSaveStatus("dirty");
                window.clearTimeout(saveTimer);
                saveTimer = window.setTimeout(() => saveLayoutAsync(), 1000);
            }

            function workspaceState(overrides = {}) {
                const activePanel = panels.find(panel => panel.classList.contains("active"));
                const gridWrap = activePanel?.querySelector(".floor-plan-grid-wrap");
                const toolboxBody = editor.querySelector(".floor-plan-toolbox-body");
                return {
                    activeFloorId: activePanel?.dataset.floorPanel || tabs.find(tab => tab.classList.contains("active"))?.dataset.floorTab || "",
                    activeToolboxTab: toolboxTabs.find(tab => tab.classList.contains("active"))?.dataset.toolboxTab || "add",
                    selectedBlockId: selectedBlock?.dataset.block || "",
                    floorPlanMode: modeButtons.find(button => button.classList.contains("active"))?.dataset.floorPlanMode || "structure",
                    occupancySceneId: selectedOccupancySceneId(),
                    pageX: window.scrollX,
                    pageY: window.scrollY,
                    gridLeft: gridWrap?.scrollLeft || 0,
                    gridTop: gridWrap?.scrollTop || 0,
                    toolboxTop: toolboxBody?.scrollTop || 0,
                    ...overrides
                };
            }

            function showWorkspaceError(message) {
                setSaveStatus("failed", message || "Save failed");
                let alert = editor.querySelector("[data-workspace-error]");
                if (!alert) {
                    alert = document.createElement("div");
                    alert.className = "alert alert-danger";
                    alert.dataset.workspaceError = "true";
                    editor.prepend(alert);
                }
                alert.textContent = message || "Save failed";
            }

            function clearWorkspaceError() {
                editor.querySelector("[data-workspace-error]")?.remove();
            }

            function cleanupModalArtifacts() {
                document.querySelectorAll(".modal-backdrop").forEach(backdrop => backdrop.remove());
                document.body.classList.remove("modal-open");
                document.body.style.removeProperty("overflow");
                document.body.style.removeProperty("padding-right");
            }

            function restoreWorkspaceState(state) {
                if (state.activeFloorId) {
                    setActiveFloor(String(state.activeFloorId));
                }
                if (!panels.some(panel => panel.classList.contains("active")) && tabs[0]) {
                    setActiveFloor(tabs[0].dataset.floorTab);
                }
                if (state.activeToolboxTab) {
                    setToolboxTab(state.activeToolboxTab);
                }
                if (state.occupancySceneId && occupancySceneSelect) {
                    occupancySceneSelect.value = String(state.occupancySceneId);
                }
                if (state.floorPlanMode) {
                    setFloorPlanMode(state.floorPlanMode);
                }
                if (state.selectedBlockId) {
                    const block = blocks.find(item => item.dataset.block === String(state.selectedBlockId));
                    if (block) {
                        selectBlock(block, { showEditor: state.activeToolboxTab === "selected" });
                    }
                }

                const activePanel = panels.find(panel => panel.classList.contains("active"));
                const gridWrap = activePanel?.querySelector(".floor-plan-grid-wrap");
                const toolboxBody = editor.querySelector(".floor-plan-toolbox-body");
                if (gridWrap) {
                    gridWrap.scrollLeft = state.gridLeft || 0;
                    gridWrap.scrollTop = state.gridTop || 0;
                }
                if (toolboxBody) {
                    toolboxBody.scrollTop = state.toolboxTop || 0;
                }
                window.scrollTo(state.pageX || 0, state.pageY || 0);
                document.documentElement.scrollTop = state.pageY || 0;
                document.body.scrollTop = state.pageY || 0;
            }

            async function refreshWorkspaceAsync(state) {
                const floorPlanId = workspace?.dataset.floorPlanId;
                if (!workspace || !floorPlanId) return;

                const response = await fetch(`/FloorPlans/Edit/${floorPlanId}`, {
                    method: "GET",
                    headers: { "X-Requested-With": "XMLHttpRequest" },
                    credentials: "same-origin"
                });
                const html = await response.text();
                if (!response.ok) {
                    throw new Error("The floor plan workspace could not be refreshed.");
                }

                const doc = new DOMParser().parseFromString(html, "text/html");
                const nextWorkspace = doc.querySelector("[data-floor-plan-workspace]");
                if (!nextWorkspace) {
                    throw new Error("The refreshed floor plan workspace was not found.");
                }

                cleanupModalArtifacts();
                workspace.replaceWith(nextWorkspace);
                window.initializeFloorPlanEditor({
                    activeFloorId: state.activeFloorId,
                    activeToolboxTab: state.activeToolboxTab,
                    selectedBlockId: state.selectedBlockId,
                    floorPlanMode: state.floorPlanMode,
                    occupancySceneId: state.occupancySceneId
                });
                const nextEditor = document.querySelector("[data-floor-plan-editor]");
                const nextStatus = nextEditor?.querySelector("[data-save-status]");
                if (nextStatus) {
                    nextStatus.dataset.saveState = "saved";
                    nextStatus.textContent = "Saved";
                }
                window.requestAnimationFrame(() => {
                    const refreshedEditor = document.querySelector("[data-floor-plan-editor]");
                    if (!refreshedEditor) return;
                    refreshedEditor.dispatchEvent(new CustomEvent("floor-plan-restore-state", { detail: state }));
                });
            }

            async function saveLayoutAsync() {
                window.clearTimeout(saveTimer);
                if (saveInFlight) {
                    saveAgain = true;
                    return;
                }

                saveInFlight = true;
                saveButtons.forEach(button => button.disabled = true);
                setSaveStatus("saving");
                try {
                    const response = await fetch(editor.action, {
                        method: "POST",
                        body: new FormData(editor),
                        headers: {
                            "Accept": "application/json",
                            "X-Requested-With": "XMLHttpRequest"
                        },
                        credentials: "same-origin"
                    });
                    const result = await response.json().catch(() => ({}));
                    if (!response.ok || result.ok === false) {
                        throw new Error(result.message || "Unable to save layout.");
                    }

                    clearWorkspaceError();
                    setSaveStatus("saved");
                    if (refreshAfterSave) {
                        refreshAfterSave = false;
                        await refreshWorkspaceAsync(workspaceState());
                    }
                } catch (error) {
                    setSaveStatus("failed", error.message || "Save failed");
                    showWorkspaceError(error.message || "Save failed");
                } finally {
                    saveInFlight = false;
                    saveButtons.forEach(button => button.disabled = false);
                    if (saveAgain) {
                        saveAgain = false;
                        markDirty();
                    }
                }
            }

            function updateFloorGrid(floorId) {
                const grid = editor.querySelector(`[data-floor-grid="${floorId}"]`);
                const widthInput = editor.querySelector(`[data-floor-width="${floorId}"]`);
                const heightInput = editor.querySelector(`[data-floor-height="${floorId}"]`);
                if (!grid || !widthInput || !heightInput) return;

                const width = Math.max(8, Math.min(Number(widthInput.value || 24), 80));
                const height = Math.max(8, Math.min(Number(heightInput.value || 16), 80));
                widthInput.value = width;
                heightInput.value = height;
                grid.dataset.gridWidth = width;
                grid.dataset.gridHeight = height;
                grid.style.setProperty("--grid-width", width);
                grid.style.setProperty("--grid-height", height);

                grid.querySelectorAll("[data-block]").forEach(block => writeBlock(block, readBlock(block)));
                checkOverlaps();
                refreshGridCoordinateLabels();
                refreshReferenceOverlay();
                refreshTransitionDestination(floorId);
            }

            function setActiveFloor(floorId) {
                tabs.forEach(tab => tab.classList.toggle("active", tab.dataset.floorTab === floorId));
                panels.forEach(panel => panel.classList.toggle("active", panel.dataset.floorPanel === floorId));
                floorEditors.forEach(panel => panel.classList.toggle("d-none", panel.dataset.floorEditor !== floorId));
                backgroundEditors.forEach(panel => {
                    const active = panel.dataset.backgroundEditor === floorId;
                    panel.classList.toggle("d-none", !active);
                    panel.hidden = !active;
                });
                transitionEditors.forEach(panel => {
                    const active = panel.dataset.transitionEditor === floorId;
                    panel.classList.toggle("d-none", !active);
                    panel.hidden = !active;
                });
                const activeTab = tabs.find(tab => tab.dataset.floorTab === floorId);
                if (addBlockFloor) addBlockFloor.value = floorId;
                if (activeTab) {
                    addBlockFloorNames.forEach(item => item.textContent = activeTab.dataset.floorName || activeTab.textContent || "the current floor");
                }
                filterAddBlockLocations(activeTab?.dataset.floorLocationId || "");
                checkOverlaps();
                refreshReferenceOverlay();
                refreshTransitionDestination(floorId);
                refreshOccupancyTrays();
            }

            function backgroundFields(floorId) {
                const panel = editor.querySelector(`[data-background-editor="${floorId}"]`);
                return {
                    form: document.querySelector(`[data-background-settings-form="${floorId}"]`),
                    image: editor.querySelector(`[data-background-image="${floorId}"]`),
                    opacity: panel?.querySelector("[data-background-field='opacity']"),
                    scale: panel?.querySelector("[data-background-field='scale']"),
                    offsetX: panel?.querySelector("[data-background-field='offsetX']"),
                    offsetY: panel?.querySelector("[data-background-field='offsetY']"),
                    locked: panel?.querySelector("[data-background-field='locked']"),
                    opacityValue: editor.querySelector(`[data-background-opacity-value="${floorId}"]`)
                };
            }

            function updateBackgroundPreview(floorId) {
                const fields = backgroundFields(floorId);
                if (!fields.image) return;
                const opacity = Number(fields.opacity?.value || 0.35);
                const scale = Number(fields.scale?.value || 1);
                const offsetX = Number(fields.offsetX?.value || 0);
                const offsetY = Number(fields.offsetY?.value || 0);
                fields.image.style.setProperty("--bg-opacity", Math.max(0.05, Math.min(opacity, 1)));
                fields.image.style.setProperty("--bg-scale", Math.max(0.1, Math.min(scale, 5)));
                fields.image.style.setProperty("--bg-offset-x", `${Math.max(-10000, Math.min(offsetX, 10000))}px`);
                fields.image.style.setProperty("--bg-offset-y", `${Math.max(-10000, Math.min(offsetY, 10000))}px`);
                fields.image.classList.toggle("is-locked", fields.locked?.checked !== false);
                if (fields.opacityValue) fields.opacityValue.textContent = fields.opacity?.value || "0.35";
            }

            function markBackgroundDirty(floorId) {
                updateBackgroundPreview(floorId);
                setSaveStatus("dirty");
                window.clearTimeout(backgroundTimers.get(floorId));
                backgroundTimers.set(floorId, window.setTimeout(() => saveBackgroundSettingsAsync(floorId), 1000));
            }

            async function saveBackgroundSettingsAsync(floorId) {
                const fields = backgroundFields(floorId);
                if (!fields.form) return;
                window.clearTimeout(backgroundTimers.get(floorId));
                setSaveStatus("saving");
                try {
                    const response = await fetch(fields.form.action, {
                        method: "POST",
                        body: new FormData(fields.form),
                        headers: {
                            "Accept": "application/json",
                            "X-Requested-With": "XMLHttpRequest"
                        },
                        credentials: "same-origin"
                    });
                    const result = await response.json().catch(() => ({}));
                    if (!response.ok || result.ok === false) {
                        throw new Error(result.message || "Unable to save background settings.");
                    }

                    setSaveStatus("saved");
                } catch (error) {
                    setSaveStatus("failed", error.message || "Save failed");
                }
            }

            function filterAddBlockLocations(floorLocationId) {
                if (!addBlockLocation) return;
                let firstValue = "";
                [...addBlockLocation.options].forEach(option => {
                    if (!option.value) {
                        option.hidden = false;
                        option.disabled = false;
                        return;
                    }
                    const matches = option.dataset.parentLocationId === floorLocationId;
                    option.hidden = !matches;
                    option.disabled = !matches;
                    if (matches && !firstValue) firstValue = option.value;
                });
                if (!addBlockLocation.value || addBlockLocation.selectedOptions[0]?.disabled) {
                    addBlockLocation.value = firstValue;
                }
            }

            function updateAddBlockMode() {
                const selectedMode = addBlockModes.find(input => input.checked)?.value || "Existing";
                const isCreate = selectedMode === "Create";
                existingLocationPanel?.classList.toggle("d-none", isCreate);
                newLocationPanel?.classList.toggle("d-none", !isCreate);
            }

            function attachBlockEvents(block) {
                block.addEventListener("pointerdown", event => {
                    if (editor.classList.contains("floor-plan-editor--occupancy")) return;
                    selectBlock(block);
                    const grid = gridFor(block);
                    if (!grid) return;
                    const start = cellFromPointer(grid, event);
                    const current = readBlock(block);
                    const resizing = event.target.matches("[data-resize-handle]");
                    pointerState = { block, grid, start, current, resizing };
                    block.setPointerCapture(event.pointerId);
                    event.preventDefault();
                });
            }

            function appendHidden(panel, name, value, field) {
                const input = document.createElement("input");
                input.type = "hidden";
                input.name = name;
                input.value = value ?? "";
                if (field) input.dataset.blockField = field;
                panel.append(input);
                return input;
            }

            function createBlockEditor(block) {
                const selectedPanel = editor.querySelector("#floor-plan-tool-selected");
                if (!selectedPanel) return null;

                const index = editor.querySelectorAll("[data-block-editor]").length;
                const panel = document.createElement("div");
                panel.className = "floor-plan-block-editor d-none";
                panel.dataset.blockEditor = block.floorPlanBlockID;

                appendHidden(panel, `Blocks[${index}].FloorPlanBlockID`, block.floorPlanBlockID);
                appendHidden(panel, `Blocks[${index}].FloorPlanFloorID`, block.floorPlanFloorID, "floor");
                appendHidden(panel, `Blocks[${index}].LocationName`, block.locationName || "");
                appendHidden(panel, `Blocks[${index}].LocationPath`, block.locationPath || "");

                const locationTemplate = editor.querySelector("[data-block-field='location']");
                const typeTemplate = editor.querySelector("[data-block-field='type']");

                const locationWrap = document.createElement("div");
                locationWrap.className = "mb-2";
                locationWrap.innerHTML = `<label class="form-label">Location</label>`;
                const locationSelect = locationTemplate?.cloneNode(true) || document.createElement("select");
                locationSelect.name = `Blocks[${index}].LocationID`;
                locationSelect.dataset.blockField = "location";
                locationSelect.value = block.locationID || "";
                locationSelect.addEventListener("change", () => {
                    if (!selectedBlock) return;
                    selectedBlock.dataset.locationId = locationSelect.value || "";
                    checkOverlaps();
                    refreshTransitionDestination(selectedBlock.dataset.floorId);
                    markDirty();
                });
                locationWrap.append(locationSelect);
                panel.append(locationWrap);

                const typeWrap = document.createElement("div");
                typeWrap.className = "mb-2";
                typeWrap.innerHTML = `<label class="form-label">Type</label>`;
                const typeSelect = typeTemplate?.cloneNode(true) || document.createElement("select");
                typeSelect.name = `Blocks[${index}].BlockType`;
                typeSelect.dataset.blockField = "type";
                typeSelect.value = block.blockType || "Room";
                typeSelect.addEventListener("change", () => {
                    if (!selectedBlock) return;
                    selectedBlock.className = selectedBlock.className
                        .split(" ")
                        .filter(name => !name.startsWith("floor-plan-block--"))
                        .concat(`floor-plan-block--${typeSelect.value.toLowerCase()}`)
                        .join(" ");
                    markDirty();
                });
                typeWrap.append(typeSelect);
                panel.append(typeWrap);

                const gridFields = document.createElement("div");
                gridFields.className = "row g-2";
                [
                    ["X", "x", block.x, "X"],
                    ["Y", "y", block.y, "Y"],
                    ["Width", "w", block.widthCells, "WidthCells"],
                    ["Height", "h", block.heightCells, "HeightCells"]
                ].forEach(([label, field, value, property]) => {
                    const wrap = document.createElement("div");
                    wrap.className = "col-6";
                    wrap.innerHTML = `<label class="form-label">${label}</label>`;
                    const input = field === "x" || field === "y" ? document.createElement("select") : document.createElement("input");
                    input.className = field === "x" || field === "y" ? "form-select form-select-sm" : "form-control form-control-sm";
                    if (input instanceof HTMLInputElement) {
                        input.type = "number";
                        input.min = "1";
                        input.max = "80";
                    } else {
                        input.dataset.coordinateAxis = field;
                        input.dataset.coordinateValue = value;
                    }
                    input.name = `Blocks[${index}].${property}`;
                    input.value = value;
                    input.dataset.blockField = field;
                    const updateBlockField = () => {
                        if (!selectedBlock) return;
                        writeBlock(selectedBlock, readBlock(selectedBlock));
                        markDirty();
                    };
                    input.addEventListener("input", updateBlockField);
                    input.addEventListener("change", updateBlockField);
                    wrap.append(input);
                    gridFields.append(wrap);
                });
                panel.append(gridFields);

                const labelWrap = document.createElement("div");
                labelWrap.className = "mt-2";
                labelWrap.innerHTML = `<label class="form-label">Label override</label>`;
                const labelInput = document.createElement("input");
                labelInput.className = "form-control form-control-sm";
                labelInput.name = `Blocks[${index}].LabelOverride`;
                labelInput.value = block.labelOverride || "";
                labelInput.addEventListener("input", markDirty);
                labelWrap.append(labelInput);
                panel.append(labelWrap);

                const notesWrap = document.createElement("div");
                notesWrap.className = "mt-2";
                notesWrap.innerHTML = `<label class="form-label">Notes</label>`;
                const notesInput = document.createElement("textarea");
                notesInput.className = "form-control form-control-sm";
                notesInput.name = `Blocks[${index}].Notes`;
                notesInput.rows = 3;
                notesInput.value = block.notes || "";
                notesInput.addEventListener("input", markDirty);
                notesWrap.append(notesInput);
                panel.append(notesWrap);

                selectedPanel.append(panel);
                return panel;
            }

            function addBlockToGrid(block) {
                const grid = editor.querySelector(`[data-floor-grid="${block.floorPlanFloorID}"]`);
                if (!grid) return null;

                const button = document.createElement("button");
                button.type = "button";
                button.className = `floor-plan-block floor-plan-block--${(block.blockType || "Room").toLowerCase()}`;
                button.style.setProperty("--x", block.x);
                button.style.setProperty("--y", block.y);
                button.style.setProperty("--w", block.widthCells);
                button.style.setProperty("--h", block.heightCells);
                button.dataset.block = block.floorPlanBlockID;
                button.dataset.blockIndex = blocks.length;
                button.dataset.floorId = block.floorPlanFloorID;
                button.dataset.locationId = block.locationID || "";
                button.dataset.locationName = block.locationName || "";
                button.dataset.locationPath = block.locationPath || "";
                button.dataset.locationTitle = block.locationPath || "";
                button.title = block.locationPath || "";
                button.innerHTML = `<span class="floor-plan-block-label"></span><span class="floor-plan-resize-handle" data-resize-handle aria-hidden="true"></span>`;
                button.querySelector(".floor-plan-block-label").textContent = block.displayLabel || block.locationName || "New block";
                grid.append(button);

                blocks.push(button);
                attachBlockEvents(button);
                createBlockEditor(block);
                writeBlock(button, readBlock(button));
                checkOverlaps();
                return button;
            }

            async function addBlockAsync(event) {
                event.preventDefault();
                if (!addBlockForm) return;
                if (document.activeElement instanceof HTMLElement) {
                    document.activeElement.blur();
                }

                const scrollState = workspaceState();
                setSaveStatus("saving");
                try {
                    const response = await fetch(addBlockForm.action, {
                        method: "POST",
                        body: new FormData(addBlockForm),
                        headers: {
                            "Accept": "application/json",
                            "X-Requested-With": "XMLHttpRequest"
                        },
                        credentials: "same-origin"
                    });
                    const result = await response.json().catch(() => ({}));
                    if (!response.ok || result.ok === false || !result.block) {
                        throw new Error(result.message || "Unable to add block.");
                    }

                    scrollState.selectedBlockId = result.block.floorPlanBlockID;
                    await refreshWorkspaceAsync(scrollState);
                } catch (error) {
                    setSaveStatus("failed", error.message || "Save failed");
                }
            }

            function selectBlock(block, options = {}) {
                const showEditor = options.showEditor !== false;
                selectedBlock = block;
                blocks.forEach(item => item.classList.toggle("selected", item === block));
                editor.querySelectorAll("[data-block-editor]").forEach(panel => panel.classList.add("d-none"));
                const panel = editor.querySelector(`[data-block-editor="${block.dataset.block}"]`);
                if (panel) panel.classList.remove("d-none");
                if (noSelection) noSelection.classList.add("d-none");
                if (showEditor) {
                    setToolboxTab("selected");
                }
            }

            function fieldsFor(block) {
                const panel = editor.querySelector(`[data-block-editor="${block.dataset.block}"]`);
                return {
                    panel,
                    floor: panel?.querySelector("[data-block-field='floor']"),
                    x: panel?.querySelector("[data-block-field='x']"),
                    y: panel?.querySelector("[data-block-field='y']"),
                    w: panel?.querySelector("[data-block-field='w']"),
                    h: panel?.querySelector("[data-block-field='h']"),
                    location: panel?.querySelector("[data-block-field='location']"),
                    type: panel?.querySelector("[data-block-field='type']")
                };
            }

            function gridFor(block) {
                return editor.querySelector(`[data-floor-grid="${block.dataset.floorId}"]`);
            }

            function clampBlock(block, values) {
                const grid = gridFor(block);
                const gridWidth = Number(grid?.dataset.gridWidth || 24);
                const gridHeight = Number(grid?.dataset.gridHeight || 16);
                const w = Math.max(1, Math.min(Number(values.w), gridWidth));
                const h = Math.max(1, Math.min(Number(values.h), gridHeight));
                const x = Math.max(0, Math.min(Number(values.x), gridWidth - w));
                const y = Math.max(0, Math.min(Number(values.y), gridHeight - h));
                return { x, y, w, h };
            }

            function readBlock(block) {
                const fields = fieldsFor(block);
                return {
                    x: Number(fields.x?.value || fields.x?.dataset.coordinateValue || block.style.getPropertyValue("--x") || 0),
                    y: Number(fields.y?.value || fields.y?.dataset.coordinateValue || block.style.getPropertyValue("--y") || 0),
                    w: Number(fields.w?.value || block.style.getPropertyValue("--w") || 1),
                    h: Number(fields.h?.value || block.style.getPropertyValue("--h") || 1)
                };
            }

            function writeBlock(block, values) {
                const next = clampBlock(block, values);
                const grid = gridFor(block);
                const gridWidth = Number(grid?.dataset.gridWidth || 24);
                const gridHeight = Number(grid?.dataset.gridHeight || 16);
                const maxXOptions = Math.max(gridWidth - next.w + 1, 1);
                const maxYOptions = Math.max(gridHeight - next.h + 1, 1);
                block.style.setProperty("--x", next.x);
                block.style.setProperty("--y", next.y);
                block.style.setProperty("--w", next.w);
                block.style.setProperty("--h", next.h);
                const fields = fieldsFor(block);
                populateCoordinateSelect(fields.x, maxXOptions, gridColumnLabel, next.x);
                populateCoordinateSelect(fields.y, maxYOptions, value => String(value + 1), next.y);
                if (fields.w) fields.w.value = next.w;
                if (fields.h) fields.h.value = next.h;
                scheduleGeometryRefresh();
            }

            function scheduleGeometryRefresh() {
                if (geometryRefreshQueued) return;
                geometryRefreshQueued = true;
                window.requestAnimationFrame(() => {
                    geometryRefreshQueued = false;
                    checkOverlaps();
                });
            }

            function rangesOverlap(aStart, aLength, bStart, bLength) {
                return aStart < bStart + bLength && bStart < aStart + aLength;
            }

            function normalizeTransitionType(type) {
                return (type || "").toLowerCase().replace(/[^a-z0-9]/g, "");
            }

            function isStairTransition(type) {
                return normalizeTransitionType(type) === "stairsup" || normalizeTransitionType(type) === "stairsdown";
            }

            function isLadderTransition(type) {
                return ["ladder", "ladders"].includes(normalizeTransitionType(type));
            }

            function isInsideLocationTransition(type) {
                return isStairTransition(type) || isLadderTransition(type);
            }

            function isUnrestrictedTransition(type) {
                return ["hiddenpassage", "openpassage", "secretpassage"].includes(normalizeTransitionType(type));
            }

            function isSecretRouteTransition(type) {
                return ["hiddenpassage", "secretpassage"].includes(normalizeTransitionType(type));
            }

            function isVisibleEntranceTransition(type) {
                return [
                    "door",
                    "doubledoor",
                    "frenchdoor",
                    "frenchdoors",
                    "slidingdoor",
                    "slidingdoors",
                    "exteriordoor",
                    "archway",
                    "openpassage",
                    "stairsup",
                    "stairsdown"
                ].includes(normalizeTransitionType(type));
            }

            function blocksTouchAlongEdge(a, b) {
                const verticalTouch = a.x + a.w === b.x || b.x + b.w === a.x;
                const verticalOverlap = a.y < b.y + b.h && b.y < a.y + a.h;
                const horizontalTouch = a.y + a.h === b.y || b.y + b.h === a.y;
                const horizontalOverlap = a.x < b.x + b.w && b.x < a.x + a.w;
                return (verticalTouch && verticalOverlap) || (horizontalTouch && horizontalOverlap);
            }

            function blocksOverlapArea(a, b) {
                return a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
            }

            function blockItemsForFloor(floorId) {
                return [...editor.querySelectorAll(`[data-block][data-floor-id="${floorId}"]`)].map(block => ({ block, ...readBlock(block) }));
            }

            function floorNameFor(floorId) {
                const tab = tabs.find(item => item.dataset.floorTab === String(floorId));
                return tab?.dataset.floorName || tab?.textContent.trim() || "Floor";
            }

            function locationOptionsFromBlocks(blockItems) {
                const byLocation = new Map();
                blockItems.forEach(item => {
                    const id = item.block.dataset.locationId;
                    if (!id || byLocation.has(id)) return;
                    byLocation.set(id, {
                        id,
                        label: item.block.dataset.locationPath || item.block.dataset.locationName || item.block.textContent.trim()
                    });
                });
                return [...byLocation.values()].sort((a, b) => a.label.localeCompare(b.label));
            }

            function ladderLocationOptions(currentFloorId, fromLocationId) {
                const currentFloor = tabs.find(tab => tab.dataset.floorTab === String(currentFloorId));
                const currentLevel = Number(currentFloor?.dataset.floorOrder || 0);
                const floorsAbove = tabs
                    .filter(tab => Number(tab.dataset.floorOrder || 0) > currentLevel)
                    .sort((a, b) => Number(a.dataset.floorOrder || 0) - Number(b.dataset.floorOrder || 0)
                        || (a.dataset.floorName || a.textContent.trim()).localeCompare(b.dataset.floorName || b.textContent.trim()));

                const options = [];
                floorsAbove.forEach(floor => {
                    locationOptionsFromBlocks(blockItemsForFloor(floor.dataset.floorTab))
                        .filter(option => option.id !== fromLocationId)
                        .forEach(option => {
                            const floorName = floorNameFor(floor.dataset.floorTab);
                            const parts = option.label.split(" > ").map(part => part.trim()).filter(Boolean);
                            const floorIndex = parts.lastIndexOf(floorName);
                            const locationLabel = floorIndex >= 0 && parts.length > floorIndex + 1
                                ? parts.slice(floorIndex + 1).join(" > ")
                                : parts[parts.length - 1] || option.label;
                            options.push({
                                id: option.id,
                                label: `${floorName} > ${locationLabel}`,
                                level: Number(floor.dataset.floorOrder || 0),
                                floorName
                            });
                        });
                });

                return {
                    floorsAbove,
                    options: options.sort((a, b) => a.level - b.level
                        || a.floorName.localeCompare(b.floorName)
                        || a.label.localeCompare(b.label))
                };
            }

            function setTransitionOptions(select, options, messageElement, emptyMessage) {
                const previous = select.value;
                select.innerHTML = "";
                const placeholder = document.createElement("option");
                placeholder.value = "";
                placeholder.textContent = options.length ? "Choose a Location" : emptyMessage;
                select.append(placeholder);
                options.forEach(option => {
                    const element = document.createElement("option");
                    element.value = option.id;
                    element.textContent = option.label;
                    element.selected = option.id === previous;
                    select.append(element);
                });
                select.disabled = options.length === 0;
                if (messageElement) {
                    messageElement.textContent = options.length ? "" : emptyMessage;
                }
            }

            function refreshTransitionDestination(floorId, showAllStairTargets = false) {
                const typeSelect = editor.querySelector(`[data-transition-type-select="${floorId}"]`);
                const fromSelect = editor.querySelector(`[data-transition-from-select="${floorId}"]`);
                const toSelect = editor.querySelector(`[data-transition-to-select="${floorId}"]`);
                const message = editor.querySelector(`[data-transition-to-message="${floorId}"]`);
                const positionPanel = editor.querySelector(`[data-transition-position-panel="${floorId}"]`);
                const fallbackButton = editor.querySelector(`[data-transition-stair-fallback="${floorId}"]`);
                if (!typeSelect || !fromSelect || !toSelect) return;

                const type = typeSelect.value;
                const fromLocationId = fromSelect.value;
                const stair = isStairTransition(type);
                const ladder = isLadderTransition(type);
                positionPanel?.classList.toggle("d-none", !isInsideLocationTransition(type));
                fallbackButton?.classList.add("d-none");
                if (!fromLocationId) {
                    setTransitionOptions(toSelect, [], message, "Choose a From Location first.");
                    return;
                }

                let options = [];
                let emptyMessage = "No available locations.";
                if (stair) {
                    const currentFloor = tabs.find(tab => tab.dataset.floorTab === String(floorId));
                    const currentLevel = Number(currentFloor?.dataset.floorOrder || 0);
                    const isUp = normalizeTransitionType(type) === "stairsup";
                    const targetLevel = currentLevel + (isUp ? 1 : -1);
                    const targetFloor = tabs.find(tab => Number(tab.dataset.floorOrder || 0) === targetLevel);
                    emptyMessage = isUp
                        ? "No floor exists above this level."
                        : "No floor exists below this level.";
                    if (targetFloor) {
                        const fromBlocks = blockItemsForFloor(floorId)
                            .filter(item => item.block.dataset.locationId === fromLocationId);
                        const targetBlocks = blockItemsForFloor(targetFloor.dataset.floorTab);
                        const overlappingLocationIds = new Set();
                        if (!showAllStairTargets) {
                            targetBlocks.forEach(target => {
                                const targetLocationId = target.block.dataset.locationId;
                                if (!targetLocationId || targetLocationId === fromLocationId) return;
                                if (fromBlocks.some(from => blocksOverlapArea(from, target))) {
                                    overlappingLocationIds.add(targetLocationId);
                                }
                            });
                        }

                        options = locationOptionsFromBlocks(targetBlocks)
                            .filter(option => option.id !== fromLocationId)
                            .filter(option => showAllStairTargets || overlappingLocationIds.has(option.id));
                        emptyMessage = isUp
                            ? "No locations directly above this location."
                            : "No locations directly below this location.";
                        if (!options.length && locationOptionsFromBlocks(targetBlocks).some(option => option.id !== fromLocationId)) {
                            fallbackButton?.classList.remove("d-none");
                            if (fallbackButton) {
                                fallbackButton.textContent = isUp
                                    ? "Show all locations on floor above"
                                    : "Show all locations on floor below";
                            }
                        }
                    }
                } else if (ladder) {
                    const ladderTargets = ladderLocationOptions(floorId, fromLocationId);
                    options = ladderTargets.options;
                    emptyMessage = ladderTargets.floorsAbove.length
                        ? "No placed locations exist on floors above."
                        : "No floors above this level.";
                } else if (isUnrestrictedTransition(type)) {
                    options = locationOptionsFromBlocks([...editor.querySelectorAll("[data-block]")].map(block => ({ block, ...readBlock(block) })))
                        .filter(option => option.id !== fromLocationId);
                } else {
                    const floorBlocks = blockItemsForFloor(floorId);
                    const fromBlocks = floorBlocks.filter(item => item.block.dataset.locationId === fromLocationId);
                    const adjacentIds = new Set();
                    fromBlocks.forEach(from => {
                        floorBlocks.forEach(to => {
                            const toLocationId = to.block.dataset.locationId;
                            if (!toLocationId || toLocationId === fromLocationId) return;
                            if (blocksTouchAlongEdge(from, to)) adjacentIds.add(toLocationId);
                        });
                    });
                    options = locationOptionsFromBlocks(floorBlocks).filter(option => adjacentIds.has(option.id));
                    emptyMessage = "No adjacent locations are available.";
                }

                setTransitionOptions(toSelect, options, message, emptyMessage);
            }

            function subtractRange(segments, start, end) {
                return segments.flatMap(segment => {
                    const clippedStart = Math.max(segment.start, start);
                    const clippedEnd = Math.min(segment.end, end);
                    if (clippedStart >= clippedEnd) return [segment];

                    return [
                        { start: segment.start, end: clippedStart },
                        { start: clippedEnd, end: segment.end }
                    ].filter(next => next.end > next.start);
                });
            }

            function renderWallSegment(block, side, start, end, total) {
                if (end <= start || total <= 0) return;

                const segment = document.createElement("span");
                segment.dataset.wallSegment = side;
                segment.className = `floor-plan-wall-segment floor-plan-wall-segment--${side}`;
                segment.style.setProperty("--wall-start", `${(start / total) * 100}%`);
                segment.style.setProperty("--wall-length", `${((end - start) / total) * 100}%`);
                block.append(segment);
            }

            function visibleEdgeSegments(item, visibleBlocks, side) {
                const axisStart = side === "east" || side === "west" ? item.y : item.x;
                const axisLength = side === "east" || side === "west" ? item.h : item.w;
                let segments = [{ start: 0, end: axisLength }];

                visibleBlocks.forEach(other => {
                    if (item === other || !item.block.dataset.locationId || item.block.dataset.locationId !== other.block.dataset.locationId) return;

                    let touches = false;
                    let overlapStart = 0;
                    let overlapEnd = 0;

                    if (side === "east" && item.x + item.w === other.x) {
                        touches = true;
                        overlapStart = Math.max(item.y, other.y) - axisStart;
                        overlapEnd = Math.min(item.y + item.h, other.y + other.h) - axisStart;
                    } else if (side === "west" && other.x + other.w === item.x) {
                        touches = true;
                        overlapStart = Math.max(item.y, other.y) - axisStart;
                        overlapEnd = Math.min(item.y + item.h, other.y + other.h) - axisStart;
                    } else if (side === "south" && item.y + item.h === other.y) {
                        touches = true;
                        overlapStart = Math.max(item.x, other.x) - axisStart;
                        overlapEnd = Math.min(item.x + item.w, other.x + other.w) - axisStart;
                    } else if (side === "north" && other.y + other.h === item.y) {
                        touches = true;
                        overlapStart = Math.max(item.x, other.x) - axisStart;
                        overlapEnd = Math.min(item.x + item.w, other.x + other.w) - axisStart;
                    }

                    if (touches && overlapStart < overlapEnd) {
                        segments = subtractRange(segments, overlapStart, overlapEnd);
                    }
                });

                return segments;
            }

            function refreshWallSegments(visibleBlocks) {
                visibleBlocks.forEach(item => {
                    item.block.querySelectorAll("[data-wall-segment]").forEach(segment => segment.remove());
                    ["north", "east", "south", "west"].forEach(side => {
                        const total = side === "east" || side === "west" ? item.h : item.w;
                        visibleEdgeSegments(item, visibleBlocks, side).forEach(segment => {
                            renderWallSegment(item.block, side, segment.start, segment.end, total);
                        });
                    });
                });
            }

            function refreshSharedWalls(activePanel) {
                const visibleBlocks = [...activePanel.querySelectorAll("[data-block]")].map(block => ({ block, ...readBlock(block) }));
                refreshWallSegments(visibleBlocks);
                return visibleBlocks;
            }

            function locationCenter(blocksForLocation) {
                if (blocksForLocation.length === 0) return null;
                const minX = Math.min(...blocksForLocation.map(item => item.x));
                const minY = Math.min(...blocksForLocation.map(item => item.y));
                const maxX = Math.max(...blocksForLocation.map(item => item.x + item.w));
                const maxY = Math.max(...blocksForLocation.map(item => item.y + item.h));
                return { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };
            }

            function refreshHiddenLocationStyles(visibleBlocks) {
                const visibleEntranceLocationIds = new Set();
                editor.querySelectorAll("[data-transition-marker]").forEach(marker => {
                    if (!isVisibleEntranceTransition(marker.dataset.transitionKind)) return;
                    if (marker.dataset.fromLocationId) visibleEntranceLocationIds.add(marker.dataset.fromLocationId);
                    if (marker.dataset.toLocationId) visibleEntranceLocationIds.add(marker.dataset.toLocationId);
                });

                visibleBlocks.forEach(item => {
                    const locationId = item.block.dataset.locationId;
                    const isHidden = !!locationId && !visibleEntranceLocationIds.has(locationId);
                    item.block.classList.toggle("is-hidden-location", isHidden);
                    const baseTitle = item.block.dataset.locationTitle || item.block.dataset.locationPath || item.block.dataset.locationName || "";
                    item.block.title = isHidden
                        ? `${baseTitle}${baseTitle ? "\n" : ""}Hidden location: no visible entrance`
                        : baseTitle;
                });
            }

            function renderSecretRoute(activePanel, marker, fromCenter, toCenter) {
                if (!fromCenter || !toCenter) return;
                const dx = toCenter.x - fromCenter.x;
                const dy = toCenter.y - fromCenter.y;
                const length = Math.sqrt(dx * dx + dy * dy);
                if (length <= 0) return;

                const route = document.createElement("span");
                route.className = "floor-plan-secret-route";
                route.dataset.secretRoute = marker.dataset.transitionMarker || "";
                route.style.setProperty("--route-x", fromCenter.x);
                route.style.setProperty("--route-y", fromCenter.y);
                route.style.setProperty("--route-length", length);
                route.style.setProperty("--route-angle", `${Math.atan2(dy, dx)}rad`);
                route.title = marker.title || "";
                route.setAttribute("aria-hidden", "true");
                activePanel.querySelector("[data-floor-grid]")?.append(route);
            }

            function sharedTransitionEdge(fromBlocks, toBlocks) {
                let best = null;
                fromBlocks.forEach(from => {
                    toBlocks.forEach(to => {
                        if (from.x + from.w === to.x || to.x + to.w === from.x) {
                            const start = Math.max(from.y, to.y);
                            const end = Math.min(from.y + from.h, to.y + to.h);
                            const length = end - start;
                            if (length > 0 && (!best || length > best.length)) {
                                best = {
                                    length,
                                    orientation: "vertical",
                                    start,
                                    end,
                                    x: from.x + from.w === to.x ? to.x : from.x,
                                    y: start + length / 2
                                };
                            }
                        }

                        if (from.y + from.h === to.y || to.y + to.h === from.y) {
                            const start = Math.max(from.x, to.x);
                            const end = Math.min(from.x + from.w, to.x + to.w);
                            const length = end - start;
                            if (length > 0 && (!best || length > best.length)) {
                                best = {
                                    length,
                                    orientation: "horizontal",
                                    start,
                                    end,
                                    x: start + length / 2,
                                    y: from.y + from.h === to.y ? to.y : from.y
                                };
                            }
                        }
                    });
                });
                return best;
            }

            function positionWithinLocation(blocksForLocation, position) {
                const center = locationCenter(blocksForLocation);
                if (!center) return null;

                const minX = Math.min(...blocksForLocation.map(item => item.x));
                const minY = Math.min(...blocksForLocation.map(item => item.y));
                const maxX = Math.max(...blocksForLocation.map(item => item.x + item.w));
                const maxY = Math.max(...blocksForLocation.map(item => item.y + item.h));
                const inset = 0.35;
                const normalized = (position || "Centre").toLowerCase().replace(/\s+/g, "");
                const x = normalized.includes("west") ? minX + inset : normalized.includes("east") ? maxX - inset : center.x;
                const y = normalized.includes("north") ? minY + inset : normalized.includes("south") ? maxY - inset : center.y;
                return { x, y };
            }

            function positionTransitions(activePanel, visibleBlocks) {
                const markerPositions = [];
                activePanel.querySelectorAll("[data-secret-route]").forEach(route => route.remove());
                activePanel.querySelectorAll("[data-transition-marker]").forEach(marker => {
                    const fromBlocks = visibleBlocks.filter(item => item.block.dataset.locationId === marker.dataset.fromLocationId);
                    const toBlocks = visibleBlocks.filter(item => item.block.dataset.locationId === marker.dataset.toLocationId);
                    const fromCenter = locationCenter(fromBlocks);
                    const toCenter = locationCenter(toBlocks);

                    if (isSecretRouteTransition(marker.dataset.transitionKind)) {
                        marker.hidden = true;
                        if (fromCenter && toCenter) {
                            renderSecretRoute(activePanel, marker, fromCenter, toCenter);
                        }
                        return;
                    }

                    if (fromBlocks.length === 0 || (!isInsideLocationTransition(marker.dataset.transitionKind) && toBlocks.length === 0)) {
                        marker.hidden = true;
                        return;
                    }

                    const insidePlacement = isInsideLocationTransition(marker.dataset.transitionKind)
                        ? positionWithinLocation(fromBlocks, marker.dataset.positionWithinLocation)
                        : null;
                    const sharedEdge = insidePlacement ? null : sharedTransitionEdge(fromBlocks, toBlocks);
                    marker.hidden = false;
                    marker.classList.toggle("is-shared-edge", !!sharedEdge);
                    marker.classList.toggle("is-inside-location", !!insidePlacement);
                    marker.classList.toggle("is-horizontal", sharedEdge?.orientation === "horizontal");
                    marker.classList.toggle("is-vertical", sharedEdge?.orientation === "vertical");
                    const pairKey = [marker.dataset.fromLocationId, marker.dataset.toLocationId].sort().join("-");
                    const key = insidePlacement
                        ? `${pairKey}:inside:${normalizeTransitionType(marker.dataset.transitionKind)}:${marker.dataset.positionWithinLocation || "Centre"}:${insidePlacement.x}:${insidePlacement.y}`
                        : sharedEdge
                        ? `${pairKey}:${sharedEdge.orientation}:${sharedEdge.x}:${sharedEdge.y}:${sharedEdge.start}:${sharedEdge.end}`
                        : `${pairKey}:fallback`;
                    markerPositions.push({ marker, insidePlacement, sharedEdge, fromCenter, toCenter, key });
                });

                const groups = markerPositions.reduce((map, item) => {
                    if (!map.has(item.key)) map.set(item.key, []);
                    map.get(item.key).push(item);
                    return map;
                }, new Map());

                groups.forEach(group => {
                    group.forEach((item, index) => {
                        const fraction = (index + 1) / (group.length + 1);
                        let x;
                        let y;
                        if (item.insidePlacement) {
                            x = item.insidePlacement.x;
                            y = item.insidePlacement.y;
                        } else if (item.sharedEdge?.orientation === "vertical") {
                            x = item.sharedEdge.x;
                            y = item.sharedEdge.start + item.sharedEdge.length * fraction;
                        } else if (item.sharedEdge?.orientation === "horizontal") {
                            x = item.sharedEdge.start + item.sharedEdge.length * fraction;
                            y = item.sharedEdge.y;
                        } else {
                            x = item.fromCenter.x + (item.toCenter.x - item.fromCenter.x) * fraction;
                            y = item.fromCenter.y + (item.toCenter.y - item.fromCenter.y) * fraction;
                        }

                        item.marker.style.setProperty("--transition-x", x);
                        item.marker.style.setProperty("--transition-y", y);
                    });
                });
            }

            function cellFromPointer(grid, event) {
                const rect = grid.getBoundingClientRect();
                const gridWidth = Number(grid.dataset.gridWidth || 24);
                const gridHeight = Number(grid.dataset.gridHeight || 16);
                return {
                    x: Math.floor((event.clientX - rect.left) / (rect.width / gridWidth)),
                    y: Math.floor((event.clientY - rect.top) / (rect.height / gridHeight))
                };
            }

            function checkOverlaps() {
                const activePanel = panels.find(panel => panel.classList.contains("active"));
                if (!activePanel) return;
                const visibleBlocks = refreshSharedWalls(activePanel);
                positionTransitions(activePanel, visibleBlocks);
                refreshHiddenLocationStyles(visibleBlocks);
                let overlaps = false;
                visibleBlocks.forEach(a => {
                    a.block.classList.remove("overlap");
                    visibleBlocks.forEach(b => {
                        if (a === b) return;
                        const separated = a.x + a.w <= b.x || b.x + b.w <= a.x || a.y + a.h <= b.y || b.y + b.h <= a.y;
                        if (!separated) {
                            overlaps = true;
                            a.block.classList.add("overlap");
                            b.block.classList.add("overlap");
                        }
                    });
                });
                warning?.classList.toggle("d-none", !overlaps);
            }

            function applySizePreset() {
                const [width, height] = (sizePreset?.value || "2x1").split("x");
                if (addBlockWidth) addBlockWidth.value = width || "2";
                if (addBlockHeight) addBlockHeight.value = height || "1";
            }

            function updateSizePresets() {
                if (!sizePreset) return;
                const presets = sizePresets[addBlockType?.value || "Room"] || sizePresets.Room;
                sizePreset.innerHTML = "";
                presets.forEach(([value, label], index) => {
                    const option = document.createElement("option");
                    option.value = value;
                    option.textContent = label;
                    option.selected = index === 0;
                    sizePreset.append(option);
                });
                applySizePreset();
            }

            tabs.forEach(tab => tab.addEventListener("click", () => setActiveFloor(tab.dataset.floorTab)));
            toolboxTabs.forEach(tab => tab.addEventListener("click", () => setToolboxTab(tab.dataset.toolboxTab)));
            modeButtons.forEach(button => button.addEventListener("click", () => setFloorPlanMode(button.dataset.floorPlanMode)));
            occupancySceneSelect?.addEventListener("change", () => {
                applyOccupancySceneFloor();
                refreshOccupancyTrays();
            });
            occupancyPrev?.addEventListener("click", () => moveOccupancyScene(-1));
            occupancyNext?.addEventListener("click", () => moveOccupancyScene(1));
            editor.querySelectorAll("[data-transition-type-select],[data-transition-from-select]").forEach(input => {
                const floorId = input.dataset.transitionTypeSelect || input.dataset.transitionFromSelect;
                input.addEventListener("change", () => refreshTransitionDestination(floorId));
                refreshTransitionDestination(floorId);
            });
            editor.querySelectorAll("[data-transition-stair-fallback]").forEach(button => {
                button.addEventListener("click", () => refreshTransitionDestination(button.dataset.transitionStairFallback, true));
            });
            if (gridCoordinatesToggle) {
                gridCoordinatesToggle.checked = window.localStorage?.getItem(gridCoordinatesStorageKey) === "true";
                gridCoordinatesToggle.addEventListener("change", () => {
                    window.localStorage?.setItem(gridCoordinatesStorageKey, gridCoordinatesToggle.checked ? "true" : "false");
                    refreshGridCoordinateLabels();
                });
            }
            if (referenceFloorSelect) {
                const storedReferenceFloor = window.localStorage?.getItem(referenceFloorStorageKey);
                if (storedReferenceFloor && [...referenceFloorSelect.options].some(option => option.value === storedReferenceFloor)) {
                    referenceFloorSelect.value = storedReferenceFloor;
                }
                referenceFloorSelect.addEventListener("change", () => {
                    window.localStorage?.setItem(referenceFloorStorageKey, referenceFloorSelect.value);
                    refreshReferenceOverlay();
                });
            }
            document.querySelectorAll("[data-edit-transition-type]").forEach(select => {
                const panel = select.closest(".modal-body")?.querySelector("[data-edit-transition-position-panel]");
                const refreshPositionPanel = () => panel?.classList.toggle("d-none", !isInsideLocationTransition(select.value));
                select.addEventListener("change", refreshPositionPanel);
                refreshPositionPanel();
            });
            addBlockForm?.addEventListener("submit", addBlockAsync);
            editor.addEventListener("floor-plan-restore-state", event => restoreWorkspaceState(event.detail || {}));
            workspace?.addEventListener("submit", async event => {
                if (event.defaultPrevented) return;
                const form = event.target;
                if (!(form instanceof HTMLFormElement)) return;
                if (form === editor || form.id === "floor-plan-add-block-form" || form.matches("[data-background-settings-form]")) return;

                event.preventDefault();
                const state = workspaceState();
                setSaveStatus("saving");
                try {
                    const response = await fetch(form.action, {
                        method: (form.method || "POST").toUpperCase(),
                        body: new FormData(form),
                        credentials: "same-origin"
                    });

                    if (!response.ok) {
                        throw new Error("Save failed");
                    }

                    const html = await response.text();
                    const doc = new DOMParser().parseFromString(html, "text/html");
                    const nextWorkspace = doc.querySelector("[data-floor-plan-workspace]");
                    if (!nextWorkspace) {
                        throw new Error("The floor plan workspace could not be refreshed.");
                    }

                    cleanupModalArtifacts();
                    workspace.replaceWith(nextWorkspace);
                    window.initializeFloorPlanEditor({
                        activeFloorId: state.activeFloorId,
                        activeToolboxTab: state.activeToolboxTab,
                        selectedBlockId: state.selectedBlockId,
                        floorPlanMode: state.floorPlanMode,
                        occupancySceneId: state.occupancySceneId
                    });
                    window.requestAnimationFrame(() => {
                        const refreshedEditor = document.querySelector("[data-floor-plan-editor]");
                        refreshedEditor?.dispatchEvent(new CustomEvent("floor-plan-restore-state", { detail: state }));
                    });
                } catch (error) {
                    showWorkspaceError(error.message || "Save failed");
                }
            });

            editor.addEventListener("submit", event => {
                event.preventDefault();
                saveLayoutAsync();
            });

            blocks.forEach(block => {
                attachBlockEvents(block);
                writeBlock(block, readBlock(block));
            });

            editor.addEventListener("pointermove", event => {
                if (!pointerState) return;
                if (editor.classList.contains("floor-plan-editor--occupancy")) {
                    pointerState = null;
                    return;
                }
                const currentCell = cellFromPointer(pointerState.grid, event);
                const dx = currentCell.x - pointerState.start.x;
                const dy = currentCell.y - pointerState.start.y;
                if (pointerState.resizing) {
                    writeBlock(pointerState.block, {
                        ...pointerState.current,
                        w: pointerState.current.w + dx,
                        h: pointerState.current.h + dy
                    });
                } else {
                    writeBlock(pointerState.block, {
                        ...pointerState.current,
                        x: pointerState.current.x + dx,
                        y: pointerState.current.y + dy
                    });
                }
            });

            editor.addEventListener("pointerup", () => {
                if (pointerState) {
                    const floorId = pointerState.block.dataset.floorId;
                    checkOverlaps();
                    markDirty();
                    refreshTransitionDestination(floorId);
                }
                pointerState = null;
            });
            editor.addEventListener("pointercancel", () => {
                if (pointerState) {
                    const floorId = pointerState.block.dataset.floorId;
                    checkOverlaps();
                    markDirty();
                    refreshTransitionDestination(floorId);
                }
                pointerState = null;
            });

            editor.querySelectorAll("[data-block-field='x'],[data-block-field='y'],[data-block-field='w'],[data-block-field='h']").forEach(input => {
                const updateBlockField = () => {
                    if (!selectedBlock) return;
                    writeBlock(selectedBlock, readBlock(selectedBlock));
                    markDirty();
                };
                input.addEventListener("input", updateBlockField);
                input.addEventListener("change", updateBlockField);
            });

            editor.querySelectorAll("[data-block-field='type']").forEach(select => {
                select.addEventListener("change", () => {
                    if (!selectedBlock) return;
                    selectedBlock.className = selectedBlock.className
                        .split(" ")
                        .filter(name => !name.startsWith("floor-plan-block--"))
                        .concat(`floor-plan-block--${select.value.toLowerCase()}`)
                        .join(" ");
                    markDirty();
                });
            });

            editor.querySelectorAll("[data-block-field='location']").forEach(select => {
                select.addEventListener("change", () => {
                    if (!selectedBlock) return;
                    selectedBlock.dataset.locationId = select.value || "";
                    checkOverlaps();
                    refreshTransitionDestination(selectedBlock.dataset.floorId);
                    markDirty();
                });
            });

            editor.querySelectorAll("[data-floor-width],[data-floor-height]").forEach(input => {
                input.addEventListener("input", () => {
                    const floorId = input.dataset.floorWidth || input.dataset.floorHeight;
                    updateFloorGrid(floorId);
                    markDirty();
                });
                input.addEventListener("change", () => {
                    const floorId = input.dataset.floorWidth || input.dataset.floorHeight;
                    updateFloorGrid(floorId);
                    markDirty();
                });
            });

            editor.querySelectorAll("[data-floor-level-input]").forEach(input => {
                const updateFloorLevel = (clampValue = false) => {
                    const parsed = Number(input.value);
                    if (!Number.isFinite(parsed)) return;
                    if (clampValue && Number.isFinite(parsed)) {
                        input.value = Math.max(-10, Math.min(parsed, 50));
                    }
                    refreshAfterSave = true;
                    markDirty();
                };
                input.addEventListener("input", () => updateFloorLevel());
                input.addEventListener("change", () => updateFloorLevel(true));
            });

            editor.querySelectorAll("[data-floor-location-select]").forEach(input => {
                input.addEventListener("change", () => {
                    refreshAfterSave = true;
                    markDirty();
                });
            });

            editor.querySelectorAll("input, select, textarea").forEach(input => {
                if (input.matches("[form='floor-plan-add-block-form'], [form='floor-plan-add-floor-form']")) return;
                if (input.matches("[form^='floor-plan-background-'], [form^='floor-plan-transition-add-']")) return;
                if (input.matches("[data-background-field]")) return;
                if (input.matches("[data-floor-width],[data-floor-height],[data-floor-level-input]")) return;
                if (input.matches("[data-block-field='x'],[data-block-field='y'],[data-block-field='w'],[data-block-field='h'],[data-block-field='type'],[data-block-field='location']")) return;
                const eventName = input.tagName === "TEXTAREA" || input.type === "text" ? "input" : "change";
                input.addEventListener(eventName, markDirty);
            });

            editor.querySelectorAll("[data-background-field]").forEach(input => {
                const eventName = input.type === "checkbox" ? "change" : "input";
                input.addEventListener(eventName, () => markBackgroundDirty(input.dataset.backgroundFloor));
            });

            sizePreset?.addEventListener("change", applySizePreset);
            addBlockType?.addEventListener("change", updateSizePresets);
            updateSizePresets();
            addBlockModes.forEach(input => input.addEventListener("change", updateAddBlockMode));
            updateAddBlockMode();

            const initialBlock = blocks.find(block => block.dataset.block === initialSelectedBlockId);
            if (initialBlock) {
                setActiveFloor(initialBlock.dataset.floorId);
                selectBlock(initialBlock);
            } else if (tabs[0]) {
                setActiveFloor(tabs[0].dataset.floorTab);
            }
            refreshGridCoordinateLabels();
            refreshReferenceOverlay();
            refreshOccupancyTrays();
            checkOverlaps();
        };

        window.initializeFloorPlanEditor();
    </script>
}
