// ==========================================
// clipboard.js: COPY / PASTE overlays via the instruction processor
// ==========================================
// Ctrl+C snapshots selection → in-memory clipboard
// Ctrl+V materializes clones on the page under the pointer, then commits
// CREATE_NODE (or COMPOUND of them) through CcsProcessor — undo/redo just works.
// Paste lands at the pointer (not Adobe-style “same coords, wrong page”).

/** @type {{ items: object[], originLeft: number, originTop: number } | null} */
window.overlayClipboard = window.overlayClipboard || null;

/** Last known pointer in viewport coords (for paste-at-cursor). */
window.APP = window.APP || {};
window.APP.lastPointer = window.APP.lastPointer || { clientX: 0, clientY: 0 };

(function trackPointerForPaste() {
    document.addEventListener("mousemove", (e) => {
        if (!window.APP) return;
        window.APP.lastPointer = { clientX: e.clientX, clientY: e.clientY };
    }, true);
})();

/**
 * Overlays currently selected (multi or single), filtered by workspace.
 * @returns {Element[]}
 */
window.getClipboardSourceOverlays = function() {
    let items = [];
    if (APP.multiSelectedItems && APP.multiSelectedItems.size > 0) {
        items = Array.from(APP.multiSelectedItems);
    } else if (APP.activeOverlay) {
        items = [APP.activeOverlay];
    }
    return items.filter((el) => {
        if (!el || !el.classList || !el.isConnected) return false;
        if (el.classList.contains("stencil-box") || el.classList.contains("selection-marquee")) return false;
        const isForm = el.classList.contains("formFieldOverlay");
        const isDecor = el.classList.contains("textOverlay")
            || el.classList.contains("shapeOverlay")
            || el.classList.contains("overlayImg");
        if (APP.workspaceMode === "form") return isForm;
        // Editor: decorative only (form shells are fill targets, not design-copied here)
        return isDecor;
    });
};

/**
 * Re-wire chrome / tools on a cloned or HTML-restored overlay so it is interactive.
 * Does not append or commit history.
 * @param {Element} clone
 * @returns {Element}
 */
