/**
 * Cast Conductor Proprietary License v5
 * SPDX-License-Identifier: LicenseRef-CastConductor-Proprietary-v5
 *
 * Copyright (c) 2025 CastConductor.com. All Rights Reserved.
 *
 * This file is part of Cast Conductor ("Software"). The Software and its source
 * code constitute proprietary, confidential, and trade secret information of
 * CastConductor.com ("Company"). Any access or use is governed strictly by the
 * Cast Conductor Proprietary License v5 ("License"). By installing, copying,
 * accessing, compiling, or otherwise using the Software you agree to be bound by
 * all terms of the License. If you do not agree, you must cease use immediately.
 *
 * Key Terms (Summary – see full License for binding terms):
 *  1. No Redistribution: You may not publish, distribute, sublicense, rent,
 *     lease, transfer, sell, or otherwise make the Software (or any derivative)
 *     available to any third party without prior written consent of Company.
 *  2. No Modification: Modification, reverse engineering, decompilation, or
 *     disassembly is prohibited except to the limited extent expressly permitted
 *     by applicable law that cannot be contractually waived.
 *  3. Confidentiality: Treat all source code and related artifacts as Company
 *     Confidential Information. Maintain at least the same degree of care as for
 *     your own confidential materials, and not less than reasonable care.
 *  4. No Patent License: No express or implied patent rights are granted. Future
 *     patents (if any) are fully reserved.
 *  5. No Trademark License: Company names, marks, and logos may not be used
 *     without prior written permission.
 *  6. Limited Internal Use: Use is limited solely to internal evaluation and
 *     operation of licensed Cast Conductor deployments. Commercial hosting or
 *     resale as a service requires a separate written agreement.
 *  7. Telemetry & License Validation: The Software may periodically transmit a
 *     hashed installation identifier, domain (or site ID), plugin/app version,
 *     and a truncated (non-reversible) fragment of the license key solely to
 *     validate activation status and enforce licensing. This minimal "phone home"
 *     check contains no personal or content data. If optional telemetry is later
 *     introduced it will be limited to aggregate operational metrics (no PII),
 *     fully documented, and optionally disableable per published instructions.
 *  8. Third-Party Components: The Software may include open source components
 *     covered by their own licenses. See THIRD-PARTY-NOTICES.md. Those licenses
 *     govern their respective components; this License governs all remaining code.
 *  9. Export Compliance: You are responsible for compliance with all applicable
 *     export control and sanctions laws.
 * 10. Warranty Disclaimer: THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF
 *     ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO MERCHANTABILITY,
 *     FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT.
 * 11. Limitation of Liability: IN NO EVENT WILL COMPANY OR AUTHORS BE LIABLE FOR
 *     ANY INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE
 *     DAMAGES, OR LOST PROFITS, EVEN IF ADVISED OF THE POSSIBILITY.
 * 12. Acceptance: Use of the Software constitutes acceptance of the License.
 * 13. Enforcement: Unauthorized reproduction or distribution may result in civil
 *     and criminal penalties and will be prosecuted to the maximum extent allowed
 *     by law.
 *
 * Authoritative EULA: EULA-v5.1.md (repository root – private) and https://castconductor.com/eula
 * Precedence: If this summary conflicts with the EULA, the EULA governs.
 * Revision: Current EULA revision v5.1 (subject to update; check EULA for current enterprise thresholds).
 *
 * Full License text available from: licensing@castconductor.com
 * Security reports: security@castconductor.com
 * Commercial inquiries: licensing@castconductor.com
 *
 * END OF HEADER
 *
 * background-layers.js
 * Layered background management (image / gradient / overlay / color) with ordering & optimistic styling.
 */

import { invalidatePreviewHash } from './preview-glue.js';
import { syncBackgroundLayersToHydrated } from './legacy-layer-hydration.js';

