/**
 * 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
 * 
 * Guides & Snapping Module
 * Extracted from canvas-editor.js (Phase 2.7 Step 5)
 */

export function ensureBaselineGuides(editor, opts) {
    // Baseline guides depend on current authoring bounds
    // For token/layer mode: use container bounds from currentConfig.layout
    // For container mode: use full canvas bounds (1280×720)
    if (editor._baselineGuidesAdded) return;
    editor._baselineGuidesAdded = true;
    try {
        const mode = (opts && opts.layerType) || 'container';
        let w = 1280, h = 720;
        
        if (mode === 'token') {
            // Use container bounds for token/layer editing
            const layout = editor.currentConfig?.layout;
            if (layout && layout.width && layout.height) {
                w = layout.width;
                h = layout.height;
                console.debug('[CCVE][guides] Using container bounds for baseline guides:', w, 'x', h);
            }
        } else {
            // Use full canvas bounds for container editing
            const bounds = editor.canvasBounds || {};
            w = bounds.w || 1280;
            h = bounds.h || 720;
        }
        
        const thirdsV = Math.round(w / 2); // keep vertical center
        const thirdH1 = Math.round(h / 3);
        const thirdH2 = Math.round((2 * h) / 3);
        const addOnce = (arr,v)=>{ if (!arr.includes(v)) arr.push(v); };
        addOnce(editor._guidesHorizontal, thirdH1); // upper third
        addOnce(editor._guidesHorizontal, thirdH2); // lower third
        addOnce(editor._guidesVertical, thirdsV);   // vertical center
        if (typeof editor._renderExistingGuides === 'function') editor._renderExistingGuides();
        try { console.debug('[CCVE] baselineGuidesAdded', { h:editor._guidesHorizontal, v:editor._guidesVertical }); } catch(_) {}
    } catch(e) { console.warn('baseline guides add failed', e); }
}

function _collectSiblingAnchors(editor, excludeId){
    try {
        const nodes = Array.from(document.querySelectorAll('.canvas-container'));
        const anchors = { v: [], h: [] };
        for (const n of nodes){
            const id = n.getAttribute('data-container-id');
            if (!id || id === excludeId) continue;
            if (n.classList.contains('ccve-locked') || n.style.display === 'none' || n.hidden) continue;
            const x = parseInt(n.style.left,10) || n.offsetLeft || 0;
            const y = parseInt(n.style.top,10) || n.offsetTop || 0;
            const w = parseInt(n.style.width,10) || n.offsetWidth || 0;
            const h = parseInt(n.style.height,10) || n.offsetHeight || 0;
            const cx = x + Math.round(w/2);
            const cy = y + Math.round(h/2);
            anchors.v.push(x, x + w, cx);
            anchors.h.push(y, y + h, cy);
        }
        // de-duplicate
        anchors.v = Array.from(new Set(anchors.v)).sort((a,b)=>a-b);
        anchors.h = Array.from(new Set(anchors.h)).sort((a,b)=>a-b);
        return anchors;
    } catch(_) { return { v:[], h:[] }; }
}

/**
 * Begin a snapping session: compute dynamic guides (edges, centers, safe margins, siblings).
 * For token/layer mode, uses container bounds (e.g., 1280×240 for lower third)
 * For container mode, uses full canvas bounds (1280×720)
 * 
 * COORDINATE SYSTEM: All guides are in container-relative coordinates for token mode.
 */
