(function () {
    "use strict";

    const copy = window.h4bsSliderEditorL10n || {};
    const layerTypes = ["heading", "text", "button", "image", "shape", "icon", "video", "counter", "progress", "html"];
    const devices = {
        desktop: { label: "Desktop", suffix: "", width: "100%" },
        tablet: { label: "Tablet", suffix: "_tablet", width: "768px" },
        mobile: { label: "Mobile", suffix: "_mobile", width: "390px" },
    };
    const SETTINGS_STORAGE_KEY = "h4bs.slider.settingsCollapsed";

    function readStoredBoolean(key, fallback) {
        try {
            const value = window.localStorage.getItem(key);
            return value === null ? !!fallback : value === "1";
        } catch (_error) {
            return !!fallback;
        }
    }

    function persistStoredBoolean(key, value) {
        try {
            window.localStorage.setItem(key, value ? "1" : "0");
        } catch (_error) {
            return;
        }
    }

    function escapeHtml(value) {
        return String(value == null ? "" : value)
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#039;");
    }

    function number(value, fallback) {
        const parsed = Number(value);
        return Number.isFinite(parsed) ? parsed : fallback;
    }

    function clamp(value, minimum, maximum) {
        return Math.max(minimum, Math.min(maximum, number(value, minimum)));
    }

    function uniqueId(prefix) {
        return String(prefix || "layer") + "-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 7);
    }

    function defaultLayer(type) {
        const content = {
            heading: "Slide heading",
            text: "Supporting slide copy",
            button: "Learn more",
            image: "",
            shape: "",
            icon: "Featured",
            video: "",
            counter: "100",
            progress: "75",
            html: "<p>Safe HTML layer</p>",
        };
        return {
            id: uniqueId(type),
            type: type,
            content: content[type] || "",
            src: "",
            url: "",
            alt: "",
            icon: "star",
            tag: "h2",
            x: 8,
            y: 18,
            width: type === "button" ? 30 : 70,
            height: type === "shape" ? 22 : 0,
            font_size: type === "heading" ? 56 : 20,
            font_size_tablet: type === "heading" ? 42 : 18,
            font_size_mobile: type === "heading" ? 32 : 17,
            color: "#ffffff",
            background: type === "button" ? "#b31217" : (type === "shape" ? "#b31217" : ""),
            align: "left",
            z: 2,
            opacity: 1,
            radius: type === "button" ? 4 : 0,
            padding: type === "button" ? 14 : 0,
            entrance: "fade_up",
            exit: "fade",
            delay: 0,
            duration: 650,
            exit_delay: 0,
            easing: "ease_out",
            parallax: 0,
            hide_desktop: false,
            hide_tablet: false,
            hide_mobile: false,
        };
    }

    function defaultSlide() {
        return {
            source: "image",
            image_id: 0,
            image_url: "",
            background_alt: "",
            snapshot_url: "",
            snapshot_width: 960,
            snapshot_height: 540,
            title: "Slide",
            badge: "",
            caption: "",
            button_text: "",
            content_position: "bottom_left",
            link: "",
            background_color: "#090909",
            background_overlay: "#000000",
            background_overlay_opacity: 0.35,
            background_fit: "cover",
            background_position_x: 50,
            background_position_y: 50,
            video_url: "",
            video_poster: "",
            duration: 5500,
            layers: [defaultLayer("heading"), defaultLayer("button")],
        };
    }

    function normalizeSlides(value) {
        if (!Array.isArray(value)) return [defaultSlide()];
        const slides = value.filter(function (slide) { return slide && typeof slide === "object" && !Array.isArray(slide); });
        if (!slides.length) return [defaultSlide()];
        slides.forEach(function (slide) {
            if (!Array.isArray(slide.layers)) slide.layers = [];
            if (!slide.source) slide.source = "image";
            if (!slide.background_fit) slide.background_fit = "cover";
            if (slide.background_position_x == null) slide.background_position_x = 50;
            if (slide.background_position_y == null) slide.background_position_y = 50;
        });
        return slides;
    }

    function option(value, label, selected) {
        return '<option value="' + escapeHtml(value) + '"' + (String(value) === String(selected) ? " selected" : "") + ">" + escapeHtml(label) + "</option>";
    }

    function field(label, control, help) {
        return '<label class="h4bs-slider-field"><span>' + escapeHtml(label) + '</span>' + control + (help ? '<small>' + escapeHtml(help) + '</small>' : "") + '</label>';
    }

    function input(binding, value, type, attributes) {
        return '<input type="' + escapeHtml(type || "text") + '" data-' + binding + ' value="' + escapeHtml(value == null ? "" : value) + '" ' + (attributes || "") + ">";
    }

    function select(binding, selected, entries) {
        return '<select data-' + binding + '>' + entries.map(function (entry) { return option(entry[0], entry[1], selected); }).join("") + "</select>";
    }

    function initBuilder(root) {
        const textarea = root.querySelector("[data-h4bs-slider-json]");
        const app = root.querySelector("[data-h4bs-slider-app]");
        const jsonStatus = root.querySelector("[data-h4bs-slider-json-status]");
        if (!(textarea instanceof HTMLTextAreaElement) || !(app instanceof HTMLElement)) return;

        let slides;
        let preserveInvalidJson = false;
        try {
            slides = normalizeSlides(JSON.parse(textarea.value || "[]"));
        } catch (error) {
            slides = [defaultSlide()];
            preserveInvalidJson = true;
            if (jsonStatus) jsonStatus.textContent = "Existing JSON could not be parsed. The original text is preserved until you edit the canvas.";
        }
        let slideIndex = 0;
        let layerIndex = slides[0].layers.length ? 0 : -1;
        let device = "desktop";
        let drag = null;
        let workspaceExpanded = false;
        let settingsCollapsed = readStoredBoolean(SETTINGS_STORAGE_KEY, false);
        let historyPast = [];
        let historyFuture = [];
        let fieldStartSnapshot = "";

        function historySnapshot() {
            return JSON.stringify(slides);
        }

        function pushHistory(snapshot) {
            const value = String(snapshot || "");
            if (!value || value === historySnapshot()) return;
            if (historyPast[historyPast.length - 1] !== value) {
                historyPast.push(value);
            }
            historyPast = historyPast.slice(-80);
            historyFuture = [];
        }

        function captureHistory() {
            const value = historySnapshot();
            if (historyPast[historyPast.length - 1] !== value) {
                historyPast.push(value);
            }
            historyPast = historyPast.slice(-80);
            historyFuture = [];
        }

        function restoreHistory(snapshot) {
            try {
                slides = normalizeSlides(JSON.parse(snapshot));
                slideIndex = Math.min(slideIndex, slides.length - 1);
                layerIndex = slides[slideIndex].layers.length
                    ? Math.min(Math.max(0, layerIndex), slides[slideIndex].layers.length - 1)
                    : -1;
                render();
            } catch (_error) {
                return;
            }
        }

        function undoHistory() {
            if (!historyPast.length) return;
            historyFuture.push(historySnapshot());
            restoreHistory(historyPast.pop());
        }

        function redoHistory() {
            if (!historyFuture.length) return;
            historyPast.push(historySnapshot());
            restoreHistory(historyFuture.pop());
        }

        function currentSlide() {
            return slides[Math.max(0, Math.min(slides.length - 1, slideIndex))];
        }

        function currentLayer() {
            const slide = currentSlide();
            return layerIndex >= 0 && slide.layers[layerIndex] ? slide.layers[layerIndex] : null;
        }

        function syncJson() {
            textarea.value = JSON.stringify(slides, null, 2);
            textarea.dispatchEvent(new Event("input", { bubbles: true }));
        }

        function responsiveKey(base) {
            return base + devices[device].suffix;
        }

        function responsiveValue(layer, base, fallback) {
            const direct = layer[responsiveKey(base)];
            if (direct !== undefined && direct !== null && direct !== "") return direct;
            if (device === "mobile") {
                const tablet = layer[base + "_tablet"];
                if (tablet !== undefined && tablet !== null && tablet !== "") return tablet;
            }
            return layer[base] !== undefined && layer[base] !== null && layer[base] !== "" ? layer[base] : fallback;
        }

        function layerHidden(layer) {
            return Boolean(layer["hide_" + device]);
        }

        function canvasLayer(layer, index) {
            const x = clamp(responsiveValue(layer, "x", 8), -100, 200);
            const y = clamp(responsiveValue(layer, "y", 12), -100, 200);
            const width = clamp(responsiveValue(layer, "width", 70), 1, 200);
            const font = clamp(responsiveValue(layer, "font_size", 20), 8, 240);
            const height = clamp(layer.height || 0, 0, 200);
            const background = layer.background || "transparent";
            const style = [
                "left:" + x + "%",
                "top:" + y + "%",
                "width:" + width + "%",
                height ? "min-height:" + height + "%" : "",
                "font-size:" + font + "px",
                "color:" + (layer.color || "#ffffff"),
                "background:" + background,
                "opacity:" + clamp(layer.opacity == null ? 1 : layer.opacity, 0, 1),
                "border-radius:" + clamp(layer.radius || 0, 0, 200) + "px",
                "padding:" + clamp(layer.padding || 0, 0, 160) + "px",
                "text-align:" + (layer.align || "left"),
                "z-index:" + clamp(layer.z || index + 2, 0, 500),
                "--h4bs-editor-layer-delay:" + clamp(layer.delay || 0, 0, 60000) + "ms",
                "--h4bs-editor-layer-duration:" + clamp(layer.duration || 650, 0, 60000) + "ms",
            ].filter(Boolean).join(";");
            let content = escapeHtml(layer.content || (layer.type === "image" ? "Image" : layer.type));
            if (layer.type === "image" && layer.src) content = '<img src="' + escapeHtml(layer.src) + '" alt="' + escapeHtml(layer.alt || "") + '">';
            if (layer.type === "video" && layer.src) content = '<video muted playsinline src="' + escapeHtml(layer.src) + '"></video>';
            if (layer.type === "shape") content = "";
            if (layer.type === "button") content = '<span class="h4bs-slider-canvas__button">' + content + "</span>";
            return '<div class="h4bs-slider-canvas__layer is-' + escapeHtml(layer.type || "text") + (index === layerIndex ? " is-selected" : "") + (layerHidden(layer) ? " is-device-hidden" : "") + '" data-layer-index="' + index + '" data-entrance="' + escapeHtml(layer.entrance || "none") + '" style="' + escapeHtml(style) + '" tabindex="0" role="button" aria-label="Edit ' + escapeHtml(layer.type || "text") + ' layer">' + content + '<span class="h4bs-slider-canvas__handle" data-resize-layer aria-hidden="true"></span></div>';
        }

        function canvasMarkup() {
            const slide = currentSlide();
            const media = slide.source === "video" ? (slide.video_poster || "") : (slide.image_url || "");
            const style = [
                "--h4bs-editor-bg:" + (slide.background_color || "#090909"),
                "--h4bs-editor-overlay:" + (slide.background_overlay || "#000000"),
                "--h4bs-editor-overlay-opacity:" + clamp(slide.background_overlay_opacity || 0, 0, 1),
                "--h4bs-editor-fit:" + (slide.background_fit || "cover"),
                "--h4bs-editor-position-x:" + clamp(slide.background_position_x == null ? 50 : slide.background_position_x, 0, 100) + "%",
                "--h4bs-editor-position-y:" + clamp(slide.background_position_y == null ? 50 : slide.background_position_y, 0, 100) + "%",
                "max-width:" + devices[device].width,
            ].join(";");
            return '<div class="h4bs-slider-canvas-wrap"><div class="h4bs-slider-canvas" data-slider-canvas style="' + escapeHtml(style) + '">' +
                (media ? '<img class="h4bs-slider-canvas__background" src="' + escapeHtml(media) + '" alt="">' : "") +
                '<span class="h4bs-slider-canvas__overlay" aria-hidden="true"></span>' +
                slide.layers.map(canvasLayer).join("") +
                '<p class="h4bs-slider-canvas__empty"' + (slide.layers.length ? " hidden" : "") + '>Add a heading, button, image, or shape to begin.</p>' +
                '</div><p class="h4bs-slider-builder__canvas-help">Drag a selected layer to position it. Arrow keys move it one percent; hold Shift for five percent.</p></div>';
        }

        function slideInspector() {
            const slide = currentSlide();
            return '<section class="h4bs-slider-inspector" aria-labelledby="h4bs-slide-settings-title"><h3 id="h4bs-slide-settings-title">Slide settings</h3>' +
                field("Editor label", input("bind-slide=\"title\"", slide.title || "", "text"), "Used in the slide list; visible legacy content remains compatible.") +
                field("Background source", select("bind-slide=\"source\"", slide.source || "image", [["image", "Image"], ["video", "Video"], ["color", "Color"], ["snapshot", "Website snapshot"]])) +
                field("Background image URL", '<span class="h4bs-slider-field__media" data-h4bs-image-profile="slider">' + input("bind-slide=\"image_url\" data-h4bs-image-profile=\"slider\"", slide.image_url || "", "url") + '<button type="button" class="button" data-media-target="slide.image_url">Choose</button></span>') +
                field("Background image alternative text", input("bind-slide=\"background_alt\"", slide.background_alt || "", "text"), "Leave empty when the background is decorative and the same message is present in text layers.") +
                field("Background video URL", '<span class="h4bs-slider-field__media">' + input("bind-slide=\"video_url\"", slide.video_url || "", "url") + '<button type="button" class="button" data-media-target="slide.video_url">Choose</button></span>') +
                field("Video poster URL", '<span class="h4bs-slider-field__media" data-h4bs-image-profile="video_poster">' + input("bind-slide=\"video_poster\" data-h4bs-image-profile=\"video_poster\"", slide.video_poster || "", "url") + '<button type="button" class="button" data-media-target="slide.video_poster">Choose</button></span>') +
                field("Background color", input("bind-slide=\"background_color\"", slide.background_color || "#090909", "color")) +
                field("Media fit", select("bind-slide=\"background_fit\"", slide.background_fit || "cover", [["cover", "Cover"], ["contain", "Contain"], ["fill", "Stretch"], ["none", "Original size"]])) +
                '<div class="h4bs-slider-field-row">' + field("Position X %", input("bind-slide=\"background_position_x\"", slide.background_position_x == null ? 50 : slide.background_position_x, "number", 'min="0" max="100"')) + field("Position Y %", input("bind-slide=\"background_position_y\"", slide.background_position_y == null ? 50 : slide.background_position_y, "number", 'min="0" max="100"')) + '</div>' +
                '<div class="h4bs-slider-field-row">' + field("Overlay", input("bind-slide=\"background_overlay\"", slide.background_overlay || "#000000", "color")) + field("Overlay opacity", input("bind-slide=\"background_overlay_opacity\"", slide.background_overlay_opacity == null ? 0.35 : slide.background_overlay_opacity, "number", 'min="0" max="1" step="0.05"')) + '</div>' +
                field("Slide duration (ms)", input("bind-slide=\"duration\"", slide.duration || 0, "number", 'min="0" max="120000" step="100"'), "Zero uses the slider shortcode autoplay duration.") +
                '</section>';
        }

        function contrastRatio(foreground, background) {
            function luminance(hex) {
                const value = String(hex || "").replace("#", "");
                if (!/^[0-9a-f]{6}$/i.test(value)) return null;
                const channels = [0, 2, 4].map(function (offset) {
                    const channel = parseInt(value.slice(offset, offset + 2), 16) / 255;
                    return channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);
                });
                return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
            }
            const a = luminance(foreground);
            const b = luminance(background);
            if (a == null || b == null) return null;
            return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
        }

        function layerInspector() {
            const layer = currentLayer();
            if (!layer) return '<section class="h4bs-slider-inspector"><h3>Layer settings</h3><p>Select a layer on the canvas or add one below.</p></section>';
            const xKey = responsiveKey("x");
            const yKey = responsiveKey("y");
            const widthKey = responsiveKey("width");
            const fontKey = responsiveKey("font_size");
            const hideKey = "hide_" + device;
            const ratio = contrastRatio(layer.color || "#ffffff", layer.background || currentSlide().background_color || "#090909");
            const contrast = ratio == null ? "Contrast depends on the background media and overlay." : (ratio >= 7 ? "AAA text contrast: " : (ratio >= 4.5 ? "AA text contrast: " : "Low text contrast: ")) + ratio.toFixed(2) + ":1";
            return '<section class="h4bs-slider-inspector" aria-labelledby="h4bs-layer-settings-title"><h3 id="h4bs-layer-settings-title">' + escapeHtml(devices[device].label) + ' layer settings</h3>' +
                field("Layer type", select("bind-layer=\"type\"", layer.type || "text", layerTypes.map(function (type) { return [type, type.charAt(0).toUpperCase() + type.slice(1)]; }))) +
                field("Text / value", '<textarea rows="3" data-bind-layer="content">' + escapeHtml(layer.content || "") + '</textarea>') +
                (layer.type === "button" ? field("Link / CTA URL", input("bind-layer=\"url\"", layer.url || "", "url")) : "") +
                (["image", "video"].indexOf(layer.type) >= 0 ? field("Media URL", '<span class="h4bs-slider-field__media"' + (layer.type === "image" ? ' data-h4bs-image-profile="content"' : "") + '>' + input("bind-layer=\"src\"" + (layer.type === "image" ? " data-h4bs-image-profile=\"content\"" : ""), layer.src || "", "url") + '<button type="button" class="button" data-media-target="layer.src">Choose</button></span>') + field("Alternative text / video label", input("bind-layer=\"alt\"", layer.alt || "", "text"), "Leave image alt empty only when it is decorative.") : "") +
                '<div class="h4bs-slider-field-row">' + field("X %", input('bind-layer="' + escapeHtml(xKey) + '"', responsiveValue(layer, "x", 8), "number", 'min="-100" max="200" step="0.5"')) + field("Y %", input('bind-layer="' + escapeHtml(yKey) + '"', responsiveValue(layer, "y", 12), "number", 'min="-100" max="200" step="0.5"')) + '</div>' +
                '<div class="h4bs-slider-field-row">' + field("Width %", input('bind-layer="' + escapeHtml(widthKey) + '"', responsiveValue(layer, "width", 70), "number", 'min="1" max="200" step="0.5"')) + field("Font size px", input('bind-layer="' + escapeHtml(fontKey) + '"', responsiveValue(layer, "font_size", 20), "number", 'min="8" max="240"')) + '</div>' +
                field("Hide on " + devices[device].label.toLowerCase(), '<input type="checkbox" data-bind-layer="' + hideKey + '"' + (layer[hideKey] ? " checked" : "") + '>') +
                '<div class="h4bs-slider-field-row">' + field("Text color", input("bind-layer=\"color\"", layer.color || "#ffffff", "color")) + field("Background", input("bind-layer=\"background\"", layer.background || "#000000", "color")) + '</div>' +
                '<p class="h4bs-slider-contrast' + (ratio != null && ratio < 7 ? " is-warning" : " is-pass") + '" role="status">' + escapeHtml(contrast) + '</p>' +
                field("Text alignment", select("bind-layer=\"align\"", layer.align || "left", [["left", "Left"], ["center", "Center"], ["right", "Right"]])) +
                '<div class="h4bs-slider-field-row">' + field("Padding px", input("bind-layer=\"padding\"", layer.padding || 0, "number", 'min="0" max="160"')) + field("Corner radius px", input("bind-layer=\"radius\"", layer.radius || 0, "number", 'min="0" max="200"')) + '</div>' +
                field("Entrance", select("bind-layer=\"entrance\"", layer.entrance || "fade_up", [["none", "None"], ["fade", "Fade"], ["fade_up", "Fade up"], ["fade_down", "Fade down"], ["fade_left", "Fade left"], ["fade_right", "Fade right"], ["zoom", "Zoom"], ["rotate", "Rotate"], ["blur", "Blur"]])) +
                '<div class="h4bs-slider-field-row">' + field("Delay ms", input("bind-layer=\"delay\"", layer.delay || 0, "number", 'min="0" max="60000" step="50"')) + field("Duration ms", input("bind-layer=\"duration\"", layer.duration || 650, "number", 'min="0" max="60000" step="50"')) + '</div>' +
                field("Easing", select("bind-layer=\"easing\"", layer.easing || "ease_out", [["linear", "Linear"], ["ease", "Ease"], ["ease_in", "Ease in"], ["ease_out", "Ease out"], ["ease_in_out", "Ease in/out"], ["spring", "Spring"]])) +
                '<div class="h4bs-slider-layer-actions"><button type="button" class="button" data-layer-action="duplicate">Duplicate layer</button><button type="button" class="button" data-layer-action="back">Move back</button><button type="button" class="button" data-layer-action="forward">Move forward</button><button type="button" class="button button-link-delete" data-layer-action="delete">Delete layer</button></div>' +
                '</section>';
        }

        function updateWorkspaceChrome() {
            root.classList.toggle("is-workspace-expanded", workspaceExpanded);
            root.classList.toggle("is-settings-collapsed", settingsCollapsed);
            document.body.classList.toggle("h4bs-slider-workspace-expanded", workspaceExpanded);

            const workspaceToggle = app.querySelector("[data-slider-toggle-workspace]");
            if (workspaceToggle) {
                workspaceToggle.setAttribute("aria-pressed", workspaceExpanded ? "true" : "false");
                workspaceToggle.setAttribute("title", workspaceExpanded ? "Restore WordPress Editor" : "Expand Slider Workspace");
                workspaceToggle.innerHTML = '<span class="dashicons ' + (workspaceExpanded ? "dashicons-editor-contract" : "dashicons-editor-expand") + '" aria-hidden="true"></span><span>' + (workspaceExpanded ? "Restore editor" : "Full workspace") + "</span>";
            }

            const settingsToggle = app.querySelector("[data-slider-toggle-settings]");
            if (settingsToggle) {
                settingsToggle.setAttribute("aria-expanded", settingsCollapsed ? "false" : "true");
                settingsToggle.setAttribute("title", settingsCollapsed ? "Show Slide and Layer Settings" : "Hide Slide and Layer Settings");
                settingsToggle.innerHTML = '<span class="dashicons ' + (settingsCollapsed ? "dashicons-arrow-left-alt2" : "dashicons-arrow-right-alt2") + '" aria-hidden="true"></span><span>' + (settingsCollapsed ? "Show settings" : "Hide settings") + "</span>";
            }
        }

        function render() {
            slideIndex = Math.max(0, Math.min(slides.length - 1, slideIndex));
            const slide = currentSlide();
            layerIndex = Math.max(-1, Math.min(slide.layers.length - 1, layerIndex));
            app.innerHTML = '<div class="h4bs-slider-builder__toolbar">' +
                '<div class="h4bs-slider-builder__slides" role="list" aria-label="Slides">' + slides.map(function (item, index) {
                    return '<button type="button" role="listitem" class="h4bs-slider-slide-chip' + (index === slideIndex ? " is-active" : "") + '" data-slide-index="' + index + '" aria-current="' + (index === slideIndex ? "true" : "false") + '"><span>' + (index + 1) + '</span>' + escapeHtml(item.title || "Untitled slide") + "</button>";
                }).join("") + '</div>' +
                '<div class="h4bs-slider-builder__slide-actions"><button type="button" class="button" data-history-action="undo"' + (historyPast.length ? "" : " disabled") + '>Undo</button><button type="button" class="button" data-history-action="redo"' + (historyFuture.length ? "" : " disabled") + '>Redo</button><button type="button" class="button button-primary" data-slide-action="add">Add slide</button><button type="button" class="button" data-slide-action="duplicate">Duplicate</button><button type="button" class="button" data-slide-action="previous" aria-label="Move slide earlier">&#8592;</button><button type="button" class="button" data-slide-action="next" aria-label="Move slide later">&#8594;</button><button type="button" class="button button-link-delete" data-slide-action="delete">Delete</button></div>' +
                '</div><div class="h4bs-slider-builder__devicebar"><div role="group" aria-label="Preview device">' + Object.keys(devices).map(function (key) { return '<button type="button" class="button' + (device === key ? " is-active" : "") + '" data-device="' + key + '" aria-pressed="' + (device === key ? "true" : "false") + '">' + devices[key].label + "</button>"; }).join("") + '</div><div class="h4bs-slider-builder__workspace-actions"><button type="button" class="button" data-preview-animation>Preview entrance</button><button type="button" class="button" data-slider-toggle-settings aria-controls="h4bs-slider-settings"></button><button type="button" class="button button-primary" data-slider-toggle-workspace></button></div></div>' +
                '<div class="h4bs-slider-builder__workspace"><main class="h4bs-slider-builder__stage">' + canvasMarkup() + '<div class="h4bs-slider-builder__add-layer"><span>Add layer:</span>' + ["heading", "text", "button", "image", "shape"].map(function (type) { return '<button type="button" class="button" data-add-layer="' + type + '">' + type.charAt(0).toUpperCase() + type.slice(1) + "</button>"; }).join("") + '<select aria-label="More layer types" data-add-layer-select><option value="">More...</option>' + ["icon", "video", "counter", "progress", "html"].map(function (type) { return option(type, type.charAt(0).toUpperCase() + type.slice(1), ""); }).join("") + '</select></div></main><aside id="h4bs-slider-settings" class="h4bs-slider-builder__panels">' + slideInspector() + layerInspector() + '</aside></div>';
            updateWorkspaceChrome();
            syncJson();
        }

        function renderCanvasOnly() {
            const old = app.querySelector(".h4bs-slider-canvas-wrap");
            if (!old) return;
            const holder = document.createElement("div");
            holder.innerHTML = canvasMarkup();
            old.replaceWith(holder.firstElementChild);
            syncJson();
        }

        function addLayer(type) {
            captureHistory();
            const layer = defaultLayer(type);
            currentSlide().layers.push(layer);
            layerIndex = currentSlide().layers.length - 1;
            render();
        }

        function chooseMedia(target) {
            if (!window.wp || !wp.media) return;
            const frame = wp.media({ title: copy.mediaTitle || "Choose slider media", button: { text: copy.mediaButton || "Use this media" }, multiple: false });
            frame.on("select", function () {
                const attachment = frame.state().get("selection").first().toJSON();
                captureHistory();
                if (target.indexOf("slide.") === 0) {
                    const key = target.slice(6);
                    currentSlide()[key] = attachment.url || "";
                    if (key === "image_url") currentSlide().image_id = attachment.id || 0;
                } else if (target.indexOf("layer.") === 0 && currentLayer()) {
                    currentLayer()[target.slice(6)] = attachment.url || "";
                }
                render();
            });
            frame.open();
        }

        app.addEventListener("click", function (event) {
            const target = event.target.closest("button, [data-layer-index]");
            if (!target || !app.contains(target)) return;
            if (target.hasAttribute("data-history-action")) {
                if (target.getAttribute("data-history-action") === "undo") undoHistory();
                if (target.getAttribute("data-history-action") === "redo") redoHistory();
                return;
            }
            if (target.hasAttribute("data-slide-index")) {
                slideIndex = Number(target.getAttribute("data-slide-index"));
                layerIndex = currentSlide().layers.length ? 0 : -1;
                render();
                return;
            }
            if (target.hasAttribute("data-device")) {
                device = target.getAttribute("data-device");
                render();
                return;
            }
            if (target.hasAttribute("data-add-layer")) {
                addLayer(target.getAttribute("data-add-layer"));
                return;
            }
            if (target.hasAttribute("data-media-target")) {
                chooseMedia(target.getAttribute("data-media-target"));
                return;
            }
            if (target.hasAttribute("data-preview-animation")) {
                const canvas = app.querySelector("[data-slider-canvas]");
                if (canvas) {
                    canvas.classList.remove("is-previewing");
                    void canvas.offsetWidth;
                    canvas.classList.add("is-previewing");
                }
                return;
            }
            if (target.hasAttribute("data-slider-toggle-settings")) {
                settingsCollapsed = !settingsCollapsed;
                persistStoredBoolean(SETTINGS_STORAGE_KEY, settingsCollapsed);
                updateWorkspaceChrome();
                return;
            }
            if (target.hasAttribute("data-slider-toggle-workspace")) {
                workspaceExpanded = !workspaceExpanded;
                updateWorkspaceChrome();
                return;
            }
            if (target.hasAttribute("data-layer-index")) {
                layerIndex = Number(target.getAttribute("data-layer-index"));
                render();
                return;
            }
            const slideAction = target.getAttribute("data-slide-action");
            if (slideAction) {
                captureHistory();
                if (slideAction === "add") { slides.push(defaultSlide()); slideIndex = slides.length - 1; layerIndex = 0; }
                if (slideAction === "duplicate") { slides.splice(slideIndex + 1, 0, JSON.parse(JSON.stringify(currentSlide()))); slideIndex += 1; }
                if (slideAction === "delete" && slides.length > 1 && window.confirm(copy.confirmDeleteSlide || "Delete this slide?")) { slides.splice(slideIndex, 1); slideIndex = Math.min(slideIndex, slides.length - 1); layerIndex = 0; }
                if (slideAction === "previous" && slideIndex > 0) { const item = slides.splice(slideIndex, 1)[0]; slides.splice(slideIndex - 1, 0, item); slideIndex -= 1; }
                if (slideAction === "next" && slideIndex < slides.length - 1) { const item = slides.splice(slideIndex, 1)[0]; slides.splice(slideIndex + 1, 0, item); slideIndex += 1; }
                render();
                return;
            }
            const layerAction = target.getAttribute("data-layer-action");
            if (layerAction && currentLayer()) {
                captureHistory();
                const layers = currentSlide().layers;
                if (layerAction === "duplicate") { layers.splice(layerIndex + 1, 0, JSON.parse(JSON.stringify(currentLayer()))); layers[layerIndex + 1].id = uniqueId(layers[layerIndex + 1].type); layerIndex += 1; }
                if (layerAction === "delete" && window.confirm(copy.confirmDeleteLayer || "Delete this layer?")) { layers.splice(layerIndex, 1); layerIndex = Math.min(layerIndex, layers.length - 1); }
                if (layerAction === "back" && layerIndex > 0) { const item = layers.splice(layerIndex, 1)[0]; layers.splice(layerIndex - 1, 0, item); layerIndex -= 1; }
                if (layerAction === "forward" && layerIndex < layers.length - 1) { const item = layers.splice(layerIndex, 1)[0]; layers.splice(layerIndex + 1, 0, item); layerIndex += 1; }
                layers.forEach(function (layer, index) { layer.z = index + 2; });
                render();
            }
        });

        app.addEventListener("change", function (event) {
            const target = event.target;
            if (!(target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement)) return;
            if (target.hasAttribute("data-add-layer-select")) { if (target.value) addLayer(target.value); return; }
            const slideKey = target.getAttribute("data-bind-slide");
            const layerKey = target.getAttribute("data-bind-layer");
            const value = target.type === "checkbox" ? target.checked : (target.type === "number" ? number(target.value, 0) : target.value);
            if (slideKey) currentSlide()[slideKey] = value;
            if (layerKey && currentLayer()) currentLayer()[layerKey] = value;
            pushHistory(fieldStartSnapshot);
            fieldStartSnapshot = "";
            render();
        });

        app.addEventListener("focusin", function (event) {
            const target = event.target;
            if (!(target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement)) return;
            if (target.hasAttribute("data-bind-slide") || target.hasAttribute("data-bind-layer")) {
                fieldStartSnapshot = historySnapshot();
            }
        });

        app.addEventListener("input", function (event) {
            const target = event.target;
            if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) return;
            const slideKey = target.getAttribute("data-bind-slide");
            const layerKey = target.getAttribute("data-bind-layer");
            const value = target.type === "checkbox" ? target.checked : (target.type === "number" ? number(target.value, 0) : target.value);
            if (slideKey) currentSlide()[slideKey] = value;
            if (layerKey && currentLayer()) currentLayer()[layerKey] = value;
            renderCanvasOnly();
        });

        app.addEventListener("pointerdown", function (event) {
            const layerElement = event.target.closest("[data-layer-index]");
            const canvas = event.target.closest("[data-slider-canvas]");
            if (!layerElement || !canvas || event.button !== 0) return;
            layerIndex = Number(layerElement.getAttribute("data-layer-index"));
            const layer = currentLayer();
            if (!layer) return;
            captureHistory();
            const bounds = canvas.getBoundingClientRect();
            drag = {
                pointerId: event.pointerId,
                canvas: canvas,
                bounds: bounds,
                layer: layer,
                mode: event.target.closest("[data-resize-layer]") ? "resize" : "move",
                startX: event.clientX,
                startY: event.clientY,
                startWidth: number(responsiveValue(layer, "width", 70), 70),
                startHeight: number(layer.height || 0, 0),
                startLayerX: number(responsiveValue(layer, "x", 8), 8),
                startLayerY: number(responsiveValue(layer, "y", 12), 12),
            };
            layerElement.setPointerCapture(event.pointerId);
            event.preventDefault();
        });

        app.addEventListener("pointermove", function (event) {
            if (!drag || event.pointerId !== drag.pointerId) return;
            if (drag.mode === "resize") {
                const width = drag.startWidth + ((event.clientX - drag.startX) / drag.bounds.width) * 100;
                const height = drag.startHeight + ((event.clientY - drag.startY) / drag.bounds.height) * 100;
                drag.layer[responsiveKey("width")] = Math.round(clamp(width, 1, 200) * 10) / 10;
                drag.layer.height = Math.round(clamp(height, 0, 200) * 10) / 10;
            } else {
                const x = clamp(drag.startLayerX + ((event.clientX - drag.startX) / drag.bounds.width) * 100, -100, 200);
                const y = clamp(drag.startLayerY + ((event.clientY - drag.startY) / drag.bounds.height) * 100, -100, 200);
                drag.layer[responsiveKey("x")] = Math.round(x * 10) / 10;
                drag.layer[responsiveKey("y")] = Math.round(y * 10) / 10;
            }
            renderCanvasOnly();
        });

        app.addEventListener("pointerup", function (event) {
            if (!drag || event.pointerId !== drag.pointerId) return;
            drag = null;
            render();
        });

        app.addEventListener("keydown", function (event) {
            const layerElement = event.target.closest("[data-layer-index]");
            if (!layerElement || ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].indexOf(event.key) < 0) return;
            layerIndex = Number(layerElement.getAttribute("data-layer-index"));
            const layer = currentLayer();
            if (!layer) return;
            captureHistory();
            const step = event.shiftKey ? 5 : 1;
            const xKey = responsiveKey("x");
            const yKey = responsiveKey("y");
            layer[xKey] = number(responsiveValue(layer, "x", 8), 8) + (event.key === "ArrowLeft" ? -step : (event.key === "ArrowRight" ? step : 0));
            layer[yKey] = number(responsiveValue(layer, "y", 12), 12) + (event.key === "ArrowUp" ? -step : (event.key === "ArrowDown" ? step : 0));
            event.preventDefault();
            renderCanvasOnly();
            const replacement = app.querySelector('[data-layer-index="' + layerIndex + '"]');
            if (replacement) replacement.focus();
        });

        document.addEventListener("keydown", function (event) {
            if ((event.ctrlKey || event.metaKey) && !event.altKey) {
                const key = String(event.key || "").toLowerCase();
                if (key === "z") {
                    event.preventDefault();
                    event.shiftKey ? redoHistory() : undoHistory();
                    return;
                }
                if (key === "y") {
                    event.preventDefault();
                    redoHistory();
                    return;
                }
            }
            if (event.key !== "Escape" || !workspaceExpanded) return;
            workspaceExpanded = false;
            updateWorkspaceChrome();
            const toggle = app.querySelector("[data-slider-toggle-workspace]");
            if (toggle) toggle.focus();
        });

        window.addEventListener("pagehide", function () {
            document.body.classList.remove("h4bs-slider-workspace-expanded");
        });

        const applyJson = root.querySelector("[data-h4bs-slider-apply-json]");
        if (applyJson) {
            applyJson.addEventListener("click", function () {
                try {
                    captureHistory();
                    slides = normalizeSlides(JSON.parse(textarea.value || "[]"));
                    slideIndex = 0;
                    layerIndex = slides[0].layers.length ? 0 : -1;
                    if (jsonStatus) jsonStatus.textContent = "JSON applied to the canvas.";
                    render();
                } catch (error) {
                    if (jsonStatus) jsonStatus.textContent = "JSON is invalid. Nothing was changed.";
                }
            });
        }

        if (preserveInvalidJson) {
            app.innerHTML = '<div class="notice notice-error inline"><p>The saved slider JSON is invalid. It has not been overwritten.</p><p><button type="button" class="button button-primary" data-h4bs-slider-start-new>Start a new slider</button></p></div>';
            const startNew = app.querySelector("[data-h4bs-slider-start-new]");
            if (startNew) {
                startNew.addEventListener("click", function () {
                    preserveInvalidJson = false;
                    slides = [defaultSlide()];
                    slideIndex = 0;
                    layerIndex = 0;
                    render();
                });
            }
            return;
        }

        render();
    }

    document.addEventListener("DOMContentLoaded", function () {
        document.querySelectorAll("[data-h4bs-slider-builder]").forEach(initBuilder);
    });
})();
