/**
 * 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
 *
 * preview-glue.js
 * Thin orchestration layer extracted from the legacy monolith `canvas-editor.js`.
 * Provides deprecated facade methods for preview triggering and fallback rendering
 * so the main editor class can shrink and future PreviewEngine integrations can
 * replace/override these without touching the facade again.
 */

import { applyPreviewDisplay } from './preview-display.js';

/** Initialize preview glue onto the editor instance. */
export function attachPreviewGlue(editor) {
    // Idempotent guard
    if (editor.__previewGlueAttached) return;
    editor.__previewGlueAttached = true;

    // Lightweight debounce helper (avoid extra imports; scope-local)
    function debounce(fn, wait) {
        let t = null;
        return function debounced(...args) {
            if (t) clearTimeout(t);
            t = setTimeout(()=>fn.apply(this,args), wait);
        };
    }

    // Install debounced generate function (300ms) – first immediate call uses non-debounced path
    editor.generatePreviewDebounced = debounce(()=>editor.generatePreview(), 300);

    /** Unified preview trigger (debounced when available) */
    editor.triggerPreview = function triggerPreview(immediate = false) {
        try {
            // Optimistic background/border/overlay styling for instant feedback
            if (this.currentConfig && this.previewContainer) {
                const bgCfg = this.currentConfig.background || {};
                const overlayCfg = this.currentConfig.overlay || {};
                const typoCfg = this.currentConfig.typography || {};
                const root = this.previewContainer.querySelector('.castconductor-preview-block');
                const bgLayer = document.getElementById('canvas-background-layer');
                const ovLayer = document.getElementById('canvas-overlay-layer');
                if (bgLayer) {
                    // Apply background/overlay to dedicated background layer below preview HTML
                    bgLayer.style.border = '';
                    if (bgCfg.type === 'gradient' && Array.isArray(bgCfg.gradient_colors)) {
                        const dir = bgCfg.gradient_direction || '135deg';
                        const colors = bgCfg.gradient_colors.filter(Boolean).join(', ');
                        bgLayer.style.backgroundImage = `linear-gradient(${dir}, ${colors})`;
                        bgLayer.style.backgroundColor = 'transparent';
                    } else {
                        bgLayer.style.backgroundImage = '';
                        if (bgCfg.color) bgLayer.style.backgroundColor = bgCfg.color; else bgLayer.style.backgroundColor = 'transparent';
                    }
                }
                if (ovLayer) {
                    // Default: clear global overlay
                    ovLayer.style.backgroundImage = '';
                    ovLayer.style.backgroundColor = 'transparent';
                    // Also clear block-scoped overlay element if present
                    try {
                        const wrap = this.previewContainer?.querySelector('.cc-block-editor-wrap');
                        const blockBg = wrap?.querySelector('.ccve-block-bg');
                        if (blockBg) { blockBg.style.backgroundImage = ''; blockBg.style.backgroundColor = 'transparent'; }
                    } catch(_) {}
                    const o = (overlayCfg && overlayCfg.opacity!==undefined) ? parseFloat(overlayCfg.opacity) : 0;
                    if (overlayCfg && !isNaN(o) && o > 0 && overlayCfg.color) {
                        let overlayColor = overlayCfg.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})`);
                        }
                        let scoped=false;
                        try {
                            const wrap = this.previewContainer?.querySelector('.cc-block-editor-wrap');
                            const blockBg = wrap?.querySelector('.ccve-block-bg');
                            if (blockBg) {
                                blockBg.style.backgroundImage = `linear-gradient(0deg, ${overlayColor}, ${overlayColor})`;
                                scoped = true;
                            }
                        } catch(_) {}
                        if (!scoped) {
                            ovLayer.style.backgroundImage = `linear-gradient(0deg, ${overlayColor}, ${overlayColor})`;
                        }
                        try { console.debug('[CCVE][overlay] optimistic-applied', { color: overlayCfg.color, opacity: o, computed: overlayColor, scoped }); } catch(_) {}
                    } else {
                        try { console.debug('[CCVE][overlay] optimistic-cleared', { color: overlayCfg?.color, opacity: overlayCfg?.opacity }); } catch(_) {}
                    }
                }
                if (root) {
                    let bgStyle = '';
                    if (bgCfg.type === 'gradient' && Array.isArray(bgCfg.gradient_colors)) {
                        const dir = bgCfg.gradient_direction || '135deg';
                        const colors = bgCfg.gradient_colors.filter(Boolean).join(', ');
                        bgStyle = `linear-gradient(${dir}, ${colors})`;
                    } else if (bgCfg.type === 'overlay') {
                        // transparent base; overlay applied below
                        bgStyle = 'none';
                    } else if (bgCfg.color) {
                        // Background color now applied to bgLayer; keep root transparent to avoid overlaying preview DOM
                        root.style.backgroundColor = 'transparent';
                    }
                    if (bgStyle) {
                        // Avoid painting gradient on top of preview content; delegate to bgLayer
                        root.style.backgroundImage = '';
                    } else if (bgCfg.type !== 'gradient') {
                        root.style.backgroundImage = '';
                    }
                    if (typeof bgCfg.border_radius === 'number') {
                        // Apply border radius to both layers for visual parity
                        try { const bg = document.getElementById('canvas-background-layer'); if (bg) bg.style.borderRadius = bgCfg.border_radius + 'px'; } catch(_){ }
                        root.style.borderRadius = bgCfg.border_radius + 'px';
                    }
                    // Border is a visual of the stage, not overlay on preview content; apply to container wrapper via CSS elsewhere if needed.
                    // Overlay simulation by layering a CSS gradient on top of existing backgroundImage
                    if (false && overlayCfg && typeof overlayCfg.opacity === 'number' && overlayCfg.opacity > 0 && overlayCfg.color) {
                        const rgba = overlayCfg.color.startsWith('rgba(') || overlayCfg.color.startsWith('rgb(')
                            ? overlayCfg.color.replace(/rgba?\(/,'').replace(/\)/,'').split(',').map(p=>p.trim())
                            : null;
                        let overlayColor = overlayCfg.color;
                        if (rgba && rgba.length>=3) {
                            const r=rgba[0],g=rgba[1],b=rgba[2];
                            overlayColor = `rgba(${r}, ${g}, ${b}, ${overlayCfg.opacity})`;
                        } else if (/^#/.test(overlayCfg.color)) {
                            // crude hex -> rgb
                            const hx = overlayCfg.color.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}, ${overlayCfg.opacity})`;
                        }
                        const existing = root.style.backgroundImage || '';
                        if (existing) {
                            if (!existing.includes('ccve-overlay-layer')) {
                                root.style.backgroundImage = `linear-gradient(0deg, ${overlayColor}, ${overlayColor}) , ${existing}`;
                            }
                        } else {
                            root.style.backgroundImage = `linear-gradient(0deg, ${overlayColor}, ${overlayColor})`;
                        }
                    }
                    // Optimistic typography (font, size, weight, color, line-height, text-shadow)
                    try {
                        if (typoCfg.font_family) root.style.fontFamily = typoCfg.font_family;
                        if (typoCfg.font_size) root.style.fontSize = parseInt(typoCfg.font_size)+'px';
                        if (typoCfg.font_weight || typoCfg.font_weight === 0) root.style.fontWeight = String(typoCfg.font_weight);
                        if (typoCfg.color) root.style.color = typoCfg.color;
                        if (typoCfg.line_height) root.style.lineHeight = String(typoCfg.line_height);
                        if (typoCfg.text_shadow) {
                            const ts = typoCfg.text_shadow;
                            const sx = parseInt(ts.x)||0, sy = parseInt(ts.y)||0, sb = parseInt(ts.blur)||0, sc = ts.color||'rgba(0,0,0,0)';
                            root.style.textShadow = `${sx}px ${sy}px ${sb}px ${sc}`;
                        }
                    } catch(_e) { /* swallow */ }
                }
            }
        } catch(e) { /* non-fatal */ }
        if (immediate || !this.generatePreviewDebounced) {
            this.generatePreview();
            try { this._renderTokenLayers && this._renderTokenLayers(); } catch(_) {}
            try { this.refreshLiveTokens && this.refreshLiveTokens(); } catch(_) {}
        } else {
            try { this.generatePreviewDebounced(); } catch { this.generatePreview(); }
            try { this._renderTokenLayers && this._renderTokenLayers(); } catch(_) {}
            try { this.refreshLiveTokens && this.refreshLiveTokens(); } catch(_) {}
        }
        if (this.previewContainer && !this.previewContainer.classList.contains('ccve-placeholder')) {
            this.previewContainer.classList.add('ccve-preview-stale');
        }
    };

    /**
     * Generate live preview (delegates to PreviewEngine when present)
     * @deprecated Direct logic replaced by PreviewEngine.generate()
     */
    editor._previewSeq = 0; // sequence counter for in-flight preview requests
    editor._previewDropped = 0; // diagnostics
    editor.generatePreview = async function generatePreview() {
    // Respect Editor Mode and background suppression: avoid server preview and any dummy content
    try { if (this._editorMode === 'layers' || this._suppressPreviewForBackground) { return; } } catch(_) {}
        const seq = ++this._previewSeq;
        // Diff hashing: build a lightweight hash of impactful visual fields; skip server call if unchanged
        try {
            if (this.currentConfig) {
                const c = this.currentConfig;
                // Pick only fields affecting preview markup or CSS (avoid volatile runtime counters)
                // Phase 1 Step 6: Use unified layers[] instead of artwork.items[]
                // Extract slideshow layer info for preview hash (replaces legacy artwork.items)
                const slideshowLayers = (c.layers || []).filter(l => l && l.kind === 'slideshow-image');
                const slideshowInfo = slideshowLayers.length > 0 ? {
                    count: slideshowLayers.length,
                    images: slideshowLayers.map(l => ({
                        id: l.id,
                        imgCount: (l.images || []).length,
                        autoplay: l.autoplay ? 1 : 0,
                        transition: l.transition
                    }))
                } : null;
                
                const sig = {
                    layout: c.layout && { w:c.layout.width, h:c.layout.height, p:c.layout.padding },
                    typography: c.typography && { f:c.typography.font_family, s:c.typography.font_size, w:c.typography.font_weight, c:c.typography.color, lh:c.typography.line_height, ta:c.typography.text_align, ts:c.typography.text_shadow },
                    background: c.background && { type:c.background.type, color:c.background.color, grad:c.background.gradient_colors, dir:c.background.gradient_direction, br:c.background.border_radius, b:c.background.border, layers:(c.background.layers||[]).map(l=>({k:l.kind,e:l.enabled?1:0, img:l.image?{src:l.image.src}:null})) },
                    overlay: c.overlay && { c:c.overlay.color, o:c.overlay.opacity },
                    // Phase 1 Step 6: Replace artwork.items[] with slideshow layers from unified system
                    slideshow: slideshowInfo,
                    // Legacy artwork support (for configs not yet migrated)
                    artwork_legacy: c.artwork && c.artwork.items && !c.artwork._migrated ? { items:c.artwork.items.length } : null,
                    behavior: c.behavior && { ticker:c.behavior.ticker },
                    animation: c.animation && { ent:c.animation.entrance, dur:c.animation.duration, easing:c.animation.easing }
                };
                const sigStr = JSON.stringify(sig);
                let hash = 0, chr; for (let i=0;i<sigStr.length;i++){ chr = sigStr.charCodeAt(i); hash = ((hash<<5)-hash)+chr; hash |= 0; }
                if (this._lastPreviewHash === hash) { return; }
                this._lastPreviewHash = hash;
            }
        } catch(e) { /* non-fatal */ }
        // If we have an active block id, prefer real server preview (no placeholder/sample text allowed)
        if (this.currentContentBlockId) {
            try {
                if (this.previewEngine && this.previewEngine.generate) { this.previewEngine.generate(); return; }
                // Directly call server preview endpoint
                const endpoint = `${castconductorCanvasAjax.rest_url}castconductor/v5/canvas-editor/preview`;
                // Include unsaved visual config snapshot when available so preview reflects live changes without forcing a save
                let snapshot = null;
                try { if (this.currentConfig) snapshot = JSON.parse(JSON.stringify(this.currentConfig)); } catch(_){ snapshot = null; }
                const payload = { block_id: parseInt(this.currentContentBlockId,10) };
                if (snapshot) payload.override_config = snapshot;
                const resp = await fetch(endpoint, { method:'POST', headers:{ 'Content-Type':'application/json','X-WP-Nonce':castconductorCanvasAjax.nonce }, body: JSON.stringify(payload) });
                const text = await resp.text();
                let data = null; try { data = JSON.parse(text); } catch(e){ console.warn('[CC] server preview non-JSON', text); return; }
                if (seq !== this._previewSeq) { this._previewDropped++; return; }
                if (!resp.ok || !data.success) { console.warn('[CC] server preview failed', data); return; }
                const preview = data.preview || {};
                if (preview.html) {
                    try { this._applyPreviewHtml({ html: preview.html, css: preview.css || '' }); return; } catch(e){ console.warn('[CC] apply server preview failed', e); }
                }
                return; // Do not fall back to sample text
            } catch(e) { console.warn('[CC] generatePreview server path error', e); return; }
        }
        // No block selected yet: keep container placeholder; do not inject dummy content.
        if (this.previewContainer && !this._previewFirstRendered) {
            this.previewContainer.classList.add('ccve-placeholder');
        }
    };

    // Internal helpers exposed for PreviewEngine integration
    editor._applyPreviewHtml = function _applyPreviewHtml(preview) {
        if (!this.previewContainer) return;
        try { applyPreviewDisplay(this, preview); } catch(e) { console.warn('applyPreviewDisplay failed', e); }
        // After preview HTML is applied, re-apply overlay with block-scoped preference once the wrap exists.
        // This avoids falling back to the canvas-wide overlay when the block overlay element isn't yet in the DOM.
        const self = this;
        function computeOverlayColor(cfg){
            if (!cfg || cfg.opacity === undefined || cfg.opacity === null) return null;
            const o = parseFloat(cfg.opacity);
            if (isNaN(o) || o <= 0 || !cfg.color) return null;
            let c = cfg.color;
            if (/^#/.test(c)) {
                const hx = c.substring(1);
                const v = hx.length===3 ? hx.split('').map(ch=>ch+ch).join('') : hx;
                const r=parseInt(v.substring(0,2),16), g=parseInt(v.substring(2,4),16), b=parseInt(v.substring(4,6),16);
                return `rgba(${r}, ${g}, ${b}, ${o})`;
            } else if (/^rgb\(/i.test(c)) {
                return c.replace(/rgb\(/i,'rgba(').replace(/\)$/i,`, ${o})`);
            }
            return null;
        }
        function clearGlobalAndBlock(){
            try {
                const ovLayer = document.getElementById('canvas-overlay-layer');
                if (ovLayer) { ovLayer.style.backgroundImage=''; ovLayer.style.backgroundColor='transparent'; }
                const wrap = self.previewContainer?.querySelector('.cc-block-editor-wrap');
                const blockBg = wrap?.querySelector('.ccve-block-bg');
                if (blockBg) { blockBg.style.backgroundImage=''; blockBg.style.backgroundColor='transparent'; }
            } catch(_) {}
        }
        function applyScopedOverlay(){
            const overlayColor = computeOverlayColor(self.currentConfig?.overlay);
            // Always start by clearing any prior overlays (prevents stale canvas-wide tint)
            clearGlobalAndBlock();
            if (!overlayColor) { try { console.debug('[CCVE][overlay] post-apply: inactive/cleared'); } catch(_) {} return; }
            let scoped=false;
            try {
                const wrap = self.previewContainer?.querySelector('.cc-block-editor-wrap');
                const blockBg = wrap?.querySelector('.ccve-block-bg');
                if (blockBg) {
                    blockBg.style.backgroundImage = `linear-gradient(0deg, ${overlayColor}, ${overlayColor})`;
                    scoped = true;
                }
            } catch(_) {}
            if (!scoped) {
                const ovLayer = document.getElementById('canvas-overlay-layer');
                if (ovLayer) ovLayer.style.backgroundImage = `linear-gradient(0deg, ${overlayColor}, ${overlayColor})`;
            }
            try { console.debug('[CCVE][overlay] post-apply', { overlayColor, scoped }); } catch(_) {}
        }
        // Two-pass rAF: first frame after DOM write, then one more to catch late wrap/overlay creation
        try {
            requestAnimationFrame(()=>{
                applyScopedOverlay();
                requestAnimationFrame(()=>{ applyScopedOverlay(); });
            });
        } catch(_) { applyScopedOverlay(); }
    };
    editor._handlePreviewError = function _handlePreviewError(message) {
        if (!this.previewContainer) return;
        this.previewContainer.innerHTML = `<div class="canvas-preview-placeholder">${this.escapeHtml(message)}</div>`;
    };
    editor._handlePreviewState = function _handlePreviewState(_state) { /* handled by preview-status module */ };

    /** Compute scale factor between logical layout and current preview container size */
    editor.getPreviewScale = function getPreviewScale() {
        if (!this.previewContainer) return { x:1, y:1 };
        let sx = 1;
        if (this.previewScaleMode === '1x') {
            this.previewContainer.style.width = '1920px';
            this.previewContainer.style.height = '1080px';
            sx = 1;
        } else {
            const pw = this.previewContainer.clientWidth || 1920;
            sx = pw / 1920;
            this.previewContainer.style.height = Math.round(1080 * sx) + 'px';
        }
        return { x:sx, y:sx };
    };
}

// Utility for other modules to force next preview fetch (e.g., layer reorder affecting visual stack but hash might collide)
export function invalidatePreviewHash(editor){ if (editor) delete editor._lastPreviewHash; }

export default { attachPreviewGlue };