{#
/**
 * OTS Signs — Layout Designer Page
 *
 * Overrides the Xibo core layout-designer-page.twig to add two tailored
 * editor experiences alongside the standard full-page mode.
 *
 * Usage:
 *   Normal:    /layout/designer/{layoutId}
 *   Deep-link: /layout/designer/{layoutId}?deeplink=1[&returnUrl=/app/layouts/123]
 *   Embed:     /layout/designer/{layoutId}?embed=1
 *
 * Deep-link mode (?deeplink=1):
 *   - Designed for direct navigation from an external React app.
 *   - All Xibo chrome is stripped (sidebar, topbar, help pane, back buttons).
 *   - A minimal OTS branded back bar is rendered at the top of the viewport.
 *   - Clicking "Back" navigates to ?returnUrl= (relative paths only) or
 *     falls back to window.history.back() if the param is absent/invalid.
 *   - The editor options dropdown (Publish, Checkout, Discard…) stays visible.
 *
 * Embed mode (?embed=1):
 *   - All CMS navigation is hidden (sidebar, topbar, help pane).
 *   - Back/Exit buttons in the editor toolbar are hidden.
 *   - postMessage events are sent to the parent window:
 *       xibo:editor:ready, xibo:editor:save, xibo:editor:publish, xibo:editor:exit
 *   - Parent can send: xibo:editor:requestSave
 *
 * Copyright (C) 2020-2026 Xibo Signage Ltd
 * Copyright (C) 2026 Oribi Technology Services
 *
 * Licensed under the GNU Affero General Public License v3.0
 */
#}
{% extends "authed.twig" %}
{% import "inline.twig" as inline %}

{% block title %}{{ layout.layout }} | {{ "Layout Designer"|trans }} | {% endblock %}

{% set hideNavigation = "1" %}
{% set forceHide = true %}

{% block headContent %}
    {{ parent() }}

    {# Early mode detection — prevents FOUC for both embed and deep-link modes #}
    <script nonce="{{ cspNonce }}">
    (function(){
        try {
            var params = new URLSearchParams(window.location.search);
            var isEmbed = params.get('embed') === '1' || window !== window.parent;
            if (isEmbed) {
                document.documentElement.classList.add('ots-embed-mode');
            }
            if (params.get('deeplink') === '1') {
                document.documentElement.classList.add('ots-deeplink-mode');
            }
        } catch(e) {
            // Cross-origin iframe detection may throw — treat as embed
            document.documentElement.classList.add('ots-embed-mode');
        }
    })();
    </script>

    <style nonce="{{ cspNonce }}">
    /* ── Ensure editor pop-ups appear above our fixed action bars ───────────── */
    /* Our #ots-editor-bar sits at z-index 1300. Bootstrap modals default to
       backdrop:1040 / modal:1050, so they render behind it. Raise them to
       1302/1303 so the Checkout / welcome dialogs always appear on top. */
    .modal-backdrop { z-index: 1302 !important; }
    .modal          { z-index: 1303 !important; }

    /* ── Embed mode styles ──────────────────────────────────── */

    /* Hide Back/Exit button area */
    .ots-embed-mode .back-button {
        display: none !important;
    }

    /* Hide the help pane */
    .ots-embed-mode #help-pane {
        display: none !important;
    }

    /* Remove content wrapper padding/margins for full-bleed editor */
    .ots-embed-mode #content-wrapper {
        padding: 0 !important;
        margin: 0 !important;
    }

    .ots-embed-mode .page-content {
        padding: 0 !important;
        margin: 0 !important;
    }

    .ots-embed-mode .page-content > .row {
        margin: 0 !important;
    }

    .ots-embed-mode .page-content > .row > .col-sm-12 {
        padding: 0 !important;
    }

    /* Full viewport height for the editor */
    .ots-embed-mode body,
    .ots-embed-mode #layout-editor {
        min-height: 100vh;
    }

    /* Hide the top-bar options dropdown (Publish/Checkout/Discard/etc.) */
    .ots-embed-mode .editor-top-bar .editor-options-dropdown {
        display: none !important;
    }

    /* Hide the editor jump list (layout switcher) */
    .ots-embed-mode .editor-top-bar #layoutJumpListContainer {
        display: none !important;
    }

    /* ── Deep-link mode styles ──────────────────────────────── */

    /* Hide Back/Exit button area */
    .ots-deeplink-mode .back-button {
        display: none !important;
    }

    /* Hide the help pane */
    .ots-deeplink-mode #help-pane {
        display: none !important;
    }

    /* Remove content wrapper padding/margins for full-bleed editor */
    .ots-deeplink-mode #content-wrapper {
        padding: 0 !important;
        margin: 0 !important;
    }

    .ots-deeplink-mode .page-content {
        padding: 0 !important;
        margin: 0 !important;
    }

    .ots-deeplink-mode .page-content > .row {
        margin: 0 !important;
    }

    .ots-deeplink-mode .page-content > .row > .col-sm-12 {
        padding: 0 !important;
    }

    /* Full viewport height for the editor */
    .ots-deeplink-mode body,
    .ots-deeplink-mode #layout-editor {
        min-height: 100vh;
    }

    /* ── Editor positioning fix ─────────────────────────────────────────────
       Xibo's editor internal structure (from layout-editor.hbs + layout-editor.scss):

         #layout-editor
           .editor-top-bar   ← always min-height ~40px in normal flow;
                               its children are position:fixed top:0 (covered by our bar)
           .container-designer { height: calc(100vh - 50px) }

       Keeping #layout-editor in normal flow and hiding .editor-top-bar removes
       the 40px gap. We then push .container-designer down with margin-top and
       resize it so it fills exactly from below our bar to the viewport bottom.
    ─────────────────────────────────────────────────────────────────────── */

    /* Restore #layout-editor to normal document flow */
    #layout-editor {
        position: static !important;
        top: auto !important;
        left: auto !important;
        right: auto !important;
        bottom: auto !important;
        width: auto !important;
        height: auto !important;
        /* Preserve Xibo's horizontal negative margins (Bootstrap column gutter) */
        margin: 0 -15px -15px -15px !important;
    }

    /* Xibo's .editor-top-bar inner items use position:fixed top:0 so they
       already render behind our #ots-editor-bar (z-index 1300). Hiding the
       container removes the dead 40px normal-flow space it leaves behind. */
    .editor-top-bar {
        display: none !important;
    }

    /* Reposition the playlist-editor "Back to Layout" button below our bar
       instead of hiding it — it's shown by lD.openPlaylistEditor() */
    .back-button-playlist {
        top: 50px !important;
        z-index: 1290 !important;
    }

    /* Push the canvas below our 44px bar and fill the remaining viewport height */
    body.editor-opened .container-designer {
        margin-top: 44px !important;
        height: calc(100vh - 44px) !important;
    }

    /* Loading spinner while editor data loads — same offset */
    body.editor-opened #layout-editor .loading-container {
        margin-top: 44px !important;
        height: calc(100vh - 44px) !important;
    }

    /* Embed mode: no bar — canvas fills the full viewport */
    .ots-embed-mode .container-designer,
    .ots-embed-mode #layout-editor .loading-container {
        margin-top: 0 !important;
        height: 100vh !important;
    }

    /* Remove body-level padding (harmless safety reset) */
    body {
        padding-top: 0 !important;
    }

    /* Ensure no stray padding above the editor from page-wrapper */
    #page-wrapper,
    #content-wrapper {
        padding-top: 0 !important;
    }

    /* ── OTS combined editor bar ──────────────────────────── */

    /* Single fixed bar: back button + layout name + action buttons */
    #ots-editor-bar {
        display: flex;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        height: 44px;
        background: #0f172a;
        border-bottom: 2px solid #e87800;
        z-index: 1300;
        align-items: center;
        padding: 0 12px;
        gap: 8px;
        box-sizing: border-box;
    }

    /* Hide bar inside an iframe embed */
    .ots-embed-mode #ots-editor-bar {
        display: none;
    }

    #ots-bar-back {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        background: none;
        border: none;
        color: #e87800;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
        padding: 6px 10px;
        border-radius: 4px;
        line-height: 1;
        transition: background 0.15s;
        flex-shrink: 0;
    }

    #ots-bar-back:hover {
        background: rgba(232, 120, 0, 0.12);
    }

    #ots-bar-back:focus-visible {
        outline: 2px solid #e87800;
        outline-offset: 2px;
    }

    #ots-bar-back svg {
        flex-shrink: 0;
    }

    /* Vertical divider between back button and layout name */
    #ots-bar-divider {
        width: 1px;
        height: 20px;
        background: #334155;
        flex-shrink: 0;
    }

    /* ── Hide stock Xibo header bar on the layout designer page ── */
    .row.header.header-side {
        display: none !important;
    }

    /* ── Remove sidebar offset so editor fills the full viewport ── */
    #page-wrapper,
    #content-wrapper {
        margin-left: 0 !important;
        padding-left: 0 !important;
        width: 100% !important;
        max-width: 100% !important;
    }

    .page-content,
    .page-content > .row,
    .page-content > .row > .col-sm-12 {
        padding: 0 !important;
        margin: 0 !important;
    }

    /* Layout name / status label */
    #ots-bar-label {
        flex: 1 1 auto;
        min-width: 0;
        font-size: 13px;
        color: #94a3b8;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        padding-left: 4px;
    }

    .ots-bar-status {
        display: inline-block;
        font-size: 10px;
        font-weight: 600;
        letter-spacing: 0.05em;
        text-transform: uppercase;
        padding: 2px 6px;
        border-radius: 3px;
        margin-left: 8px;
        vertical-align: middle;
        background: rgba(232, 120, 0, 0.15);
        color: #e87800;
    }

    .ots-bar-status.ots-status-published {
        background: rgba(74, 222, 128, 0.15);
        color: #4ade80;
    }

    /* Shared button base */
    .ots-editor-bar-btn {
        display: inline-flex;
        align-items: center;
        gap: 5px;
        height: 30px;
        padding: 0 12px;
        border-radius: 4px;
        font-size: 13px;
        font-weight: 500;
        cursor: pointer;
        border: 1px solid transparent;
        line-height: 1;
        background: none;
        font-family: inherit;
        transition: background 0.15s, opacity 0.15s, border-color 0.15s;
        white-space: nowrap;
        flex-shrink: 0;
    }

    /* Save — filled orange */
    #ots-editor-save-btn {
        background: #e87800;
        color: #fff;
        border-color: #e87800;
    }

    #ots-editor-save-btn:hover {
        background: #d16b00;
        border-color: #d16b00;
    }

    /* Brief green flash after a successful save AJAX round-trip */
    #ots-editor-save-btn.ots-btn-saved {
        background: #16a34a !important;
        border-color: #16a34a !important;
        transition: none;
    }

    /* Publish — orange outline */
    #ots-editor-publish-btn {
        color: #e87800;
        border-color: #e87800;
    }

    #ots-editor-publish-btn:hover {
        background: rgba(232, 120, 0, 0.1);
    }

    /* Checkout — muted outline */
    #ots-editor-checkout-btn {
        color: #94a3b8;
        border-color: #334155;
    }

    #ots-editor-checkout-btn:hover {
        background: rgba(148, 163, 184, 0.1);
        border-color: #64748b;
        color: #cbd5e1;
    }

    /* Draft state: show Save + Publish; hide Checkout */
    #ots-editor-bar .ots-draft-only   { display: inline-flex; }
    #ots-editor-bar .ots-readonly-only { display: none; }

    /* Read-only/published state: hide Save + Publish; show Checkout */
    #ots-editor-bar.ots-state-readonly .ots-draft-only    { display: none !important; }
    #ots-editor-bar.ots-state-readonly .ots-readonly-only { display: inline-flex !important; }

    .ots-editor-bar-btn:disabled,
    .ots-editor-bar-btn.ots-btn-loading {
        opacity: 0.45;
        cursor: default;
        pointer-events: none;
    }

    .ots-editor-bar-btn:focus-visible {
        outline: 2px solid #e87800;
        outline-offset: 2px;
    }

    /* ── Interactive mode toggle ──────────────────────── */
    #ots-editor-interactive-btn {
        color: #94a3b8;
        border-color: #334155;
    }
    #ots-editor-interactive-btn:hover {
        background: rgba(148, 163, 184, 0.1);
        border-color: #64748b;
        color: #cbd5e1;
    }
    /* Lit up when interactive mode is active */
    #ots-editor-interactive-btn.ots-btn-active {
        background: rgba(232, 120, 0, 0.15);
        border-color: #e87800;
        color: #e87800;
    }
    /* Not useful in embed mode */
    .ots-embed-mode #ots-editor-interactive-btn { display: none !important; }

    /* Secondary divider between interactive toggle and primary action buttons */
    #ots-bar-actions-divider {
        width: 1px;
        height: 20px;
        background: #334155;
        flex-shrink: 0;
    }

    /* ── Discard — destructive/red outline, draft-only ── */
    #ots-editor-discard-btn {
        color: #f87171;
        border-color: #7f1d1d;
    }
    #ots-editor-discard-btn:hover {
        background: rgba(248, 113, 113, 0.1);
        border-color: #f87171;
    }

    /* ── Schedule — muted outline, readonly-only ─────── */
    #ots-editor-schedule-btn {
        color: #94a3b8;
        border-color: #334155;
    }
    #ots-editor-schedule-btn:hover {
        background: rgba(148, 163, 184, 0.1);
        border-color: #64748b;
        color: #cbd5e1;
    }

    /* ── Unlock — amber, hidden until layout is locked ── */
    #ots-editor-unlock-btn {
        display: none;
        color: #fbbf24;
        border-color: #78350f;
    }
    #ots-editor-unlock-btn:hover {
        background: rgba(251, 191, 36, 0.1);
        border-color: #fbbf24;
    }
    /* .ots-bar-locked is added via JS when lD detects locked-for-user */
    #ots-editor-bar.ots-bar-locked #ots-editor-unlock-btn {
        display: inline-flex;
    }
    </style>
{% endblock %}