export function beginSnapSession(editor, activeId, opts){
    const mode = (opts && opts.layerType) || 'container';
    
    // Determine bounds based on mode
    let w = 1280, h = 720;
    // Container offset for coordinate conversion (only for token mode)
    let containerOffsetX = 0, containerOffsetY = 0;
    
    if (mode === 'token') {
        // Use container bounds from currentConfig.layout for token/layer editing
        const layout = editor.currentConfig?.layout;
        if (layout && layout.width && layout.height) {
            w = layout.width;
            h = layout.height;
            // Get container offset to convert DOM positions to container-relative
            // Use layout.position first (updated by Intended Container changes)
            containerOffsetX = layout.position?.x ?? layout._originalPosition?.x ?? 0;
            containerOffsetY = layout.position?.y ?? layout._originalPosition?.y ?? 0;
            console.debug('[CCVE][guides] Using container bounds for token mode:', w, 'x', h, 'offset:', containerOffsetX, containerOffsetY);
        }
    } else {
        // Use full canvas bounds for container editing
        const bounds = editor.canvasBounds || {};
        w = bounds.w || 1280;
        h = bounds.h || 720;
    }
    
    const safe = editor._safeMargins || { left: Math.round(w*0.05), right: Math.round(w*0.95), top: Math.round(h*0.05), bottom: Math.round(h*0.95) };
    // Container/stage edges and centers
    const stageV = [0, Math.round(w/2), w];
    const stageH = [0, Math.round(h/2), h];
    // Thirds within container bounds
    const thirdsV = [Math.round(w/3), Math.round(2*w/3)];
    const thirdsH = [Math.round(h/3), Math.round(2*h/3)];
    // Safe margins (5% inset from edges)
    const safeV = [safe.left, safe.right];
    const safeH = [safe.top, safe.bottom];
    
    // Sibling anchors (edges + centers) - converted to container-relative for token mode
    let sib = { v:[], h:[] };
    if (mode === 'token') {
        // Collect from token layers in block stage - use unified layer class
        try {
            const nodes = Array.from(document.querySelectorAll('#block-stage .unified-layer'));
            const v = []; const hArr = [];
            nodes.forEach(n => {
                const id = n.getAttribute('data-layer-id');
                if (!id || id === activeId) return;
                // DOM positions are canvas-absolute; convert to container-relative
                const domX = parseInt(n.style.left,10) || n.offsetLeft || 0;
                const domY = parseInt(n.style.top,10) || n.offsetTop || 0;
                const x = domX - containerOffsetX;
                const y = domY - containerOffsetY;
                const w2 = parseInt(n.style.width,10) || n.offsetWidth || 0;
                const h2 = parseInt(n.style.height,10) || n.offsetHeight || 0;
                const cx = x + Math.round(w2/2);
                const cy = y + Math.round(h2/2);
                v.push(x, x+w2, cx);
                hArr.push(y, y+h2, cy);
            });
            sib = { v: Array.from(new Set(v)).sort((a,b)=>a-b), h: Array.from(new Set(hArr)).sort((a,b)=>a-b) };
        } catch(_) {}
    } else {
        sib = _collectSiblingAnchors(editor, activeId);
    }

    // Content-aware anchors provider (optional)
    let contentV = []; let contentH = [];
    try {
        if (typeof editor._contentAnchorsProvider === 'function') {
            const ctx = { layerType: mode, id: activeId };
            const provided = editor._contentAnchorsProvider(editor, ctx) || null;
            if (provided) {
                if (Array.isArray(provided.v)) contentV = provided.v;
                if (Array.isArray(provided.h)) contentH = provided.h;
            }
        }
    } catch(_) {}

    // Heuristic artwork gutters for token mode: right edge of images + gutter
    if (mode === 'token') {
        try {
            const gutter = Number.isFinite(editor._artworkTextGutter) ? editor._artworkTextGutter : 16;
            // Use unified layer class with token-image kind
            const imgs = Array.from(document.querySelectorAll('#block-stage .unified-layer[data-layer-kind="token-image"]'));
            imgs.forEach(n => {
                // Convert DOM position to container-relative
                const domX = parseInt(n.style.left,10) || n.offsetLeft || 0;
                const x = domX - containerOffsetX;
                const w2 = parseInt(n.style.width,10) || n.offsetWidth || 0;
                const right = x + w2;
                contentV.push(right, right + gutter);
            });

            // Compute a "text lane" center X to the right of the nearest artwork to the left of the active layer
            // Lane: [laneLeft, stageRight]; laneLeft = max(image.right) where image.right <= active.left (with small tolerance)
            const activeNode = document.querySelector(`#block-stage [data-layer-id="${CSS.escape(activeId)}"]`);
            if (activeNode) {
                const domALeft = (parseInt(activeNode.style.left,10) || activeNode.offsetLeft || 0);
                const aLeft = domALeft - containerOffsetX;
                let laneLeft = 0;
                imgs.forEach(n => {
                    // Convert DOM position to container-relative
                    const domIx = parseInt(n.style.left,10) || n.offsetLeft || 0;
                    const ix = domIx - containerOffsetX;
                    const iw = parseInt(n.style.width,10) || n.offsetWidth || 0;
                    const iRight = ix + iw;
                    if (iRight <= aLeft + 1) laneLeft = Math.max(laneLeft, iRight);
                });
                laneLeft = laneLeft > 0 ? laneLeft + gutter : laneLeft;
                const laneRight = w; // use stage right as lane end for now
                const laneCenter = Math.round((laneLeft + laneRight) / 2);
                contentV.push(laneCenter);
            }
        } catch(_) {}
    }

    editor._dynGuides = {
        v: Array.from(new Set([ ...editor._guidesVertical, ...stageV, ...thirdsV, ...safeV, ...sib.v, ...contentV ])).sort((a,b)=>a-b),
        h: Array.from(new Set([ ...editor._guidesHorizontal, ...stageH, ...thirdsH, ...safeH, ...sib.h, ...contentH ])).sort((a,b)=>a-b)
    };
}