window.rebindOverlayChrome = function(clone) {
    if (!clone || !clone.classList) return clone;

    clone.classList.remove("overlay-active", "multi-selected", "is-dragging");
    if (!clone.id || document.getElementById(clone.id) === clone) {
        // always new id for paste/clone so processor targets stay unique
    }
    clone.id = (window.crypto && typeof crypto.randomUUID === "function")
        ? crypto.randomUUID()
        : ("ov-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 8));

    // Delete handle
    const oldDelete = clone.querySelector(".deleteHandle");
    if (oldDelete && typeof window.createDeleteHandle === "function") {
        oldDelete.replaceWith(window.createDeleteHandle(clone));
    }

    // Drag handle
    const oldDrag = clone.querySelector(".textDragHandle");
    if (oldDrag) {
        const newDrag = document.createElement("div");
        newDrag.className = "textDragHandle";
        newDrag.innerHTML = "⠿";
        const textContent = clone.querySelector(".textContent");
        if (textContent) newDrag.addEventListener("mousedown", () => textContent.blur());
        oldDrag.replaceWith(newDrag);
    }

    // Text toolbar
    const oldControls = clone.querySelector(".textControls");
    if (oldControls && clone.classList.contains("textOverlay")) {
        const textContent = clone.querySelector(".textContent");
        const newControls = document.createElement("div");
        newControls.className = "textControls";
        newControls.addEventListener("mousedown", (ev) => ev.stopPropagation());
        if (typeof window.createRichTextToolbar === "function" && textContent) {
            const toolbarObj = window.createRichTextToolbar(textContent, clone);
            newControls.appendChild(toolbarObj.element);
            toolbarObj.sync(
                clone.style.fontFamily || "Roboto",
                parseFloat(clone.style.fontSize) || 14,
                clone.style.color || "#000000",
                clone.style.fontWeight || "400",
                clone.style.lineHeight || "1.2"
            );
        }
        oldControls.replaceWith(newControls);
    }

    // Shape / table controls
    if (clone.classList.contains("shapeOverlay")) {
        const type = clone.classList.contains("shape-rect") ? "rect"
            : clone.classList.contains("shape-circle") ? "circle"
            : clone.classList.contains("shape-line") ? "line"
            : clone.classList.contains("shape-table") ? "table"
            : "rect";
        const ctrls = clone.querySelector(".textControls");
        if (ctrls) ctrls.remove();
        clone.querySelectorAll(".textDragHandle, .deleteHandle, .rotateHandle").forEach((h) => h.remove());
        if (type === "table" && typeof window.buildTableControls === "function") {
            window.buildTableControls(clone);
        } else if (typeof window.buildShapeControls === "function") {
            window.buildShapeControls(clone, type);
        }
        // rebuild drag/delete if build* didn't leave them
        if (!clone.querySelector(".textDragHandle")) {
            const dh = document.createElement("div");
            dh.className = "textDragHandle";
            dh.innerHTML = "⠿";
            clone.appendChild(dh);
        }
        if (!clone.querySelector(".deleteHandle") && typeof window.createDeleteHandle === "function") {
            clone.appendChild(window.createDeleteHandle(clone));
        }
        if (!clone.querySelector(".rotateHandle") && type !== "table") {
            const rh = document.createElement("div");
            rh.className = "rotateHandle";
            rh.innerHTML = "&#8635;";
            rh.style.cursor = "grab";
            clone.appendChild(rh);
        }
    }

    // Form fields — never share native twin ids; unique names for non-radios
    if (clone.classList.contains("formFieldOverlay")) {
        delete clone.dataset.nativeId;
        delete clone.dataset.importedNative;
        const ft = clone.dataset.fieldType || "text";
        if (ft !== "radio" && typeof window.generateFormFieldName === "function") {
            clone.dataset.fieldName = window.generateFormFieldName(ft);
        }
        if (typeof window.refreshFormFieldLabel === "function") {
            window.refreshFormFieldLabel(clone);
        }
        // Rebuild fill surface + controls (old listeners are dead)
        if (typeof window.buildFormFieldControls === "function") {
            const oldFc = clone.querySelector(".form-field-controls, .textControls");
            if (oldFc) {
                const neu = window.buildFormFieldControls(clone);
                oldFc.replaceWith(neu);
            }
        }
        if (typeof window.rebuildFormFieldFillSurface === "function") {
            window.rebuildFormFieldFillSurface(clone);
        } else if (typeof window.buildFormFieldFillSurface === "function") {
            const oldFill = clone.querySelector(".form-field-fill");
            if (oldFill) oldFill.remove();
            clone.insertBefore(window.buildFormFieldFillSurface(clone), clone.firstChild);
        }
    }

    if (typeof window.makeDraggable === "function") {
        window.makeDraggable(
            clone,
            clone.querySelector(".resizeHandle"),
            clone.querySelector(".textDragHandle")
        );
    }
    if (typeof window.syncOverlayChrome === "function") {
        window.syncOverlayChrome(clone);
    }
    return clone;
};

/**
 * Deep-clone a live overlay and rebind chrome (Alt+drag / internal use).
 */
window.cloneOverlayElement = function(sourceEl) {
    if (!sourceEl) return null;
    const clone = sourceEl.cloneNode(true);
    return window.rebindOverlayChrome(clone);
};

/**
 * Ctrl+C — snapshot selection into overlayClipboard (data only; no DOM yet).
 * @returns {number} count copied
 */
window.copySelectedOverlays = function() {
    const sources = window.getClipboardSourceOverlays();
    if (!sources.length) return 0;

    let originLeft = Infinity;
    let originTop = Infinity;
    const items = sources.map((el) => {
        const left = parseFloat(el.style.left) || 0;
        const top = parseFloat(el.style.top) || 0;
        if (left < originLeft) originLeft = left;
        if (top < originTop) originTop = top;
        return {
            html: el.outerHTML,
            left,
            top,
            width: el.style.width || "",
            height: el.style.height || ""
        };
    });
    if (!Number.isFinite(originLeft)) originLeft = 0;
    if (!Number.isFinite(originTop)) originTop = 0;

    window.overlayClipboard = {
        items,
        originLeft,
        originTop,
        copiedAt: Date.now()
    };
    return items.length;
};

/**
 * Resolve .pageContainer under the pointer (active document only).
 */
window.getPageContainerAtPointer = function(clientX, clientY) {
    const cx = clientX != null ? clientX : (APP.lastPointer && APP.lastPointer.clientX) || 0;
    const cy = clientY != null ? clientY : (APP.lastPointer && APP.lastPointer.clientY) || 0;

    let stack = [];
    try {
        stack = document.elementsFromPoint(cx, cy) || [];
    } catch (_) {
        stack = [];
    }
    for (let i = 0; i < stack.length; i++) {
        const pc = stack[i].closest && stack[i].closest(".pageContainer");
        if (!pc) continue;
        const viewer = pc.closest(".doc-viewer") || pc.closest("#viewer");
        if (viewer && viewer.classList && viewer.classList.contains("doc-viewer")
            && !viewer.classList.contains("is-active-doc")) {
            continue;
        }
        return pc;
    }

    // Fallback: page of first clipboard-related selection / first page in active viewer
    const viewer = (typeof window.getActiveViewer === "function" && window.getActiveViewer())
        || (APP.DOM && APP.DOM.viewer)
        || document.querySelector(".doc-viewer.is-active-doc")
        || document.getElementById("viewer");
    if (viewer) {
        const pages = viewer.querySelectorAll(".pageContainer");
        if (pages.length === 1) return pages[0];
        // Prefer page closest to pointer Y
        let best = null;
        let bestDist = Infinity;
        pages.forEach((pc) => {
            const r = pc.getBoundingClientRect();
            const midY = (r.top + r.bottom) / 2;
            const midX = (r.left + r.right) / 2;
            const d = Math.hypot(cx - midX, cy - midY);
            if (d < bestDist) {
                bestDist = d;
                best = pc;
            }
        });
        return best;
    }
    return null;
};

window.clientToPageCss = function(clientX, clientY, container) {
    const rect = container.getBoundingClientRect();
    const zoom = window.currentZoom || 1;
    return {
        x: (clientX - rect.left) / zoom,
        y: (clientY - rect.top) / zoom
    };
};

/**
 * Ctrl+V — materialize clipboard items at pointer, commit CREATE_NODE via processor.
 * @returns {Element[]} pasted nodes
 */
window.pasteOverlaysAtPointer = function() {
    const clip = window.overlayClipboard;
    if (!clip || !clip.items || !clip.items.length) return [];
    if (!window.CcsProcessor || !window.CcsProcessor.build) {
        console.warn("[paste] CcsProcessor required");
        return [];
    }

    const ptr = APP.lastPointer || { clientX: 0, clientY: 0 };
    const container = window.getPageContainerAtPointer(ptr.clientX, ptr.clientY);
    if (!container) {
        if (typeof window.customAlert === "function") {
            window.customAlert("Move the mouse over a page, then paste.", "Paste");
        }
        return [];
    }

    const drop = window.clientToPageCss(ptr.clientX, ptr.clientY, container);
    const originLeft = clip.originLeft || 0;
    const originTop = clip.originTop || 0;

    const pasted = [];
    const steps = [];

    clip.items.forEach((item) => {
        const tpl = document.createElement("div");
        tpl.innerHTML = (item.html || "").trim();
        let node = tpl.firstElementChild;
        if (!node) return;

        node = window.rebindOverlayChrome(node);

        // Group origin → pointer; preserve relative layout within the selection
        const relX = (item.left != null ? item.left : 0) - originLeft;
        const relY = (item.top != null ? item.top : 0) - originTop;
        let left = drop.x + relX;
        let top = drop.y + relY;
        left = Math.max(0, left);
        top = Math.max(0, top);
        node.style.left = left + "px";
        node.style.top = top + "px";
        if (item.width) node.style.width = item.width;
        if (item.height) node.style.height = item.height;

        // Workspace guard: don't paste form shells in editor or vice versa
        const isForm = node.classList.contains("formFieldOverlay");
        if (APP.workspaceMode === "form" && !isForm) return;
        if (APP.workspaceMode !== "form" && isForm) return;

        container.appendChild(node);
        // Instruction tape: CREATE_NODE assigns stack z and is undoable
        steps.push(window.CcsProcessor.build.createNode(node, "Paste"));
        pasted.push(node);
    });

    if (!steps.length) return [];

    if (steps.length === 1) {
        window.CcsProcessor.commit(steps[0]);
    } else {
        window.CcsProcessor.commit(
            window.CcsProcessor.build.compound(steps, `Paste ${steps.length} items`)
        );
    }

    // Select what we pasted
    if (window.clearMultiSelection) window.clearMultiSelection();
    if (pasted.length === 1) {
        if (window.setActiveOverlay) window.setActiveOverlay(pasted[0]);
    } else if (pasted.length > 1 && APP.multiSelectedItems) {
        if (window.clearActiveOverlay) window.clearActiveOverlay(true);
        pasted.forEach((el) => {
            el.classList.add("multi-selected");
            APP.multiSelectedItems.add(el);
        });
        if (window.showGroupToolbar) window.showGroupToolbar();
    }

    return pasted;
};