{% block pageContent %}

    {# OTS combined editor bar — back button + layout name + action buttons #}
    <div id="ots-editor-bar" role="banner" aria-label="{{ "Layout editor"|trans }}">
        <button id="ots-bar-back" type="button" aria-label="{{ "Back"|trans }}">
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
                <path d="M10 12L6 8l4-4" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            {{ "Back"|trans }}
        </button>
        <div id="ots-bar-divider" aria-hidden="true"></div>
        <span id="ots-bar-label">
            {{ layout.layout }}
            <span class="ots-bar-status" id="ots-bar-status-badge">{{ "Draft"|trans }}</span>
        </span>
        {# Interactive mode toggle — available in both draft and read-only #}
        <button id="ots-editor-interactive-btn" type="button" class="ots-editor-bar-btn"
                title="{{ "Toggle interactive mode to link regions with actions"|trans }}">
            <svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
                <path d="M4 2l2 9 2.5-3H12L4 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
            </svg>
            {{ "Interactive"|trans }}
        </button>
        <div id="ots-bar-actions-divider" aria-hidden="true"></div>
        {# Draft state: Save + Publish + Discard #}
        <button id="ots-editor-save-btn" type="button" class="ots-editor-bar-btn ots-draft-only"
                title="{{ "Save current changes"|trans }}">
            <svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
                <path d="M8 2v7m0 0L5 6m3 3l3-3M3 13h10" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            {{ "Save"|trans }}
        </button>
        <button id="ots-editor-publish-btn" type="button" class="ots-editor-bar-btn ots-draft-only"
                title="{{ "Publish layout to displays"|trans }}">
            <svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
                <path d="M8 11V4m0 0L5 7m3-3l3 3M3 13h10" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            {{ "Publish"|trans }}
        </button>
        <button id="ots-editor-discard-btn" type="button" class="ots-editor-bar-btn ots-draft-only"
                title="{{ "Discard this draft and revert to the published version"|trans }}">
            <svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
                <path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
            </svg>
            {{ "Discard"|trans }}
        </button>
        {# Read-only/published state: Checkout + Schedule #}
        <button id="ots-editor-checkout-btn" type="button" class="ots-editor-bar-btn ots-readonly-only"
                title="{{ "Checkout this layout for editing"|trans }}">
            <svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
                <path d="M10.5 2.5l3 3L5 14H2v-3L10.5 2.5z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
                <path d="M9 4l3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
            </svg>
            {{ "Checkout"|trans }}
        </button>
        <button id="ots-editor-schedule-btn" type="button" class="ots-editor-bar-btn ots-readonly-only"
                title="{{ "Schedule this layout on displays"|trans }}">
            <svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
                <circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5"/>
                <path d="M8 5v3.5l2.5 2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
            {{ "Schedule"|trans }}
        </button>
        {# Unlock: hidden by default, revealed via JS when lD marks the layout as locked-for-user #}
        <button id="ots-editor-unlock-btn" type="button" class="ots-editor-bar-btn"
                title="{{ "Unlock this layout"|trans }}">
            <svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
                <rect x="3" y="8" width="10" height="6" rx="1.5" stroke="currentColor" stroke-width="1.5"/>
                <path d="M6 8V5a3 3 0 016 0v2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
            </svg>
            {{ "Unlock"|trans }}
        </button>
    </div>  {# /ots-editor-bar #}

    <!-- Editor structure -->
    <div id="layout-editor" data-published-layout-id="{{ publishedLayoutId }}" data-layout-id="{{ layout.layoutId }}" data-layout-help={{ help }}></div>

{% endblock %}

{% block javaScript %}
    {# Add common files #}
    {% include "editorTranslations.twig" %}
    {% include "editorVars.twig" %}

    <script src="{{ theme.rootUri() }}dist/layoutEditor.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
    <script src="{{ theme.rootUri() }}dist/playlistEditor.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
    <script src="{{ theme.rootUri() }}dist/codeEditor.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
    <script src="{{ theme.rootUri() }}dist/wysiwygEditor.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>
    <script src="{{ theme.rootUri() }}dist/editorCommon.bundle.min.js?v={{ version }}&rev={{revision}}" nonce="{{ cspNonce }}"></script>

    <script type="text/javascript" nonce="{{ cspNonce }}">
        var previewJwt = "{{ previewJwt }}";

        {% autoescape "js" %}
            {# Custom translations #}
            var layoutEditorHelpLink = "{{ help }}";

            var layoutEditorTrans = {
                back: "{% trans "Back" %}",
                exit: "{% trans "Exit" %}",
                cancel: "{% trans "Cancel" %}",
                toggleFullscreen: "{% trans "Toggle Fullscreen Mode" %}",
                layerManager: "{% trans "Layer Manager" %}",
                snapToGrid: "{% trans "Snap to Grid" %}",
                snapToBorders: "{% trans "Snap to Borders" %}",
                snapToElements: "{% trans "Snap to Elements" %}",
                newTitle: "{% trans "New" %}",
                publishTitle: "{% trans "Publish" %}",
                discardTitle: "{% trans "Discard draft" %}",
                deleteTitle: "{% trans "Delete" %}",
                publishMessage: "{% trans "Are you sure you want to publish this Layout? If it is already in use the update will automatically get pushed." %}",
                checkoutTitle: "{% trans "Checkout" %}",
                scheduleTitle: "{% trans "Schedule" %}",
                clearLayout: "{% trans "Clear Canvas" %}",
                unlockTitle: "{% trans "Unlock" %}",
                saveTemplateTitle: "{% trans "Save Template" %}",
                readOnlyModeTitle: "{% trans "Read Only" %}",
                readOnlyModeMessage: "{% trans "You are viewing this Layout in read only mode, checkout by clicking on this message or from the Options menu above!" %}",
                lockedModeTitle: "{% trans "Locked" %}",
                lockedModeMessage: "{% trans "This is being locked by another user. Lock expires on: [expiryDate]" %}",
                checkoutMessage: "{% trans "Not editable, please checkout!" %}",
                unlockMessage: "{% trans "The current layout will be unlocked to other users. You will also be redirected to the Layouts page" %}",
                viewModeTitle: "{% trans "View" %}",
                actions: "{% trans "Actions" %}",
                welcomeModalMessage: "{% trans "This is published and cannot be edited. You can checkout for editing below, or continue to view it in a read only mode." %}",
                showingSampleData: "{% trans "Showing sample data" %}",
                emptyElementData: "{% trans "Has empty data" %}"
            };

            var viewerTrans = {
                inlineEditor: "{% trans "Inline Editor" %}",
                nextWidget: "{% trans "Next widget" %}",
                previousWidget: "{% trans "Previous widget" %}",
                addWidget: "{% trans "Add Widget" %}",
                editGroup: "{% trans "Edit Group" %}",
                editPlaylist: "{% trans "Edit Playlist" %}",
                prev: '{{ "Previous Widget"|trans }}',
                next: '{{ "Next Widget"|trans }}',
                empty: '{{ "Empty Playlist"|trans }}',
                invalidRegion: '{{ "Invalid Region"|trans }}',
                editPlaylistTitle: '{{ "Edit Playlist"|trans }}',
                dynamicPlaylistTitle: '{{ "Dynamic Playlist"|trans }}'
            };

            var timelineTrans = {
                zoomIn: "{% trans "Zoom in" %}",
                zoomOut: "{% trans "Zoom out" %}",
                resetZoom: "{% trans "Reset zoom" %}",
                zoomDelta: "{% trans "Visible area time span" %}",
                hiddenTimeruler: "{% trans "Zoom out to see timeruler!" %}",
                emptyTimeline: "{% trans "No Regions: Add a Region to start creating content by clicking here or the Edit Layout icon below!" %}",
                zoomFindSelected: "{% trans "Scroll to selected widget" %}",
                startTime: "{% trans "Visible area start time" %}",
                endTime: "{% trans "Visible area end time" %}",
                layoutName: "{% trans "Layout name" %}",
                layoutDuration: "{% trans "Layout duration" %}",
                layoutDimensions: "{% trans "Layout dimensions" %}",
                addToThisPosition: "{% trans "Add to this position" %}",
                hiddenContentInWidget: "{% trans "Zoom in to see more details!" %}",
                editRegion: "{% trans "Edit region" %}",
                openRegionAsPlaylist: "{% trans "Open as playlist" %}",
                widgetActions: "{% trans "Widget Actions:" %}",
                regionActions: "{% trans "Region Actions:" %}"
            };

            var bottombarTrans = {
                edit: "{% trans "Edit layout regions" %}",
                addRegion: "{% trans "Add" %}",
                addRegionDesc: "{% trans "Add a new region" %}",
                deleteRegion: "{% trans "Delete region" %}",
                undo: "{% trans "Undo" %}",
                undoDesc: "{% trans "Revert last change" %}",
                close: "{% trans "Close" %}",
                closeDesc: "{% trans "Return to Layout View" %}",
                save: "{% trans "Save" %}",
                saveDesc: "{% trans "Save changes" %}",
                backToLayout: "{% trans "Go back to Layout view" %}",
                saveEditorChanges: "{% trans "Save editor changes" %}",
                playPreviewLayout: "{% trans "Play Layout preview" %}",
                playPreviewLayoutPOTitle: "{% trans "Preview stopped!" %}",
                playPreviewLayoutPOMessage: "{% trans "Click to Play again" %}",
                editLayout: "{% trans "Edit Layout" %}",
                stopPreviewLayout: "{% trans "Stop Layout preview" %}",
                nextWidget: "{% trans "Next widget" %}",
                previousWidget: "{% trans "Previous widget" %}",
                widgetName: "{% trans "Widget Name" %}",
                widgetType: "{% trans "Widget Type" %}",
                widgetTemplate: "{% trans "Widget Template Name" %}",
                elementName: "{% trans "Element Name" %}",
                elementMediaInfoName: "{{ "Media Name" |trans }}",
                elementMediaInfoId: "{{ "Media ID" |trans }}",
                elementGroupName: "{% trans "Element Group Name" %}",
                regionName: "{% trans "Region Name" %}",
                templateName: "{% trans "Template" %}",
                objectType: {
                    layout: "{{ "Layout" |trans }}",
                    region: "{{ "Region" |trans }}",
                    zone: "{{ "Zone" |trans }}",
                    playlist: "{{ "Playlist" |trans }}",
                    widget: "{{ "Widget" |trans }}",
                    element: "{{ "Element" |trans }}",
                    "element-group": "{{ "Element Group" |trans }}"
                },
                tools: {
                    audio: {
                        name: "{{ "Audio" |trans }}",
                        description: "{{ "Upload Audio files to assign to Widgets"|trans }}"
                    },
                    transitionIn: {
                        name: "{{ "Transition In" |trans }}",
                        description: "{{ "Apply a Transition type for the start of a media item"|trans }}"
                    },
                    transitionOut: {
                        name: "{{ "Transition Out" |trans }}",
                        description: "{{ "Apply a Transition type for the end of a media item"|trans }}"
                    },
                    permissions: {
                        name: "{{ "Sharing" |trans }}",
                        description: "{{ "Set View, Edit and Delete Sharing for Widgets and Playlists"|trans }}"
                    }
                }
            };
        {% endautoescape %}
    </script>

    <script type="text/javascript" nonce="{{ cspNonce }}">
        /**
         * Setup the background form.
         */
        function backGroundFormSetup(dialog) {
            var $backgroundImageId = $('[name="backgroundImageId"]', dialog);
            var notFoundIcon = $('#bg_not_found_icon', dialog);
            var bgImageFileName = $('#bg_media_name', dialog);
            var saveButton = $('button#save', dialog);
            var initialBackgroundImageId = $backgroundImageId.val();
            var backgroundChanged = false;
            var mediaName = '';

            function backgroundImageChange() {
                var id = $backgroundImageId.val();
                var isNotDefined = [0, ''].indexOf(id) !== -1;

                $('#backgroundRemoveButton').toggleClass('disabled', isNotDefined);

                if (isNotDefined) {
                    notFoundIcon.show();
                    bgImageFileName.hide();
                } else {
                    notFoundIcon.hide();
                    bgImageFileName.show();

                    if(mediaName) {
                        bgImageFileName.html(mediaName);
                    }

                    if (id !== initialBackgroundImageId) {
                        saveButton.trigger('click');
                    }
                }

                if (id !== initialBackgroundImageId) {
                    backgroundChanged = true;
                }
            }

            function backgroundImageHandleDrop(mediaToAdd, fromProvider) {
                if(fromProvider) {
                    lD.importFromProvider([mediaToAdd]).then((res) => {
                        $backgroundImageId.val(res[0]).trigger('change');
                    }).catch(function() {
                        toastr.error(errorMessagesTrans.importingMediaFailed);
                    });
                } else {
                    $backgroundImageId.val(mediaToAdd).trigger('change');
                }

                lD.toolbar.deselectCardsAndDropZones();
            }

            $backgroundImageId.change(backgroundImageChange);
            backgroundImageChange();

            $('#backgroundUploadButton').on('click', function(e) {
                layoutEditBackgroundButtonClicked(e, dialog);
            });

            $('#backgroundRemoveButton').on('click', function(e) {
                if(!$(this).hasClass('disabled')) {
                    $backgroundImageId.val('').trigger('change');
                }
            });

            $('.background-image-add').droppable({
                greedy: true,
                tolerance: 'pointer',
                accept: function(el) {
                    return ($(el).data('type') === 'media' && $(el).data('subType') === 'image');
                },
                drop: _.debounce(function(event, ui) {
                    var $draggable = $(ui.draggable[0]);
                    bgImageFileName.html($draggable.data('title'));
                    mediaName = $draggable.data('cardTitle');

                    if($draggable.hasClass('from-provider')) {
                        backgroundImageHandleDrop($draggable.data('providerData'), true);
                    } else {
                        backgroundImageHandleDrop($draggable.data('mediaId'));
                    }
                }, 200)
            });

            $('.background-image-drop').on('click', function() {
                var selectedCard = lD.toolbar.selectedCard;
                var fromProvider = selectedCard.hasClass('from-provider');
                var cardData = (fromProvider) ? selectedCard.data('providerData') : selectedCard.data('mediaId');

                bgImageFileName.html(selectedCard.data('cardTitle'));
                mediaName = selectedCard.data('cardTitle');

                backgroundImageHandleDrop(cardData, fromProvider);
            });

            $("#layoutEditForm").submit(function(e) {
                e.preventDefault();
                var form = $(this);

                $.ajax({
                    type: form.attr("method"),
                    url: form.attr("action"),
                    cache: false,
                    dataType: "json",
                    data: $(form).serialize(),
                    success: function(xhr, textStatus, error) {
                        XiboSubmitResponse(xhr, form);

                        if (xhr.success) {
                            var layout = $("div#layout");

                            if (layout.length > 0) {
                                var color = form.find("#backgroundColor").val();
                                layout.data().backgroundColor = color;
                                layout.css("background-color", color);

                                if (backgroundChanged)
                                    window.location.reload();
                            } else {
                                if (backgroundChanged && typeof(table) !== 'undefined' && table.hasOwnProperty('ajax'))
                                    table.ajax.reload(null, false);
                            }
                        }
                    },
                    error: function(xhr, textStatus, errorThrown) {
                        SystemMessage(xhr.responseText, false);
                    }
                });
            })
        };

        /**
         * Layout edit background add image button
         */
        function layoutEditBackgroundButtonClicked(e, dialog) {
            e.preventDefault();

            openUploadForm({
                url: $(e.target).data().addNewBackgroundUrl,
                title: "{% trans "Add Background Image" %}",
                videoImageCovers: false,
                buttons: {
                    main: {
                        label: "{% trans "Done" %}",
                        className: "btn-primary btn-bb-main",
                        callback: function () {
                            XiboDialogClose();
                        }
                    }
                },
                templateOptions: {
                    multi: false,
                    trans: {
                        addFiles: "{% trans "Browse/Add Image" %}",
                        startUpload: "{% trans "Start Upload" %}",
                        cancelUpload: "{% trans "Cancel Upload" %}"
                    },
                    upload: {
                        maxSize: {{ libraryUpload.maxSize }},
                        maxSizeMessage: "{{ libraryUpload.maxSizeMessage }}",
                        validExt: "{{ libraryUpload.validImageExt }}"
                    }
                },
                uploadDoneEvent: function (data) {
                    var mediaId = data.result.files[0].mediaId;

                    if ($(dialog).find('[name="backgroundImageId"]').length === 0) {
                        $('<input>').attr({
                            type: 'hidden',
                            name: 'backgroundImageId',
                            value: mediaId
                        }).appendTo(dialog);
                    } else {
                        $('[name="backgroundImageId"]').val(mediaId);
                    }

                    dialog.find("#bg_not_found_icon").hide();
                    dialog.find("#backgroundRemoveButton").removeClass("disabled");

                    XiboDialogClose();
                    $('[name="backgroundImageId"]').trigger('change');
                }
            });
        }

        function layoutPublishFormOpen() {
        }

        function layoutEditFormSaved() {
            lD.reloadData(lD.layout, {
                refreshEditor: true,
            });
        }
    </script>

    {# ── Embed mode: postMessage bridge ──────────────────────── #}
    <script type="text/javascript" nonce="{{ cspNonce }}">
    (function() {
        'use strict';

        var params = new URLSearchParams(window.location.search);
        var isEmbed = false;

        try {
            isEmbed = params.get('embed') === '1' || window !== window.parent;
        } catch(e) {
            isEmbed = true;
        }

        if (!isEmbed) return;

        // Add embed class to body once DOM is ready
        document.body.classList.add('ots-embed-mode');

        var layoutId = document.getElementById('layout-editor')
            ? document.getElementById('layout-editor').getAttribute('data-layout-id')
            : null;

        // Restrict postMessage to the known React app origin.
        // window.location.origin is the CMS origin; the parent app is on the same host.
        var targetOrigin = window.location.origin;

        /**
         * Send a message to the parent window.
         */
        function sendToParent(type, data) {
            if (window.parent && window.parent !== window) {
                var message = { type: type, layoutId: layoutId };
                if (data) {
                    for (var key in data) {
                        if (data.hasOwnProperty(key)) {
                            message[key] = data[key];
                        }
                    }
                }
                window.parent.postMessage(message, targetOrigin);
            }
        }

        // ── Intercept editor save/publish via ajaxComplete ──────
        $(document).ajaxComplete(function(event, xhr, settings) {
            if (!settings || !settings.url) return;

            try {
                var response = xhr.responseJSON || {};
                if (!response.success) return;

                // Detect layout save (PUT to layout endpoint)
                if (settings.type === 'PUT' && settings.url.match(/\/layout\/\d+/)) {
                    sendToParent('xibo:editor:save', { url: settings.url });
                }

                // Detect layout publish
                if (settings.url.match(/\/layout\/publish\/\d+/)) {
                    sendToParent('xibo:editor:publish', { url: settings.url });
                }

                // Detect checkout
                if (settings.url.match(/\/layout\/checkout\/\d+/)) {
                    sendToParent('xibo:editor:checkout', {
                        url: settings.url,
                        newLayoutId: response.id || null
                    });
                }
            } catch(e) {
                // Silently ignore parse errors
            }
        });

        // ── Intercept Back/Exit navigation ──────────────────────
        // The editor sets window.location.href to exitURL — intercept it
        $(document).on('click', '.back-button a, .editor-close-btn', function(e) {
            e.preventDefault();
            e.stopPropagation();
            sendToParent('xibo:editor:exit', {});
        });

        // ── Listen for commands from parent ─────────────────────
        window.addEventListener('message', function(event) {
            var msg = event.data;
            if (!msg || typeof msg.type !== 'string') return;

            switch (msg.type) {
                case 'xibo:editor:requestSave':
                    // Trigger the properties panel save if pending
                    if (typeof lD !== 'undefined' && lD.propertiesPanel && lD.propertiesPanel.toSave) {
                        lD.propertiesPanel.save({ target: lD.selectedObject });
                    }
                    break;

                case 'xibo:editor:requestPublish':
                    if (typeof lD !== 'undefined' && lD.showPublishScreen) {
                        lD.showPublishScreen();
                    }
                    break;

                case 'xibo:editor:setTheme': {
                    var newMode = msg.mode;
                    if (newMode === 'light') {
                        document.documentElement.classList.add('ots-light-mode');
                        document.body.classList.add('ots-light-mode');
                    } else {
                        document.documentElement.classList.remove('ots-light-mode');
                        document.body.classList.remove('ots-light-mode');
                    }
                    try { localStorage.setItem('ots-theme-mode', newMode); } catch(ignore) {}
                    break;
                }
            }
        });

        // ── Notify parent when editor is ready ──────────────────
        // Fire xibo:editor:error if editor hasn't opened within 15 s
        var initErrTimeout = setTimeout(function() {
            if (!document.body.classList.contains('editor-opened')) {
                sendToParent('xibo:editor:error', { reason: 'timeout' });
            }
        }, 15000);

        var readyObserver = new MutationObserver(function(mutations) {
            if (document.body.classList.contains('editor-opened')) {
                clearTimeout(initErrTimeout);
                sendToParent('xibo:editor:ready', {
                    theme: document.documentElement.classList.contains('ots-light-mode') ? 'light' : 'dark'
                });
                readyObserver.disconnect();
            }
        });

        if (document.body.classList.contains('editor-opened')) {
            clearTimeout(initErrTimeout);
            sendToParent('xibo:editor:ready', {
                theme: document.documentElement.classList.contains('ots-light-mode') ? 'light' : 'dark'
            });
        } else {
            readyObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });
        }
    })();
    </script>

    {# ── Back bar: returnUrl navigation (always active) ────────── #}
    <script type="text/javascript" nonce="{{ cspNonce }}">
    (function() {
        'use strict';

        var params = new URLSearchParams(window.location.search);

        // Still mirror deeplink class for any other deeplink-specific behaviour.
        if (params.get('deeplink') === '1') {
            document.body.classList.add('ots-deeplink-mode');
        }

        /**
         * Allow only safe same-origin relative paths.
         * Accepts:  /app/layouts, /layouts/123?foo=bar
         * Rejects:  //evil.com, https://evil.com, javascript:void(0), empty string
         */
        function validateReturnUrl(url) {
            if (!url || typeof url !== 'string') return false;
            // Must start with '/' but not '//' (protocol-relative URL)
            if (url.charAt(0) !== '/' || url.charAt(1) === '/') return false;
            // Reject anything containing a scheme (e.g. https://)
            if (url.indexOf('://') !== -1) return false;
            return true;
        }

        var rawReturn = params.get('returnUrl');
        var safeReturn = validateReturnUrl(rawReturn) ? rawReturn : null;

        var backBtn = document.getElementById('ots-bar-back');
        if (backBtn) {
            backBtn.addEventListener('click', function() {
                if (safeReturn) {
                    window.location.href = safeReturn;
                } else {
                    window.history.back();
                }
            });
        }
    })();
    </script>

    {# ── OTS Editor Action Bar: state management & button wiring ── #}
    <script type="text/javascript" nonce="{{ cspNonce }}">
    (function() {
        'use strict';

        var bar            = document.getElementById('ots-editor-bar');
        var saveBtn        = document.getElementById('ots-editor-save-btn');
        var publishBtn     = document.getElementById('ots-editor-publish-btn');
        var checkoutBtn    = document.getElementById('ots-editor-checkout-btn');
        var discardBtn     = document.getElementById('ots-editor-discard-btn');
        var interactiveBtn = document.getElementById('ots-editor-interactive-btn');
        var scheduleBtn    = document.getElementById('ots-editor-schedule-btn');
        var unlockBtn      = document.getElementById('ots-editor-unlock-btn');
        var statusBadge    = document.getElementById('ots-bar-status-badge');

        if (!bar || !saveBtn || !publishBtn || !checkoutBtn) return;

        // ── State management ──────────────────────────────────────
        // 'draft'    → Save + Publish visible, Checkout hidden
        // 'readonly' → Checkout visible, Save + Publish hidden
        function setBarState(state) {
            if (state === 'readonly') {
                bar.classList.add('ots-state-readonly');
                if (statusBadge) {
                    statusBadge.textContent = '{{ "Published"|trans }}';
                    statusBadge.classList.add('ots-status-published');
                }
            } else {
                bar.classList.remove('ots-state-readonly');
                if (statusBadge) {
                    statusBadge.textContent = '{{ "Draft"|trans }}';
                    statusBadge.classList.remove('ots-status-published');
                }
            }
        }

        // ── Detect initial state from data attributes ─────────────
        // publishedLayoutId === layoutId  →  viewing the published version → read-only
        // publishedLayoutId !== layoutId  →  viewing a draft copy            → editable
        function detectInitialState() {
            var editorEl = document.getElementById('layout-editor');
            if (!editorEl) return;
            var layoutId    = editorEl.getAttribute('data-layout-id');
            var publishedId = editorEl.getAttribute('data-published-layout-id');
            if (publishedId && publishedId !== '0' && publishedId === layoutId) {
                setBarState('readonly');
            } else {
                setBarState('draft');
            }
        }

        // ── Wait for the React editor to signal it's ready ────────
        if (document.body.classList.contains('editor-opened')) {
            detectInitialState();
        } else {
            var initObs = new MutationObserver(function() {
                if (document.body.classList.contains('editor-opened')) {
                    initObs.disconnect();
                    detectInitialState();
                }
            });
            initObs.observe(document.body, { attributes: true, attributeFilter: ['class'] });
        }

        // ── Button: Save ──────────────────────────────────────────
        // Xibo auto-saves widget/region changes via the history manager.
        // The save button's job is to flush any pending properties panel form.
        saveBtn.addEventListener('click', function() {
            if (saveBtn.classList.contains('ots-btn-loading')) return;
            try {
                if (typeof lD !== 'undefined' && lD.propertiesPanel) {
                    lD.propertiesPanel.save({ target: lD.selectedObject });
                }
            } catch(e) { /* silent */ }
        });

        // ── Button: Publish ───────────────────────────────────────
        publishBtn.addEventListener('click', function() {
            if (publishBtn.classList.contains('ots-btn-loading')) return;
            try {
                if (typeof lD !== 'undefined' && typeof lD.showPublishScreen === 'function') {
                    lD.showPublishScreen();
                }
            } catch(e) { /* silent */ }
        });

        // ── Button: Checkout ──────────────────────────────────────
        // Use urlsForApi (set by editorVars.twig) so the URL is always correct
        // regardless of any sub-path the CMS is hosted under.
        checkoutBtn.addEventListener('click', function() {
            // Delegate to Xibo's own checkout implementation, which handles
            // the AJAX call and redirect internally using urlsForApi.
            if (typeof lD === 'undefined' || !lD.layout ||
                typeof lD.layout.checkout !== 'function') {
                toastr.error('Editor not ready. Please refresh and try again.');
                return;
            }
            lD.layout.checkout();
        });

        // ── Button: Discard ───────────────────────────────────────
        // We handle this manually so we can redirect back to the custom app
        // (returnUrl) instead of letting Xibo redirect to its own layouts page.
        if (discardBtn) {
            discardBtn.addEventListener('click', function() {
                if (discardBtn.classList.contains('ots-btn-loading')) return;

                if (!confirm('{{ "Discard this draft and revert to the published version?"|trans }}')) return;

                var editorEl = document.getElementById('layout-editor');
                if (!editorEl) return;
                // Discard endpoint takes the published (parent) layout ID.
                var publishedId = editorEl.getAttribute('data-published-layout-id');
                if (!publishedId) return;

                if (typeof urlsForApi === 'undefined' ||
                    !urlsForApi.layout || !urlsForApi.layout.discard) return;

                discardBtn.classList.add('ots-btn-loading');
                discardBtn.disabled = true;

                $.ajax({
                    type: urlsForApi.layout.discard.type || 'PUT',
                    url: urlsForApi.layout.discard.url.replace(':id', publishedId),
                    success: function() {
                        // Redirect to returnUrl (same validation as Back button)
                        var rawReturn = new URLSearchParams(window.location.search).get('returnUrl');
                        var safeReturn = (rawReturn &&
                            rawReturn.charAt(0) === '/' &&
                            rawReturn.charAt(1) !== '/' &&
                            rawReturn.indexOf('://') === -1) ? rawReturn : null;
                        window.location.href = safeReturn || '/';
                    },
                    error: function(xhr) {
                        discardBtn.classList.remove('ots-btn-loading');
                        discardBtn.disabled = false;
                        try {
                            var err = xhr.responseJSON || JSON.parse(xhr.responseText);
                            toastr.error(err.message || '{{ "Discard failed"|trans }}');
                        } catch(e) {
                            toastr.error('{{ "Discard failed"|trans }}');
                        }
                    }
                });
            });
        }

        // ── Button: Interactive mode toggle ───────────────────────
        if (interactiveBtn) {
            interactiveBtn.addEventListener('click', function() {
                try {
                    if (typeof lD !== 'undefined' && typeof lD.toggleInteractiveMode === 'function') {
                        lD.toggleInteractiveMode(!lD.interactiveMode);
                    }
                } catch(e) { /* silent */ }
            });
        }

        // ── Button: Schedule ──────────────────────────────────────
        if (scheduleBtn) {
            scheduleBtn.addEventListener('click', function() {
                if (scheduleBtn.classList.contains('ots-btn-loading')) return;
                try {
                    if (typeof lD !== 'undefined' && typeof lD.showScheduleScreen === 'function') {
                        lD.showScheduleScreen();
                    }
                } catch(e) { /* silent */ }
            });
        }

        // ── Button: Unlock ────────────────────────────────────────
        if (unlockBtn) {
            unlockBtn.addEventListener('click', function() {
                try {
                    if (typeof lD !== 'undefined' && typeof lD.showUnlockScreen === 'function') {
                        lD.showUnlockScreen();
                    }
                } catch(e) { /* silent */ }
            });
        }

        // ── Watch #layout-editor classList for interactive-mode and locked state ──
        (function() {
            var editorEl = document.getElementById('layout-editor');
            if (!editorEl) return;
            new MutationObserver(function() {
                // Sync interactive button visual toggle state
                if (interactiveBtn) {
                    interactiveBtn.classList.toggle(
                        'ots-btn-active',
                        editorEl.classList.contains('interactive-mode') ||
                        editorEl.classList.contains('interactive-edit-widget-mode')
                    );
                }
                // Show Unlock button when lD adds 'locked' + 'locked-for-user' classes
                bar.classList.toggle('ots-bar-locked', editorEl.classList.contains('locked-for-user'));
            }).observe(editorEl, { attributes: true, attributeFilter: ['class'] });
        })();

        // ── AJAX intercepts: auto-update bar state ────────────────
        $(document).ajaxComplete(function(event, xhr, settings) {
            if (!settings || !settings.url) return;
            try {
                var response = xhr.responseJSON || {};
                if (!response.success) return;
                var url = settings.url;

                // Layout draft save (PUT /layout/{id}) → flash Save button
                if (settings.type === 'PUT' && /\/layout\/\d+(?:$|\?|\/(?!publish|checkout|discard|delete))/.test(url)) {
                    saveBtn.classList.add('ots-btn-saved');
                    setTimeout(function() {
                        saveBtn.classList.remove('ots-btn-saved');
                    }, 2000);
                }

                // Publish → layout is now the published version → read-only
                if (/\/layout\/publish\/\d+/.test(url)) {
                    setBarState('readonly');
                }

                // Checkout → a new draft is now active → editable
                if (/\/layout\/checkout\/\d+/.test(url)) {
                    setBarState('draft');
                }
            } catch(e) { /* silent */ }
        });

    })();
    </script>
{% endblock %}