export function endSnapSession(editor){ editor._dynGuides = null; editor._activeGuide = null; editor._activeGuides = null; editor._centerSnap = null; }

export function getActiveSnap(editor, evt, containerEl) {
    try {
        const zones = containerEl.querySelectorAll('.zones-overlay .zone-rect');
        if (!zones || !zones.length) return editor.canvasSnap;
        const rect = containerEl.getBoundingClientRect();
        const cx = (evt.clientX ?? 0) - rect.left;
        const cy = (evt.clientY ?? 0) - rect.top;
        for (const z of zones) {
            const zx = parseInt(z.style.left, 10) || 0;
            const zy = parseInt(z.style.top, 10) || 0;
            const zw = parseInt(z.style.width, 10) || 0;
            const zh = parseInt(z.style.height, 10) || 0;
            if (cx >= zx && cx <= zx + zw && cy >= zy && cy <= zy + zh) {
                const snapAttr = z.getAttribute('data-grid-snap');
                const s = parseInt(snapAttr || '0', 10);
                return s > 0 ? s : editor.canvasSnap;
            }
        }
    } catch(_) {}
    return editor.canvasSnap;
}

export function snapValue(editor, value, step) {
    const s = step || editor.canvasSnap || 8;
    return Math.round(value / s) * s;
}