export function composeBackgroundLayers(cfg){
    const bg = (cfg && cfg.background) ? cfg.background : {};
    const layers = Array.isArray(bg.layers) ? bg.layers.filter(l=>l && l.kind && (l.enabled!==false)) : [];
    if (!layers.length) return { backgroundImage:'', backgroundColor: bg.color || 'transparent' };
    const parts = [];
    const sizes = []; const positions = []; const repeats = [];
    layers.forEach(layer => {
        switch(layer.kind){
            case 'overlay': {
                const ov = cfg.overlay || {}; if (typeof ov.opacity === 'number' && ov.opacity > 0 && ov.color){
                    let c = ov.color;
                    if (c.startsWith('#')) {
                        const hx = c.substring(1); const v = hx.length===3 ? hx.split('').map(c2=>c2+c2).join('') : hx;
                        const r=parseInt(v.substring(0,2),16), g=parseInt(v.substring(2,4),16), b=parseInt(v.substring(4,6),16);
                        c = `rgba(${r}, ${g}, ${b}, ${ov.opacity})`;
                    } else if (/^rgb\(/i.test(c)) {
                        c = c.replace(/rgb\(/i,'rgba(').replace(/\)$/i,`, ${ov.opacity})`);
                    }
                    parts.push(`linear-gradient(0deg, ${c}, ${c})`);
                }
                break; }
            case 'gradient': {
                const colors = (bg.gradient_colors || []).filter(Boolean);
                if (colors.length >= 2) {
                    const dir = bg.gradient_direction || '135deg';
                    parts.push(`linear-gradient(${dir}, ${colors.join(', ')})`);
                }
                break; }
            case 'color': {
                // Represent a solid color layer via a flat gradient for stacking
                const c = layer.color || bg.color || '#000000';
                parts.push(`linear-gradient(0deg, ${c}, ${c})`);
                break; }
            case 'image': {
                const image = layer.image || bg.image;
                if (image && image.src) {
                    parts.push(`url('${image.src}')`);
                    // Map fit/position/repeat to CSS lists; defaults if unspecified
                    const fit = image.fit || 'cover';
                    const pos = image.position || 'center';
                    const rep = image.repeat || 'no-repeat';
                    sizes.push(fit);
                    positions.push(pos);
                    repeats.push(rep);
                }
                break; }
            default: break;
        }
    });
    const result = { backgroundImage: parts.join(', '), backgroundColor: 'transparent' };
    if (sizes.length) result.backgroundSize = sizes.join(', ');
    if (positions.length) result.backgroundPosition = positions.join(', ');
    if (repeats.length) result.backgroundRepeat = repeats.join(', ');
    return result;
}

export function attachBackgroundLayers(editor){
    if (editor.__bgLayersAttached) return; editor.__bgLayersAttached = true;
    // Ensure structure exists
    const ensure = () => {
        if (!editor.currentConfig) editor.currentConfig = editor.getDefaultConfig();
        const bg = editor.currentConfig.background || (editor.currentConfig.background = {});
        // Seed or repair the layers list based on current config
        if (!Array.isArray(bg.layers)) bg.layers = [];
        // Ensure stable IDs on all existing layers
        bg.layers.forEach(l=>{ if (l && !l._id) l._id = crypto.randomUUID?.()||Date.now()+''+Math.random(); });
        // If overlay config is active but not represented in layers, add it (topmost)
        try {
            const ov = editor.currentConfig.overlay || {};
            const hasOverlayLayer = bg.layers.some(l=>l && l.kind==='overlay');
            if (typeof ov.opacity === 'number' && ov.opacity > 0 && !hasOverlayLayer) {
                bg.layers.unshift({ _id:crypto.randomUUID?.()||Date.now()+''+Math.random(), kind:'overlay', enabled:true });
            }
        } catch(_) {}
        // If gradient background selected but no gradient layer entry, add one
        try {
            const hasGradientLayer = bg.layers.some(l=>l && l.kind==='gradient');
            if (bg.type === 'gradient' && !hasGradientLayer) {
                bg.layers.push({ _id:crypto.randomUUID?.()||Date.now()+''+Math.random(), kind:'gradient', enabled:true });
            }
        } catch(_) {}
        // If a single image is configured on background and no image layer exists, add one
        try {
            const hasImageLayer = bg.layers.some(l=>l && l.kind==='image');
            if (bg.image && bg.image.src && !hasImageLayer) {
                bg.layers.push({ _id:crypto.randomUUID?.()||Date.now()+''+Math.random(), kind:'image', enabled:true, image:bg.image });
            }
        } catch(_) {}
    };
    ensure();

    const listEl = document.getElementById('ccve-bg-layer-list');
    if (!listEl) return; // UI not present

    function render(){
        ensure();
        const bg = editor.currentConfig.background;
        listEl.innerHTML = '';
        bg.layers.forEach((layer, idx) => {
            const li = document.createElement('li');
            li.className = 'ccve-bg-layer-item';
            li.style.display = 'flex'; li.style.alignItems='center'; li.style.gap='6px'; li.style.marginBottom='4px';
            li.setAttribute('draggable','true');
            li.style.cursor='grab';
            li.innerHTML = `
                <input type="checkbox" class="ccve-layer-enabled" ${layer.enabled!==false?'checked':''} aria-label="Enable layer ${layer.kind}">
                <span class="ccve-layer-kind" style="min-width:70px;font-size:11px;text-transform:uppercase;">${layer.kind}</span>
                ${layer.kind==='image' ? '<button type="button" class="button button-small ccve-layer-pick-image" title="Select image">Img</button>' : ''}
                <button type="button" class="button button-small ccve-layer-up" title="Move up">↑</button>
                <button type="button" class="button button-small ccve-layer-down" title="Move down">↓</button>
                <button type="button" class="button button-small ccve-layer-remove" title="Remove layer">✕</button>
            `;
            // Events
            li.querySelector('.ccve-layer-enabled').addEventListener('change', e => { layer.enabled = e.target.checked; applyOptimistic(); });
            li.querySelector('.ccve-layer-up').addEventListener('click', () => { if (idx>0){ bg.layers.splice(idx-1,0,bg.layers.splice(idx,1)[0]); syncBackgroundLayersToHydrated(editor); render(); applyOptimistic(); if (typeof editor.buildLayersPanel === 'function') editor.buildLayersPanel(); } });
            li.querySelector('.ccve-layer-down').addEventListener('click', () => { if (idx<bg.layers.length-1){ bg.layers.splice(idx+1,0,bg.layers.splice(idx,1)[0]); syncBackgroundLayersToHydrated(editor); render(); applyOptimistic(); if (typeof editor.buildLayersPanel === 'function') editor.buildLayersPanel(); } });
            if (layer.kind==='image') {
                // Image picker
                li.querySelector('.ccve-layer-pick-image').addEventListener('click', () => pickImage(layer));
                // Insert simple position and repeat dropdowns
                const pos = (layer.image && layer.image.position) || 'center';
                const rep = (layer.image && layer.image.repeat) || 'no-repeat';
                const posSel = document.createElement('select');
                posSel.className = 'ccve-layer-image-position';
                posSel.title = 'Background position';
                posSel.style.cssText = 'font-size:11px;padding:2px 4px;border:1px solid #334155;background:#0f172a;color:#fff;border-radius:4px;';
                posSel.innerHTML = [
                    ['center','Center'],
                    ['left top','Top Left'],
                    ['center top','Top Center'],
                    ['right top','Top Right'],
                    ['left center','Center Left'],
                    ['right center','Center Right'],
                    ['left bottom','Bottom Left'],
                    ['center bottom','Bottom Center'],
                    ['right bottom','Bottom Right']
                ].map(([v,l])=>`<option value="${v}">${l}</option>`).join('');
                posSel.value = pos;
                const repSel = document.createElement('select');
                repSel.className = 'ccve-layer-image-repeat';
                repSel.title = 'Background repeat';
                repSel.style.cssText = 'font-size:11px;padding:2px 4px;border:1px solid #334155;background:#0f172a;color:#fff;border-radius:4px;';
                repSel.innerHTML = [
                    ['no-repeat','No Repeat'],
                    ['repeat','Repeat'],
                    ['repeat-x','Repeat X'],
                    ['repeat-y','Repeat Y']
                ].map(([v,l])=>`<option value="${v}">${l}</option>`).join('');
                repSel.value = rep;
                const controlsWrap = document.createElement('span');
                controlsWrap.style.cssText = 'display:inline-flex;gap:6px;align-items:center;margin-left:4px;';
                const posLbl = document.createElement('span'); posLbl.textContent = 'Pos:'; posLbl.style.fontSize='11px'; posLbl.style.opacity='0.8';
                const repLbl = document.createElement('span'); repLbl.textContent = 'Rpt:'; repLbl.style.fontSize='11px'; repLbl.style.opacity='0.8';
                controlsWrap.appendChild(posLbl); controlsWrap.appendChild(posSel);
                controlsWrap.appendChild(repLbl); controlsWrap.appendChild(repSel);
                li.insertBefore(controlsWrap, li.querySelector('.ccve-layer-up'));
                posSel.addEventListener('change', ()=>{ layer.image = layer.image||{}; layer.image.position = posSel.value; applyOptimistic(); });
                repSel.addEventListener('change', ()=>{ layer.image = layer.image||{}; layer.image.repeat = repSel.value; applyOptimistic(); });
            }
            li.querySelector('.ccve-layer-remove').addEventListener('click', () => {
                const kind = layer.kind;
                bg.layers.splice(idx,1);
                if (kind==='overlay' && editor.currentConfig.overlay) { editor.currentConfig.overlay.opacity = 0; }
                syncBackgroundLayersToHydrated(editor);
                render(); applyOptimistic(true);
                if (typeof editor.buildLayersPanel === 'function') editor.buildLayersPanel();
            });
            // Drag-and-drop handlers
            li.addEventListener('dragstart', ev => {
                ev.dataTransfer.effectAllowed = 'move';
                ev.dataTransfer.setData('text/plain', String(idx));
                li.classList.add('dragging');
            });
            li.addEventListener('dragend', ()=> li.classList.remove('dragging'));
            li.addEventListener('dragover', ev => { ev.preventDefault(); ev.dataTransfer.dropEffect='move'; li.style.outline='1px dashed #888'; });
            li.addEventListener('dragleave', ()=> { li.style.outline='none'; });
            li.addEventListener('drop', ev => {
                ev.preventDefault(); li.style.outline='none';
                const from = parseInt(ev.dataTransfer.getData('text/plain'),10);
                const to = idx;
                if (isNaN(from) || from===to) return;
                const moved = bg.layers.splice(from,1)[0];
                bg.layers.splice(to,0,moved);
                syncBackgroundLayersToHydrated(editor);
                render(); applyOptimistic();
                if (typeof editor.buildLayersPanel === 'function') editor.buildLayersPanel();
            });
            listEl.appendChild(li);
        });
    }

    function pickImage(layer){
        if (!window.wp || !window.wp.media){ alert('Media library not available'); return; }
        const frame = window.wp.media({ title:'Select Background Image', multiple:false });
        frame.on('select', () => {
            const att = frame.state().get('selection').first().toJSON();
            layer.image = { attachment_id: att.id, src: att.sizes?.large?.url || att.url, fit:'cover', position:'center', repeat:'no-repeat' };
            applyOptimistic(); render();
        });
        frame.open();
    }

    function applyOptimistic(forceInvalidate){
        try {
            const comp = composeBackgroundLayers(editor.currentConfig);
            // Ensure background/overlay layers exist in the block stage
            let bgLayer = document.getElementById('canvas-background-layer');
            let ovLayer = document.getElementById('canvas-overlay-layer');
            const stage = document.getElementById('block-stage');
            if (stage) {
                if (!bgLayer) {
                    bgLayer = document.createElement('div');
                    bgLayer.id = 'canvas-background-layer';
                    bgLayer.className = 'canvas-background';
                    bgLayer.style.cssText = 'position:absolute;left:0;top:0;right:0;bottom:0;z-index:0';
                    // Insert as first child so it sits beneath preview/token layers
                    stage.insertBefore(bgLayer, stage.firstChild);
                }
                if (!ovLayer) {
                    ovLayer = document.createElement('div');
                    ovLayer.id = 'canvas-overlay-layer';
                    ovLayer.className = 'canvas-overlay';
                    ovLayer.style.cssText = 'position:absolute;left:0;top:0;right:0;bottom:0;z-index:4;pointer-events:none';
                    stage.appendChild(ovLayer);
                }
            }
            const root = editor.previewContainer?.querySelector('.castconductor-preview-block');
            if (bgLayer) {
                // Apply composed background stack to dedicated background layer
                if (comp.backgroundColor !== undefined) bgLayer.style.backgroundColor = comp.backgroundColor || 'transparent';
                if (comp.backgroundImage) bgLayer.style.backgroundImage = comp.backgroundImage; else bgLayer.style.backgroundImage='';
                if (comp.backgroundSize) bgLayer.style.backgroundSize = comp.backgroundSize; else bgLayer.style.backgroundSize='';
                if (comp.backgroundPosition) bgLayer.style.backgroundPosition = comp.backgroundPosition; else bgLayer.style.backgroundPosition='';
                if (comp.backgroundRepeat) bgLayer.style.backgroundRepeat = comp.backgroundRepeat; else bgLayer.style.backgroundRepeat='';
                // Mirror border radius if configured
                const br = editor.currentConfig?.background?.border_radius;
                if (typeof br === 'number') {
                    bgLayer.style.borderRadius = br + 'px';
                    if (root) root.style.borderRadius = br + 'px';
                }
            }
            // Apply overlay: prefer scoping to the block editor wrap if present; otherwise fall back to canvas overlay layer
            if (ovLayer) {
                // Clear any previous global overlay by default
                ovLayer.style.backgroundImage = '';
                ovLayer.style.backgroundColor = 'transparent';
                const ov = editor.currentConfig?.overlay;
                const o = (ov && ov.opacity!==undefined) ? parseFloat(ov.opacity) : 0;
                if (ov && !isNaN(o) && o > 0 && ov.color) {
                    let overlayColor = ov.color;
                    if (/^#/.test(overlayColor)) {
                        const hx = overlayColor.substring(1);
                        const v = hx.length===3 ? hx.split('').map(c=>c+c).join('') : hx;
                        const r=parseInt(v.substring(0,2),16), g=parseInt(v.substring(2,4),16), b=parseInt(v.substring(4,6),16);
                        overlayColor = `rgba(${r}, ${g}, ${b}, ${o})`;
                    } else if (/^rgb\(/i.test(overlayColor)) {
                        overlayColor = overlayColor.replace(/rgb\(/i,'rgba(').replace(/\)$/i,`, ${o})`);
                    }
                    // Prefer block-scoped overlay when editing a specific block
                    let appliedScoped = false;
                    try {
                        const wrap = editor.previewContainer?.querySelector('.cc-block-editor-wrap');
                        const blockBg = wrap?.querySelector('.ccve-block-bg');
                        if (blockBg) {
                            blockBg.style.backgroundImage = `linear-gradient(0deg, ${overlayColor}, ${overlayColor})`;
                            appliedScoped = true;
                        }
                    } catch(_) {}
                    if (!appliedScoped) {
                        // Fallback: apply to global overlay layer (kept under tokens by z-index)
                        ovLayer.style.backgroundImage = `linear-gradient(0deg, ${overlayColor}, ${overlayColor})`;
                    }
                    try { console.debug('[CCVE][overlay] applied', { color: ov.color, opacity: ov.opacity, computed: overlayColor, scoped: appliedScoped }); } catch(_) {}
                } else {
                    try { console.debug('[CCVE][overlay] cleared or inactive', { hasOv: !!ov, opacity: ov?.opacity, color: ov?.color }); } catch(_) {}
                }
            }
            // Keep preview root transparent to avoid overlaying server-rendered content
            if (root) {
                root.style.backgroundColor = 'transparent';
                root.style.backgroundImage = '';
            }
            editor.markUnsaved && editor.markUnsaved();
            invalidatePreviewHash(editor); // always invalidate to reflect ordering/opacity changes
            editor.triggerPreview && editor.triggerPreview(false);
        } catch(e) { console.warn('[CC][bgLayers] optimistic apply failed', e); }
    }

    // Overlay control live syncing (ensures overlay layer auto-exists when opacity >0)
    const overlayColorEl = document.getElementById('canvas-overlay-color');
    const overlayOpacityEl = document.getElementById('canvas-overlay-opacity');
    const overlayChange = () => {
        try {
            ensure();
            const bg = editor.currentConfig.background;
            editor.currentConfig.overlay = editor.currentConfig.overlay || { color:'#000000', opacity:0 };
            if (overlayColorEl && overlayColorEl.value) editor.currentConfig.overlay.color = overlayColorEl.value;
            if (overlayOpacityEl && overlayOpacityEl.value !== '') {
                const op = parseFloat(overlayOpacityEl.value); if (!isNaN(op)) editor.currentConfig.overlay.opacity = op;
            }
            const hasOverlayLayer = bg.layers.some(l=>l.kind==='overlay');
            if (editor.currentConfig.overlay.opacity > 0 && !hasOverlayLayer) {
                bg.layers.unshift({ _id:crypto.randomUUID?.()||Date.now()+''+Math.random(), kind:'overlay', enabled:true });
                _dedupeKinds(bg);
            }
            if (editor.currentConfig.overlay.opacity <= 0 && hasOverlayLayer) {
                bg.layers = bg.layers.filter(l=>l.kind!=='overlay');
            }
            applyOptimistic(true);
        } catch(e){ console.warn('[CC][bgLayers] overlay sync error', e); }
    };
    if (overlayColorEl) overlayColorEl.addEventListener('input', overlayChange);
    if (overlayOpacityEl) overlayOpacityEl.addEventListener('input', overlayChange);

    const addBtn = document.getElementById('ccve-bg-add-layer');
    if (addBtn) addBtn.addEventListener('click', () => {
        ensure();
        const sel = document.getElementById('ccve-bg-layer-kind');
        const k = (sel?.value || 'image').toLowerCase();
        let layer = null;
        if (k === 'image') layer = { _id:crypto.randomUUID?.()||Date.now()+''+Math.random(), kind:'image', enabled:true, image:{ src:'', fit:'cover', position:'center', repeat:'no-repeat' } };
        else if (k === 'gradient') layer = { _id:crypto.randomUUID?.()||Date.now()+''+Math.random(), kind:'gradient', enabled:true };
        else if (k === 'overlay') {
            layer = { _id:crypto.randomUUID?.()||Date.now()+''+Math.random(), kind:'overlay', enabled:true };
            // Ensure overlay config has a visible default so user sees effect immediately
            editor.currentConfig.overlay = editor.currentConfig.overlay || { color:'#000000', opacity:0.4 };
            if (typeof editor.currentConfig.overlay.opacity !== 'number' || editor.currentConfig.overlay.opacity <= 0) editor.currentConfig.overlay.opacity = 0.4;
            if (!editor.currentConfig.overlay.color) editor.currentConfig.overlay.color = '#000000';
            // Sync controls if present
            const oc = document.getElementById('canvas-overlay-color'); if (oc && !oc.value) oc.value = editor.currentConfig.overlay.color;
            const oo = document.getElementById('canvas-overlay-opacity'); if (oo && (oo.value==='' || parseFloat(oo.value)<=0)) oo.value = String(editor.currentConfig.overlay.opacity);
        }
        else if (k === 'color') layer = { _id:crypto.randomUUID?.()||Date.now()+''+Math.random(), kind:'color', enabled:true, color: editor.currentConfig.background.color || '#000000' };
        if (!layer) { alert('Unsupported kind'); return; }
        editor.currentConfig.background.layers.push(layer);
        _dedupeKinds(editor.currentConfig.background);
        // Sync background layers to hydrated layer list for panel display
        syncBackgroundLayersToHydrated(editor);
        render(); applyOptimistic(true);
        // Rebuild layers panel to show the new layer
        if (typeof editor.buildLayersPanel === 'function') editor.buildLayersPanel();
    });

    // Ensure gradient & overlay layer entries exist for toggling (even if disabled)
    (function ensureBaseKinds(){
        const bg = editor.currentConfig.background;
        if (!Array.isArray(bg.layers)) bg.layers = [];
        bg.layers.forEach(l=>{ if (!l._id) l._id = crypto.randomUUID?.()||Date.now()+''+Math.random(); });
        _dedupeKinds(bg);
    })();

    function _dedupeKinds(bg){
        // Allow multiples for images & color; single overlay & gradient enforced
        const seen = { overlay:false, gradient:false };
        const out = [];
        for (const layer of bg.layers){
            if (layer.kind==='overlay'){ if (seen.overlay) continue; seen.overlay=true; }
            if (layer.kind==='gradient'){ if (seen.gradient) continue; seen.gradient=true; }
            out.push(layer);
        }
        bg.layers = out;
    }

    render();
    // Expose lightweight render hook so other modules can trigger UI refresh after loads/tab switches
    try {
        editor._bgLayersRender = render;
        editor.refreshBackgroundLayersUI = () => { try { render(); applyOptimistic(true); } catch(_) {} };
    } catch(_) {}
}

export default { attachBackgroundLayers, composeBackgroundLayers };