export function applyGuideSnapping(editor, rect) {
    // rect: { x,y,w,h } mutated in place via return
    const { x, y, w, h } = rect;
    const guidesV = (editor._dynGuides && editor._dynGuides.v) || editor._guidesVertical || [];
    const guidesH = (editor._dynGuides && editor._dynGuides.h) || editor._guidesHorizontal || [];
    const baseT = Number.isFinite(editor._guideSnapThreshold) ? editor._guideSnapThreshold : 8;
    const centerT = Number.isFinite(editor._centerSnapThreshold) ? editor._centerSnapThreshold : Math.max(baseT + 4, 12);
    const centerBias = Number.isFinite(editor._centerSnapBias) ? editor._centerSnapBias : 2; // px advantage to center picks
    const snapEdge = (val, guides, axis, kind='edge') => {
        let closest = null; let dist = Infinity;
        const threshold = (kind === 'center') ? centerT : baseT;
        for (const g of guides) {
            const d = Math.abs(g - val);
            if (d < dist && d <= threshold) { dist = d; closest = g; }
        }
        if (closest === null) return { value: val };
        return { value: closest, guide: { axis, pos: closest, type: kind }, rawDist: dist };
    };
    let nx = x, ny = y, nw = w, nh = h;
    const right = nx + nw;
    const bottom = ny + nh;
    const cx = nx + Math.round(nw/2);
    const cy = ny + Math.round(nh/2);
    const snappedLeft = snapEdge(nx, guidesV, 'v', 'edge');
    const snappedTop = snapEdge(ny, guidesH, 'h', 'edge');
    const snappedRight = snapEdge(right, guidesV, 'v', 'edge');
    const snappedBottom = snapEdge(bottom, guidesH, 'h', 'edge');
    // Centers: prefer closer of center vs edges when within threshold
    const snappedCenterX = snapEdge(cx, guidesV, 'v', 'center');
    const snappedCenterY = snapEdge(cy, guidesH, 'h', 'center');

    const active = [];
    let pickX = null;
    // Choose X axis snap
    const xCandidates = [];
    if (snappedLeft.guide && snappedLeft.value !== nx) xCandidates.push({ kind:'left', value: snappedLeft.value, guide: snappedLeft.guide, dist: Math.abs(snappedLeft.value - nx) });
    if (snappedRight.guide && snappedRight.value !== right) xCandidates.push({ kind:'right', value: snappedRight.value, guide: snappedRight.guide, dist: Math.abs(snappedRight.value - right) });
    // For center snaps, include when already perfectly centered (dist=0) to show guide
    if (snappedCenterX.guide) {
        const d = Math.abs(snappedCenterX.value - cx);
        xCandidates.push({ kind:'centerX', value: snappedCenterX.value, guide: snappedCenterX.guide, dist: Math.max(0, d - centerBias) });
    }
    xCandidates.sort((a,b)=>a.dist-b.dist);
    if (xCandidates.length){
        pickX = xCandidates[0];
        if (pickX.kind==='left'){ nx = pickX.value; }
        else if (pickX.kind==='right'){ nw = Math.max(40, pickX.value - nx); }
        else if (pickX.kind==='centerX'){ nx = pickX.value - Math.round(nw/2); }
        active.push(pickX.guide);
    }
    // Choose Y axis snap
    const yCandidates = [];
    if (snappedTop.guide && snappedTop.value !== ny) yCandidates.push({ kind:'top', value: snappedTop.value, guide: snappedTop.guide, dist: Math.abs(snappedTop.value - ny) });
    if (snappedBottom.guide && snappedBottom.value !== bottom) yCandidates.push({ kind:'bottom', value: snappedBottom.value, guide: snappedBottom.guide, dist: Math.abs(snappedBottom.value - bottom) });
    // For center snaps, include when already perfectly centered (dist=0) to show guide
    if (snappedCenterY.guide) {
        const d = Math.abs(snappedCenterY.value - cy);
        yCandidates.push({ kind:'centerY', value: snappedCenterY.value, guide: snappedCenterY.guide, dist: Math.max(0, d - centerBias) });
    }
    yCandidates.sort((a,b)=>a.dist-b.dist);
    let pickY = null;
    if (yCandidates.length){
        pickY = yCandidates[0];
        if (pickY.kind==='top'){ ny = pickY.value; }
        else if (pickY.kind==='bottom'){ nh = Math.max(40, pickY.value - ny); }
        else if (pickY.kind==='centerY'){ ny = pickY.value - Math.round(nh/2); }
        active.push(pickY.guide);
    }
    editor._activeGuide = active.length ? active[0] : null;
    editor._activeGuides = { x: pickX ? pickX.guide : null, y: pickY ? pickY.guide : null };
    editor._centerSnap = {
        x: (pickX && pickX.kind === 'centerX') ? pickX.value : null,
        y: (pickY && pickY.kind === 'centerY') ? pickY.value : null
    };
    return { x: nx, y: ny, w: nw, h: nh };
}

export const guidesSnappingInternals = { ensureBaselineGuides, getActiveSnap, snapValue, applyGuideSnapping, beginSnapSession, endSnapSession };
