/**
 * Idesoftbcn AI Product Customizer — designer front-end.
 * Canvas rendering (front/back views), drag-and-drop in the print area,
 * layers (text, image, sticker, graphic), freehand paint, AI generation,
 * 3D preview, and WooCommerce cart field sync.
 */

document.addEventListener("DOMContentLoaded", function () {
    // --- Canvas bootstrap ---
    const wrap = document.getElementById("iapc_canvas_wrap");

    if (!wrap) {
        return;
    }

    const canvas = document.getElementById("iapc_mug_canvas");

    if (!canvas) {
        return;
    }

    const ctx = canvas.getContext("2d");
    const designCanvas = document.createElement("canvas");
    const designCtx = designCanvas.getContext("2d");

    const designerRoot = wrap.closest(".iapc-designer-wrapper") || document;
    // Prefer the designer root, then document: on mobile the tool panels are
    // portaled to <body> and leave .iapc-designer-wrapper.
    const $ = (id) =>
        (designerRoot.querySelector && designerRoot.querySelector("#" + id)) ||
        document.getElementById(id);

    const canvasLoading = $("iapc_canvas_loading");
    const canvasLoadingText = canvasLoading
        ? canvasLoading.querySelector(".iapc-canvas-loading__text")
        : null;
    let iapcCanvasLoading = false;

    function setCanvasLoading(on, message) {
        iapcCanvasLoading = !!on;

        if (canvasLoadingText && message) {
            canvasLoadingText.textContent = message;
        }

        if (!canvasLoading) {
            return;
        }

        canvasLoading.classList.toggle("iapc-hidden", !iapcCanvasLoading);
        canvasLoading.setAttribute("aria-busy", iapcCanvasLoading ? "true" : "false");

        if (typeof dibujar === "function") {
            try {
                dibujar({ skipGuardar: true });
            } catch (e) {
                // dibujar may not be ready during early bootstrap
            }
        }
    }

    function getRangeStyleSheet() {
        var styleEl = document.getElementById("iapc-range-styles");
        if (!styleEl) {
            styleEl = document.createElement("style");
            styleEl.id = "iapc-range-styles";
            document.head.appendChild(styleEl);
        }
        return styleEl;
    }

    function updateRangeProgress(range) {
        if (!range) {
            return;
        }

        var min = parseFloat(range.min);
        var max = parseFloat(range.max);
        var val = parseFloat(range.value);

        if (isNaN(min)) {
            min = 0;
        }

        if (isNaN(max)) {
            max = 100;
        }

        if (isNaN(val)) {
            val = min;
        }

        var pct = max === min ? 0 : ((val - min) / (max - min)) * 100;
        var pctStr = pct + "%";

        range.style.setProperty("--iapc-range-progress", pctStr);

        if (!range.id) {
            return;
        }

        var styleEl = getRangeStyleSheet();
        var selector =
            ".iapc-designer-wrapper #" +
            range.id +
            "::-webkit-slider-runnable-track";
        var cssText =
            selector +
            "{background-color:#e5e7eb!important;background-image:linear-gradient(to right,#9333ea 0%,#9333ea " +
            pctStr +
            ",#e5e7eb " +
            pctStr +
            ",#e5e7eb 100%)!important;}";

        var rules = styleEl.textContent ? styleEl.textContent.split("\n") : [];
        var found = false;

        for (var i = 0; i < rules.length; i++) {
            if (rules[i].indexOf(selector) === 0) {
                rules[i] = cssText;
                found = true;
                break;
            }
        }

        if (!found) {
            rules.push(cssText);
        }

        styleEl.textContent = rules.filter(Boolean).join("\n");
    }

    function initRangeProgress(root) {
        root.querySelectorAll('input[type="range"]').forEach(function (range) {
            updateRangeProgress(range);

            if (range.dataset.iapcRangeBound) {
                return;
            }

            range.dataset.iapcRangeBound = "1";
            range.addEventListener("input", function () {
                updateRangeProgress(range);
            });
            range.addEventListener("change", function () {
                updateRangeProgress(range);
            });
        });
    }

    initRangeProgress(designerRoot);

    var canvaMenu = designerRoot.querySelector(".iapc-canva-menu");
    if (canvaMenu) {
        canvaMenu.addEventListener("click", function () {
            window.requestAnimationFrame(function () {
                initRangeProgress(designerRoot);
            });
        });
    }

    // --- Cart form ---
    // Returns the WooCommerce add-to-cart form inside the designer wrapper.
    function getDesignerCartForm() {
        return designerRoot.querySelector("form.cart");
    }

    IAPC.cartBridge.init({
        getForm: getDesignerCartForm
    });

    function setCampoCarrito(name, value) {
        IAPC.cartBridge.setField(name, value);
    }

    const i18n = typeof IAPC_DATA !== "undefined" && IAPC_DATA.i18n ? IAPC_DATA.i18n : {};

    // Translates a key using IAPC_DATA.i18n with optional sprintf placeholders.
    function t(key) {
        const args = Array.prototype.slice.call(arguments, 1);
        let str = i18n[key] || key;

        args.forEach(function (arg, index) {
            const num = index + 1;
            str = str.replace(new RegExp("%" + num + "\\$[sd]", "g"), String(arg));
        });

        args.forEach(function (arg) {
            str = str.replace(/%[sd]/, String(arg));
        });

        return str;
    }

    function iapcNotify(message, type) {
        IAPC.toast.notify(message, type);
    }

    IAPC.viewer3d.configure({
        getCanvas: function () {
            return canvas;
        },
        getDesignCanvas: function () {
            return designCanvas;
        },
        redraw: function () {
            dibujar();
        },
        notify: iapcNotify,
        translate: t
    });

    function actualizar3DSiEstaActivo() {
        IAPC.viewer3d.updateIfActive();
    }

    // --- Text panel DOM refs ---
    const useText = $("iapc_use_text");
    const textOptions = $("iapc_text_options");
    const input = $("iapc_text");
    const fontSelect = $("iapc_font");
    const colorPicker = $("iapc_color");
    const fontSize = $("iapc_font_size");
    const bold = $("iapc_bold");
    const italic = $("iapc_italic");
    const posX = $("iapc_pos_x");
    const posY = $("iapc_pos_y");

    // --- Image panel DOM refs ---
    const useImage = $("iapc_use_image");
    const imageOptions = $("iapc_image_options");
    const imageInput = $("iapc_image");
    const imageFilename = $("iapc_image_filename");
    const imageSize = $("iapc_image_size");
    const imageRotation = $("iapc_image_rotation");
    const imageLayer = $("iapc_image_layer");
    const imageX = $("iapc_image_x");
    const imageY = $("iapc_image_y");
    const aiImageSize = $("iapc_ai_image_size");
    const aiImageRotation = $("iapc_ai_image_rotation");
    const aiImageLayer = $("iapc_ai_image_layer");
    const aiImageX = $("iapc_ai_image_x");
    const aiImageY = $("iapc_ai_image_y");
    const addImageBtn = $("iapc_add_image");
    let imagenPendiente = null;

    // --- Sticker panel DOM refs ---
    const useSticker = $("iapc_use_sticker");
    const stickerOptions = $("iapc_sticker_options");
    const stickerSelect = $("iapc_sticker");
    const customStickerInput = $("iapc_custom_sticker");
    const rightsConfirm = $("iapc_rights_confirm");
    const hasCustomStickerInput = $("iapc_has_custom_sticker");
    const stickerSize = $("iapc_sticker_size");
    const stickerRotation = $("iapc_sticker_rotation");
    const stickerLayer = $("iapc_sticker_layer");
    const stickerX = $("iapc_sticker_x");
    const stickerY = $("iapc_sticker_y");
    const addStickerBtn = $("iapc_add_sticker");
    const stickersDataInput = designerRoot.querySelector('[name="iapc_stickers_data"]');

    // --- Preview, sides, and action buttons ---
    const designInput = designerRoot.querySelector('[name="iapc_design_image"]');
    const currentSideInput = designerRoot.querySelector('[name="iapc_current_side"]');
    const previewBtn = $("iapc_preview_btn");
    const previewOverlay = $("iapc_preview_overlay");
    const previewImg = $("iapc_preview_img");
    const closePreview = $("iapc_close_preview");

    const clearTextBtn = $("iapc_clear_text");
    const addTextBtn = $("iapc_add_text");
    const clearImageBtn = $("iapc_clear_image");
    const clearStickerBtn = $("iapc_clear_sticker");
    const resetDesignBtn = $("iapc_reset_design");

    const sidesWrap = document.querySelector(".iapc-sides");

    const allowedImageTypes = typeof IAPC_DATA !== "undefined" && IAPC_DATA.allowedImageTypes ? IAPC_DATA.allowedImageTypes : ["image/png", "image/jpeg", "image/webp"];
    const maxFileSizeMB = typeof IAPC_DATA !== "undefined" && IAPC_DATA.maxFileSizeMB ? IAPC_DATA.maxFileSizeMB : 3;
    const maxFileSizeBytes = maxFileSizeMB * 1024 * 1024;
    const stickersPropios = typeof IAPC_DATA !== "undefined" && IAPC_DATA.stickers ? IAPC_DATA.stickers : {};

    // --- Graphic elements panel DOM refs ---
    const graphicSelect = $("iapc_graphic");
const graphicSize = $("iapc_graphic_size");
const graphicRotation = $("iapc_graphic_rotation");
const graphicLayer = $("iapc_graphic_layer");
const graphicX = $("iapc_graphic_x");
const graphicY = $("iapc_graphic_y");
const addGraphicBtn = $("iapc_add_graphic");

const graphicsPropios = typeof IAPC_DATA !== "undefined" && IAPC_DATA.graphics ? IAPC_DATA.graphics : {};
const graphicImages = {};

// --- AI panel DOM refs ---
const aiPrompt = $("iapc_ai_prompt");
const generateAiBtn = $("iapc_generate_ai");
const aiLimitText = $("iapc_ai_limit_text");

const aiStyle = $("iapc_ai_style");
const aiHistory = $("iapc_ai_history");
const clearAiBtn = $("iapc_clear_ai");

const designForMeBtn = $("iapc_design_for_me");
const aiColors = $("iapc_ai_colors");
const aiElements = $("iapc_ai_elements");
const guidedModal = $("iapc_ai_guided_modal");
const openGuidedBtn = $("iapc_open_guided_ai");
const clearGuidedBtn = $("iapc_clear_guided");

// Opens the guided AI design modal.
function openGuidedModal() {
    if (!guidedModal) {
        return;
    }

    // Portal to body so it stacks above the mobile tool sheet (also portaled).
    if (guidedModal.parentNode !== document.body) {
        document.body.appendChild(guidedModal);
    }

    guidedModal.classList.add("is-open");
    guidedModal.setAttribute("aria-hidden", "false");
    document.body.classList.add("iapc-guided-modal-open");
}

// Closes the guided AI design modal.
function closeGuidedModal() {
    if (!guidedModal) {
        return;
    }

    guidedModal.classList.remove("is-open");
    guidedModal.setAttribute("aria-hidden", "true");
    document.body.classList.remove("iapc-guided-modal-open");
}

if (openGuidedBtn) {
    openGuidedBtn.addEventListener("click", openGuidedModal);
}

document.addEventListener("click", function (e) {
    if (e.target.closest("[data-iapc-close-guided]")) {
        closeGuidedModal();
    }
});

document.addEventListener("keydown", function (e) {
    if (e.key === "Escape" && guidedModal && guidedModal.classList.contains("is-open")) {
        closeGuidedModal();
    }
});

let aiGuidedFormat = "";
let aiGuidedStyle = "";
let aiGuidedMood = "";

function clearGuidedFilters() {
    aiGuidedFormat = "";
    aiGuidedStyle = "";
    aiGuidedMood = "";

    if (guidedModal) {
        guidedModal.querySelectorAll(".iapc-ai-choice.iapc-active").forEach(function (btn) {
            btn.classList.remove("iapc-active");
        });
    }

    if (aiColors) {
        aiColors.value = "";
    }

    if (aiElements) {
        aiElements.value = "";
    }
}

if (clearGuidedBtn) {
    clearGuidedBtn.addEventListener("click", clearGuidedFilters);
}

let indiceIAActiva = -1;
let ultimoPromptIA = "";
let historialIA = [];
let imagenIAActiva = "";
let ultimoPromptGenerado = "";

document.addEventListener("click", function (e) {
    const btn = e.target.closest(".iapc-ai-choice");

    if (!btn) return;

    const groupWrap = btn.closest(".iapc-ai-guided-buttons");
    if (!groupWrap) return;

    const group = groupWrap.dataset.group;

    groupWrap.querySelectorAll(".iapc-ai-choice").forEach(function (otherBtn) {
        otherBtn.classList.remove("iapc-active");
    });

    btn.classList.add("iapc-active");

    if (group === "format") {
        aiGuidedFormat = btn.dataset.value;
    }

    if (group === "style") {
        aiGuidedStyle = btn.dataset.value;
    }

    if (group === "mood") {
        aiGuidedMood = btn.dataset.value;
    }
});

if (designForMeBtn) {
    designForMeBtn.addEventListener("click", function () {
        const colores = aiColors ? aiColors.value.trim() : "";
        const elementos = aiElements ? aiElements.value.trim() : "";

        const promptGuiado = [
            aiGuidedFormat,
            aiGuidedStyle,
            aiGuidedMood,
            colores,
            elementos,
            "high quality design",
            "centered composition",
            "no text"
        ].filter(Boolean).join(", ");

        if (!promptGuiado.trim()) {
            iapcNotify(t("chooseGuidedOption"), "warning");
            return;
        }

        if (aiPrompt) {
            aiPrompt.value = promptGuiado;
        }

        closeGuidedModal();
        cargarImagenIA(promptGuiado);
    });
}

// Pushes an AI-generated image into the session history (replace on regenerate).
function guardarEnHistorialIA(img, esRegenerar) {
    if (!aiHistory || !img) return;

    if (esRegenerar && indiceIAActiva !== -1 && historialIA[indiceIAActiva]) {
        historialIA[indiceIAActiva] = img.src;
    } else {
        historialIA.unshift(img.src);
        indiceIAActiva = 0;

        if (historialIA.length > 6) {
            historialIA.pop();
        }
    }

    imagenIAActiva = img.src;
    pintarHistorialIA();
    actualizarBotonClearAI();
}

// Applies a history thumbnail as the current customer image layer.
function usarImagenIA(src, index) {
    const nuevaImg = new Image();
    nuevaImg.crossOrigin = "anonymous";

    setCanvasLoading(true, t("applyingImage"));

    nuevaImg.onload = function () {
        const s = estado();

        guardarCaraActual();

        s.image = nuevaImg;
        s.useImage = true;

        // Prefer AI panel controls when applying an AI history image.
        s.imageSize = aiImageSize ? parseInt(aiImageSize.value, 10) : (imageSize ? parseInt(imageSize.value, 10) : s.imageSize);
        s.imageRotation = aiImageRotation ? parseInt(aiImageRotation.value, 10) : (imageRotation ? parseInt(imageRotation.value, 10) : s.imageRotation);
        s.imageLayer = aiImageLayer ? aiImageLayer.value : (imageLayer ? imageLayer.value : s.imageLayer);
        s.imageX = aiImageX ? parseInt(aiImageX.value, 10) : (imageX ? parseInt(imageX.value, 10) : s.imageX);
        s.imageY = aiImageY ? parseInt(aiImageY.value, 10) : (imageY ? parseInt(imageY.value, 10) : s.imageY);

        imagenIAActiva = src;
        indiceIAActiva = index;

        if (useImage) useImage.checked = true;

        const hasImage = designerRoot.querySelector('[name="iapc_has_image"]');
        if (hasImage) hasImage.value = "yes";

        // Prefer the resolved browser URL so adjust-enable matching stays stable.
        imagenIAActiva = nuevaImg.src || src;
        if (Array.isArray(historialIA) && index >= 0 && index < historialIA.length) {
            historialIA[index] = imagenIAActiva;
        }

        syncControlesTransformImagen(s);

        actualizarOpcionesVisibles();
        actualizarBotonClearAI();
        actualizarPrecioPersonalizado();
        setCanvasLoading(false);
        dibujar({ skipGuardar: true });
    };

    nuevaImg.onerror = function () {
        setCanvasLoading(false);
        iapcNotify(t("aiImageLoadError"), "error");
    };

    nuevaImg.src = src;
}

// Renders AI history thumbnails in the panel.
function pintarHistorialIA() {
    const contenedor = document.getElementById("iapc_ai_history");

    if (!contenedor) return;

    contenedor.innerHTML = "";

    historialIA.forEach(function (src, index) {
        const miniatura = document.createElement("button");
        miniatura.type = "button";
        miniatura.className = "iapc-ai-thumb";

        if (src === imagenIAActiva) {
            miniatura.classList.add("iapc-ai-thumb-active");
        }

        const img = document.createElement("img");
        img.src = src;
        img.alt = t("aiImageAlt");

        miniatura.appendChild(img);

        miniatura.addEventListener("click", function () {
            document.querySelectorAll(".iapc-ai-thumb").forEach(function (thumb) {
                thumb.classList.remove("iapc-ai-thumb-active");
            });

            miniatura.classList.add("iapc-ai-thumb-active");

            usarImagenIA(src, index);
        });

        contenedor.appendChild(miniatura);
    });

    actualizarBotonClearAI();
}

// Enables or disables the generate button based on prompt and remaining quota.
function actualizarBotonIA() {
    if (!generateAiBtn || !aiPrompt) return;

    const prompt = aiPrompt.value.trim();

    if (
        ultimoPromptGenerado &&
        prompt &&
        prompt === ultimoPromptGenerado
    ) {
        generateAiBtn.textContent = t("regenerate");
    } else {
        generateAiBtn.textContent = t("generateImageWithAi");
    }
}

// --- Session / interaction state ---
let customStickerImage = null;
let dragTarget = null;
let iapcHayImagenCliente = false;

// --- 2D canvas spin preview (mug) ---
let iapcVista3DActiva = false;
let iapcAnimacion3D = null;
let iapcAngulo3D = 0;

// Per-view design state (front, back, left, right, top).
const vistas = {
    front: crearEstadoInicial(),
    back: crearEstadoInicial(),
    left: crearEstadoInicial(),
    right: crearEstadoInicial(),
    top: crearEstadoInicial()
};

let vistaActual = "front";

const stickerImages = {};

// Preload sticker assets from IAPC_DATA.
Object.keys(stickersPropios).forEach(function (key) {
    const img = new Image();
    img.crossOrigin = "anonymous";

    img.onload = function () {
        stickerImages[key] = img;
        dibujar();
    };

    img.onerror = function () {
        console.error("No se pudo cargar el sticker:", key, stickersPropios[key]);
    };

    img.src = stickersPropios[key];
});
// Preload graphic element assets from IAPC_DATA.
Object.keys(graphicsPropios).forEach(function (key) {
    const img = new Image();
    img.crossOrigin = "anonymous";

    img.onload = function () {
        graphicImages[key] = img;
        dibujar();
    };

    img.onerror = function () {
        console.error("No se pudo cargar el elemento gráfico:", key, graphicsPropios[key]);
    };

    img.src = graphicsPropios[key];
});

    // --- Mug mockup images ---
    const mugImage = new Image();
mugImage.crossOrigin = "anonymous";

const overlayImage = new Image();
overlayImage.crossOrigin = "anonymous";

overlayImage.onload = function () {
    dibujar();
};

if (typeof IAPC_DATA !== "undefined" && IAPC_DATA.overlay) {
    overlayImage.src = IAPC_DATA.overlay;
}

mugImage.onload = function () {
    canvas.width = mugImage.naturalWidth || 900;
    canvas.height = mugImage.naturalHeight || 900;
    dibujar();
};

mugImage.onerror = function () {
    console.error("Could not load the base product image.");
};

    // --- Per-view design state ---
    // Returns the default state object for a single product view (front/back/…).
    function crearEstadoInicial() {
        return {
            useText: true,
            text: "",
            font: "Arial",
            color: "#000000",
            fontSize: 26,
            bold: false,
            italic: false,
            textX: 50,
            textY: 55,
            textAlign: "center",
            letterSpacing: 0,
            textCurve: 0,

            useImage: false,
            image: null,
            imageSize: 100,
            imageRotation: 0,
            imageLayer: "behind",
            imageX: 50,
            imageY: 45,

            useSticker: false,
            selectedSticker: "",
            customSticker: null,
            hasCustomSticker: false,
            rights: false,
            stickerSize: 80,
            stickerRotation: 0,
            stickerLayer: "behind",
            stickerX: 50,
            stickerY: 45,
            stickers: [],

            paintCanvas: null,
            paintCtx: null,

            layers: [],
            selectedLayerId: null
        };
    }

    // Clamps a numeric value between min and max.
    function clamp(valor, min, max) {
        return Math.max(min, Math.min(max, valor));
    }
    // Generates a unique layer id.
    function crearIdCapa() {
    return "layer_" + Date.now() + "_" + Math.floor(Math.random() * 10000);
    }

    // Returns the printable area rectangle for the active view (pixels + rotation).
    function getZonaImprimible() {
        const areas =
            typeof IAPC_DATA !== "undefined" && IAPC_DATA.printAreas
                ? IAPC_DATA.printAreas
                : {};
        const defaults = {
            x: 0.25,
            y: 0.25,
            w: 0.5,
            h: 0.5,
            rotation: 0
        };
        const source = areas[vistaActual] || areas.front || defaults;
        const area = {
            x: parseFloat(source.x) || defaults.x,
            y: parseFloat(source.y) || defaults.y,
            w: parseFloat(source.w) || defaults.w,
            h: parseFloat(source.h) || defaults.h,
            rotation: parseFloat(source.rotation) || 0
        };

        return {
            x: canvas.width * area.x,
            y: canvas.height * area.y,
            w: canvas.width * area.w,
            h: canvas.height * area.h,
            rotation: area.rotation
        };
    }

    // Returns the state object for the currently active view.
    function estado() {
        return vistas[vistaActual];
    }

    // --- UI control helpers ---
    function setRange(el, value) {
        if (el) el.value = String(value);
    }

    // Sets a checkbox input if the element exists.
    function setChecked(el, value) {
        if (el) el.checked = !!value;
    }

    // Sets a text/select input value if the element exists.
    function setValue(el, value) {
        if (el) el.value = value;
    }

    // True when the active view's customer image comes from AI generation.
    function normalizeImageSrc(src) {
        if (!src) {
            return "";
        }

        try {
            return new URL(String(src), window.location.href).href;
        } catch (err) {
            return String(src);
        }
    }

    function imagenActualEsIA() {
        const s = estado();

        if (!s || !s.useImage || !s.image || !s.image.src) {
            return false;
        }

        const current = normalizeImageSrc(s.image.src);

        if (imagenIAActiva && normalizeImageSrc(imagenIAActiva) === current) {
            return true;
        }

        if (Array.isArray(historialIA)) {
            for (let i = 0; i < historialIA.length; i++) {
                if (normalizeImageSrc(historialIA[i]) === current) {
                    return true;
                }
            }
        }

        // Fallback when CDN/browser rewrites the URL after load.
        if (imagenIAActiva && indiceIAActiva >= 0) {
            return true;
        }

        return false;
    }

    // Shows Remove AI and enables adjust controls only when an AI image is active.
    function actualizarBotonClearAI() {
        const tieneIA = imagenActualEsIA();

        if (clearAiBtn) {
            clearAiBtn.classList.toggle("iapc-hidden", !tieneIA);
        }

        // Resolve at call time: panel may be inside the wrapper or body portal.
        const adjustBlock =
            document.getElementById("iapc_ai_adjust_block") || $("iapc_ai_adjust_block");
        const controls = [
            document.getElementById("iapc_ai_image_size") || aiImageSize,
            document.getElementById("iapc_ai_image_rotation") || aiImageRotation,
            document.getElementById("iapc_ai_image_layer") || aiImageLayer,
            document.getElementById("iapc_ai_image_x") || aiImageX,
            document.getElementById("iapc_ai_image_y") || aiImageY
        ];

        if (adjustBlock) {
            adjustBlock.classList.toggle("iapc-controls-disabled", !tieneIA);
        }

        controls.forEach(function (el) {
            if (el) {
                el.disabled = !tieneIA;
            }
        });
    }

    // Mirrors image transform values into both Image and AI panel controls
    // (cart still posts the Image-panel named fields).
    function syncControlesTransformImagen(s) {
        const size = s.imageSize;
        const rotation = s.imageRotation;
        const layer = s.imageLayer;
        const x = s.imageX;
        const y = s.imageY;

        setRange(imageSize, size);
        setRange(imageRotation, rotation);
        setValue(imageLayer, layer);
        setRange(imageX, x);
        setRange(imageY, y);

        setRange(aiImageSize, size);
        setRange(aiImageRotation, rotation);
        setValue(aiImageLayer, layer);
        setRange(aiImageX, x);
        setRange(aiImageY, y);
    }

    // Reads transform controls from the panel that owns the current image source.
    function leerControlesTransformImagen(s) {
        const esIA = imagenActualEsIA();
        const sizeEl = esIA && aiImageSize ? aiImageSize : imageSize;
        const rotationEl = esIA && aiImageRotation ? aiImageRotation : imageRotation;
        const layerEl = esIA && aiImageLayer ? aiImageLayer : imageLayer;
        const xEl = esIA && aiImageX ? aiImageX : imageX;
        const yEl = esIA && aiImageY ? aiImageY : imageY;

        if (sizeEl) s.imageSize = parseInt(sizeEl.value, 10);
        if (rotationEl) s.imageRotation = parseInt(rotationEl.value, 10);
        if (layerEl) s.imageLayer = layerEl.value;
        if (xEl) s.imageX = parseInt(xEl.value, 10);
        if (yEl) s.imageY = parseInt(yEl.value, 10);
    }

    // Persists UI control values into the active view state.
    function guardarCaraActual() {
        const s = estado();

        const textAlignInput = $("iapc_text_align");
        const textSpacingInput = $("iapc_text_spacing");
        const textCurveInput = $("iapc_text_curve");
        
        s.textAlign = textAlignInput ? textAlignInput.value : "center";
        s.letterSpacing = textSpacingInput ? parseInt(textSpacingInput.value, 10) || 0 : 0;
        s.textCurve = textCurveInput ? parseInt(textCurveInput.value, 10) || 0 : 0;

        if (useImage) {
    s.useImage = !!useImage.checked;
}

leerControlesTransformImagen(s);

        s.useSticker = !!(useSticker && useSticker.checked);
        s.selectedSticker = stickerSelect ? stickerSelect.value : "";
        s.customSticker = customStickerImage;
        s.hasCustomSticker = !!(hasCustomStickerInput && hasCustomStickerInput.value === "yes");
        s.rights = !!(rightsConfirm && rightsConfirm.checked);
        s.stickerSize = stickerSize ? parseInt(stickerSize.value, 10) : 80;
        s.stickerRotation = stickerRotation ? parseInt(stickerRotation.value, 10) : 0;
        s.stickerLayer = stickerLayer ? stickerLayer.value : "behind";
        s.stickerX = stickerX ? parseInt(stickerX.value, 10) : 50;
        s.stickerY = stickerY ? parseInt(stickerY.value, 10) : 45;

        guardarStickersData();
    }

    // Loads the active view state back into UI controls and redraws.
    function cargarCaraActual() {
        const s = estado();

        setChecked(useText, s.useText);
        cargarControlesTextoSeleccionado();
        setValue($("iapc_text_align"), s.textAlign || "center");
        setRange($("iapc_text_spacing"), s.letterSpacing || 0);
        setRange($("iapc_text_curve"), s.textCurve || 0);

        setChecked(useImage, s.useImage);
        syncControlesTransformImagen(s);

        setChecked(useSticker, s.useSticker);
        setValue(stickerSelect, s.selectedSticker);
        customStickerImage = s.customSticker;
        setValue(hasCustomStickerInput, s.hasCustomSticker ? "yes" : "no");
        setChecked(rightsConfirm, s.rights);
        setRange(stickerSize, s.stickerSize);
        setRange(stickerRotation, s.stickerRotation);
        setValue(stickerLayer, s.stickerLayer);
        setRange(stickerX, s.stickerX);
        setRange(stickerY, s.stickerY);

        if (imageInput) imageInput.value = "";
        actualizarNombreArchivoImagen("");
        if (customStickerInput) customStickerInput.value = "";

        actualizarOpcionesVisibles();
        guardarStickersData();
        
        setTimeout(function () {
            dibujar();
        }, 0);
        }

    // Toggles panel visibility and syncs side-button active state.
    function actualizarOpcionesVisibles() {
        if (textOptions && useText) {
            textOptions.classList.toggle("iapc-hidden", !useText.checked);
        }
    
        if (imageOptions && useImage) {
            imageOptions.classList.toggle("iapc-hidden", !useImage.checked);
        }
    
        if (stickerOptions && useSticker) {
            stickerOptions.classList.toggle("iapc-hidden", !useSticker.checked);
        }
    document.querySelectorAll(".iapc-side-btn").forEach(function (btn) {
    btn.classList.toggle("iapc-active", btn.dataset.view === vistaActual);
    });
        if (currentSideInput) currentSideInput.value = vistaActual;
        actualizarEstadoCustomizacion();
        actualizarBotonClearAI();
    }

    // --- Product base image & view availability ---
    // Loads the product mockup image for the active view and redraws.
    function cambiarImagenBase() {
        if (typeof IAPC_DATA === "undefined") {
            return;
        }

        if (!productoEsPersonalizable()) {
            mugImageOverride = null;

            if (!canvas.width || !canvas.height) {
                canvas.width = 900;
                canvas.height = 900;
            }

            dibujarPlaceholderSinImagen();
            actualizarEstadoCustomizacion();
            return;
        }

        const nuevaImagen = getImagenBaseParaVista(vistaActual);

        if (!nuevaImagen) {
            if (!canvas.width || !canvas.height) {
                canvas.width = 900;
                canvas.height = 900;
            }

            dibujarPlaceholderSinImagen();
            actualizarEstadoCustomizacion();
            return;
        }

        mugImage.onload = function () {
            canvas.width = mugImage.naturalWidth || 900;
            canvas.height = mugImage.naturalHeight || 900;
            dibujar();
            actualizarEstadoCustomizacion();
        };

        mugImage.onerror = function () {
            console.error("No se pudo cargar la imagen:", nuevaImagen);
            dibujarPlaceholderSinImagen();
            actualizarEstadoCustomizacion();
        };

        mugImage.src = nuevaImagen;
    }

    // Returns the base product image URL for a given view key.
    function getImagenBaseParaVista(lado) {
        if (typeof IAPC_DATA === "undefined") {
            return "";
        }

        if (
            IAPC_DATA.views &&
            IAPC_DATA.views[lado] &&
            IAPC_DATA.views[lado].image
        ) {
            return IAPC_DATA.views[lado].image;
        }

        if (lado === "front" && IAPC_DATA.frontImage) {
            return IAPC_DATA.frontImage;
        }

        if (lado === "back" && IAPC_DATA.backImage) {
            return IAPC_DATA.backImage;
        }

        return "";
    }

    // Returns true when the product has a mockup image for the given view.
    function vistaTieneImagenProducto(lado) {
        return !!getImagenBaseParaVista(lado || vistaActual);
    }

    // Returns true when at least one product view has a mockup image.
    function productoTieneAlgunaImagen() {
        if (typeof IAPC_DATA === "undefined") {
            return false;
        }

        if (IAPC_DATA.views) {
            return Object.keys(IAPC_DATA.views).some(function (viewKey) {
                return vistaTieneImagenProducto(viewKey);
            });
        }

        return !!(IAPC_DATA.frontImage || IAPC_DATA.backImage);
    }

    // Returns the first view key that has a product mockup image.
    function obtenerPrimeraVistaDisponible() {
        if (typeof IAPC_DATA !== "undefined" && IAPC_DATA.views) {
            const vistas = Object.keys(IAPC_DATA.views);

            for (let i = 0; i < vistas.length; i++) {
                if (vistaTieneImagenProducto(vistas[i])) {
                    return vistas[i];
                }
            }
        }

        if (vistaTieneImagenProducto("front")) {
            return "front";
        }

        if (vistaTieneImagenProducto("back")) {
            return "back";
        }

        return "front";
    }

    // Toggles disabled/aria-disabled on a control element.
    function setElementoHabilitado(el, habilitado) {
        if (!el) {
            return;
        }

        el.disabled = !habilitado;

        if (habilitado) {
            el.removeAttribute("aria-disabled");
        } else {
            el.setAttribute("aria-disabled", "true");
        }
    }

    // Returns true when the product has a configured base price.
    function productoTienePrecioBase() {
        return !!(typeof IAPC_DATA !== "undefined" && IAPC_DATA.hasBasePrice);
    }

    // Returns true when the product can be customized (image + price).
    function productoEsPersonalizable() {
        return productoTieneAlgunaImagen() && productoTienePrecioBase();
    }

    // Enables/disables designer controls, canvas, and add-to-cart based on availability.
    function actualizarEstadoCustomizacion() {
        const personalizable = productoEsPersonalizable();
        const vistaDisponible = personalizable && vistaTieneImagenProducto(vistaActual);
        const leftPanel = designerRoot.querySelector(".iapc-designer-left");

        if (leftPanel) {
            leftPanel.classList.toggle("iapc-view-unavailable", !vistaDisponible);
        }

        const controles = designerRoot.querySelectorAll(
            ".iapc-canva-menu button, .iapc-canva-content input, .iapc-canva-content select, .iapc-canva-content button, .iapc-canva-content textarea, .iapc-canva-content summary, .iapc-actions .iapc-btn, #iapc_enable_paint, #iapc_eraser_paint, #iapc_undo_paint, #iapc_clear_paint"
        );

        controles.forEach(function (el) {
            setElementoHabilitado(el, vistaDisponible);
        });

        if (canvas) {
            canvas.style.pointerEvents = vistaDisponible ? "" : "none";
            canvas.classList.toggle("iapc-canvas-disabled", !vistaDisponible);
        }

        const priceCard = designerRoot.querySelector(".iapc-price-card");

        if (priceCard) {
            priceCard.classList.toggle("iapc-product-not-customizable", !personalizable);
        }

        const cartBtn = designerRoot.querySelector(".single_add_to_cart_button");
        const qtyInput = designerRoot.querySelector(".quantity input");

        setElementoHabilitado(cartBtn, personalizable);
        setElementoHabilitado(qtyInput, personalizable);
    }

    // Returns the human-readable label for a view (Side A, Side B, etc.).
    function getEtiquetaVista(lado) {
        if (
            typeof IAPC_DATA !== "undefined" &&
            IAPC_DATA.views &&
            IAPC_DATA.views[lado] &&
            IAPC_DATA.views[lado].label
        ) {
            return IAPC_DATA.views[lado].label;
        }

        return lado === "back" ? "Side B" : "Side A";
    }

    // Returns the placeholder message shown when a view has no product image.
    function getMensajePlaceholderVista(lado) {
        if (!productoEsPersonalizable()) {
            return t("noProductImages");
        }

        return t("missingProductImageForSide", getEtiquetaVista(lado || vistaActual));
    }

    // Draws word-wrapped centered text on a canvas context.
    function dibujarTextoEnvuelto(contexto, texto, x, y, maxWidth, lineHeight) {
        const palabras = String(texto).split(" ");
        const lineas = [];
        let linea = "";

        palabras.forEach(function (palabra) {
            const prueba = linea ? linea + " " + palabra : palabra;

            if (contexto.measureText(prueba).width > maxWidth && linea) {
                lineas.push(linea);
                linea = palabra;
            } else {
                linea = prueba;
            }
        });

        if (linea) {
            lineas.push(linea);
        }

        const totalHeight = lineas.length * lineHeight;
        let startY = y - totalHeight / 2 + lineHeight / 2;

        lineas.forEach(function (lineaTexto) {
            contexto.fillText(lineaTexto, x, startY);
            startY += lineHeight;
        });
    }

    // Draws the empty-state placeholder when no product image is available.
    function dibujarPlaceholderSinImagen() {
        const w = Math.max(canvas.width || 0, 900);
        const h = Math.max(canvas.height || 0, 900);

        canvas.width = w;
        canvas.height = h;

        ctx.clearRect(0, 0, w, h);
        ctx.fillStyle = "#f3f4f6";
        ctx.fillRect(0, 0, w, h);

        ctx.strokeStyle = "#9ca3af";
        ctx.lineWidth = 3;
        ctx.setLineDash([14, 10]);
        ctx.strokeRect(w * 0.1, h * 0.1, w * 0.8, h * 0.8);
        ctx.setLineDash([]);

        ctx.fillStyle = "#4b5563";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";

        const message = getMensajePlaceholderVista(vistaActual);
        const maxWidth = w * 0.68;
        const fontSize = Math.max(22, Math.min(36, w / 16));

        ctx.font = "600 " + fontSize + "px Arial, sans-serif";
        dibujarTextoEnvuelto(ctx, message, w / 2, h / 2, maxWidth, fontSize * 1.4);
    }

    const mugImagesCache = {};
    let mugImageOverride = null;

    // Preloads front/back mockup images into an in-memory cache for fast PNG export.
    function precargarImagenesMug() {
        ["front", "back"].forEach(function (lado) {
            const src = getImagenBaseParaVista(lado);

            if (!src) {
                return;
            }

            const img = new Image();
            img.crossOrigin = "anonymous";
            img.onload = function () {
                mugImagesCache[lado] = img;
            };
            img.src = src;
        });
    }

    precargarImagenesMug();

    // --- Export & cart data helpers ---
    // Renders a view to PNG (base64 data URL) for cart submission.
    function exportarPngCara(lado, skipGuardar) {
        if (!productoEsPersonalizable()) {
            return "";
        }

        if (!vistas[lado]) {
            vistas[lado] = crearEstadoInicial();
        }

        if (!getImagenBaseParaVista(lado)) {
            return "";
        }

        const vistaAnterior = vistaActual;
        vistaActual = lado;

        const cached = mugImagesCache[lado];
        if (cached && cached.complete && cached.naturalWidth) {
            canvas.width = cached.naturalWidth || 900;
            canvas.height = cached.naturalHeight || 900;
            mugImageOverride = cached;
        } else {
            mugImageOverride = null;
            const src = getImagenBaseParaVista(lado);
            if (src && mugImage.src !== src) {
                mugImage.src = src;
            }
        }

        dibujar({
            skipGuardar: !!skipGuardar,
            mostrarGuias: false
        });
        
        const png = canvas.toDataURL("image/png");
        mugImageOverride = null;
        vistaActual = vistaAnterior;

        return png;
    }

 // Concatenates all text layer strings on a view (used for cart metadata).
 function getTextoCara(lado) {
    const s = vistas[lado];

    if (!s || !Array.isArray(s.layers)) {
        return "";
    }

    return s.layers
        .filter(function (layer) {
            return layer.type === "text" && layer.text;
        })
        .map(function (layer) {
            return layer.text;
        })
        .join(" / ");
}

    // Returns true when a view has a customer image or image/sticker layer.
    function caraTieneImagen(lado) {
        const s = vistas[lado];

        if (!s) {
            return false;
        }

        if (s.image) {
            return true;
        }

        if (!Array.isArray(s.layers)) {
            return false;
        }

        return s.layers.some(function (layer) {
            return layer.type === "image" || (layer.type === "sticker" && layer.img);
        });
    }

    // Writes iapc_has_image_front/back hidden fields for a view.
    function actualizarFlagsImagenCara(lado) {
        const tieneImagen = caraTieneImagen(lado) ? "yes" : "no";
        const campo = lado === "back" ? "iapc_has_image_back" : "iapc_has_image_front";
        setCampoCarrito(campo, tieneImagen);
    }

    // Collects all layers from front and back views for cart serialization.
    function getStickersTodasLasCaras() {
        const all = [];

        ["front", "back"].forEach(function (lado) {
            const s = vistas[lado];

            if (!s || !Array.isArray(s.layers)) {
                return;
            }

            s.layers.forEach(function (layer) {
                all.push({
                    type: layer.type,
                    key: layer.key || "",
                    src: layer.img && layer.img.src ? layer.img.src : "",
                    x: layer.x,
                    y: layer.y,
                    size: layer.size,
                    rotation: layer.rotation,
                    layer: layer.layer,
                    side: lado
                });
            });
        });

        return all;
    }

    // Exports the customer-upload image on a view as a PNG data URL.
    function getImagenClientePng(lado) {
        const s = vistas[lado];

        if (!s || !s.image) {
            return "";
        }

        const tempCanvas = document.createElement("canvas");
        const tempCtx = tempCanvas.getContext("2d");

        tempCanvas.width = s.image.naturalWidth || s.image.width;
        tempCanvas.height = s.image.naturalHeight || s.image.height;
        tempCtx.drawImage(s.image, 0, 0);

        return tempCanvas.toDataURL("image/png");
    }


    // Validates file size and MIME type for user uploads.
    function validarArchivoImagen(file, mensajeTipo) {
        if (!file) {
            return false;
        }

        if (file.size > maxFileSizeBytes) {
            iapcNotify(t("fileTooLarge", mensajeTipo, maxFileSizeMB), "error");
            return false;
        }

        if (!allowedImageTypes.includes(file.type)) {
            iapcNotify(t("invalidFileType"), "error");
            return false;
        }

        return true;
    }

    // Syncs the current view layers JSON into the stickers hidden input.
    function guardarStickersData() {
        if (!stickersDataInput) {
            return;
        }

        stickersDataInput.value = JSON.stringify(
            estado().layers.map(function (sticker) {
                return {
                    type: sticker.type,
                    key: sticker.key || "",
                    x: sticker.x,
                    y: sticker.y,
                    size: sticker.size,
                    rotation: sticker.rotation,
                    layer: sticker.layer
                };
            })
        );
    }

    // Reads a File object into an HTMLImageElement via FileReader.
    function leerImagen(file, callback) {
        const reader = new FileReader();

        reader.onload = function (event) {
            const img = new Image();

            img.onload = function () {
                callback(img);
            };

            img.src = event.target.result;
        };

        reader.readAsDataURL(file);
    }

    // Shows the uploaded file name under the input; tooltip only if truncated.
    function actualizarNombreArchivoImagen(nombre) {
        if (!imageFilename) {
            return;
        }

        imageFilename.removeAttribute("title");
        ocultarTooltipArchivo();

        if (!nombre) {
            imageFilename.hidden = true;
            imageFilename.textContent = "";
            imageFilename.removeAttribute("data-tooltip");
            imageFilename.removeAttribute("tabindex");
            return;
        }

        imageFilename.textContent = nombre;
        imageFilename.hidden = false;
        enlazarTooltipArchivo();

        requestAnimationFrame(function () {
            if (imageFilename.scrollWidth > imageFilename.clientWidth + 1) {
                imageFilename.setAttribute("data-tooltip", nombre);
                imageFilename.setAttribute("tabindex", "0");
            } else {
                imageFilename.removeAttribute("data-tooltip");
                imageFilename.removeAttribute("tabindex");
            }
        });
    }

    var iapcFileTooltipEl = null;

    function ocultarTooltipArchivo() {
        if (iapcFileTooltipEl) {
            iapcFileTooltipEl.remove();
            iapcFileTooltipEl = null;
        }
    }

    function mostrarTooltipArchivo() {
        if (!imageFilename) {
            return;
        }

        var texto = imageFilename.getAttribute("data-tooltip");

        if (!texto) {
            return;
        }

        ocultarTooltipArchivo();

        iapcFileTooltipEl = document.createElement("div");
        iapcFileTooltipEl.className = "iapc-tooltip-bubble";
        iapcFileTooltipEl.textContent = texto;
        document.body.appendChild(iapcFileTooltipEl);

        var rect = imageFilename.getBoundingClientRect();
        var tipW = iapcFileTooltipEl.offsetWidth;
        var tipH = iapcFileTooltipEl.offsetHeight;
        var left = Math.min(rect.left, window.innerWidth - tipW - 12);
        var top = rect.bottom + 8;

        if (top + tipH > window.innerHeight - 8) {
            top = Math.max(8, rect.top - tipH - 8);
        }

        iapcFileTooltipEl.style.left = Math.max(8, left) + "px";
        iapcFileTooltipEl.style.top = top + "px";
    }

    function enlazarTooltipArchivo() {
        if (!imageFilename || imageFilename.dataset.iapcTooltipBound === "1") {
            return;
        }

        imageFilename.dataset.iapcTooltipBound = "1";
        imageFilename.addEventListener("mouseenter", mostrarTooltipArchivo);
        imageFilename.addEventListener("mouseleave", ocultarTooltipArchivo);
        imageFilename.addEventListener("focus", mostrarTooltipArchivo);
        imageFilename.addEventListener("blur", ocultarTooltipArchivo);
        window.addEventListener("scroll", ocultarTooltipArchivo, true);
        window.addEventListener("resize", ocultarTooltipArchivo);
    }

    // Returns the customer image on the active view, if any.
    function getCustomerImage() {
        return estado().image;
    }

    // --- Image drawing ---
    function dibujarImagen() {
        const s = estado();
        const customerImage = getCustomerImage();

        if (!(s.useImage && customerImage)) {
            return;
        }

        const size = parseInt(s.imageSize, 10);
        const rotation = parseInt(s.imageRotation, 10) * Math.PI / 180;
        const ratio = customerImage.width / customerImage.height;

        let drawWidth = size;
        let drawHeight = size / ratio;

        if (drawWidth > designCanvas.width) {
            drawWidth = designCanvas.width;
            drawHeight = drawWidth / ratio;
        }

        if (drawHeight > designCanvas.height) {
            drawHeight = designCanvas.height;
            drawWidth = drawHeight * ratio;
        }

        const x = designCanvas.width * (parseInt(s.imageX, 10) / 100);
        const y = designCanvas.height * (parseInt(s.imageY, 10) / 100);

        // Looser bottom/side bounds so the image can move comfortably without leaving entirely.
        const safeX = clamp(x, drawWidth * 0.35, designCanvas.width - drawWidth * 0.35);
        const safeY = clamp(y, drawHeight * 0.25, designCanvas.height - drawHeight * 0.15);

        designCtx.save();
        designCtx.translate(safeX, safeY);
        designCtx.rotate(rotation);
        designCtx.globalAlpha = 0.95;
        designCtx.drawImage(customerImage, -drawWidth / 2, -drawHeight / 2, drawWidth, drawHeight);
        designCtx.restore();
    }



// Draws text with custom letter spacing on a canvas context.
function dibujarTextoConEspaciado(contexto, texto, x, y, letterSpacing) {
    texto = String(texto || "");

    if (!letterSpacing) {
        contexto.fillText(texto, x, y);
        return;
    }

    const alignOriginal = contexto.textAlign;
    contexto.textAlign = "left";

    let anchoTotal = 0;

    for (let i = 0; i < texto.length; i++) {
        anchoTotal += contexto.measureText(texto[i]).width;
        if (i < texto.length - 1) anchoTotal += letterSpacing;
    }

    let inicioX = x;

    if (alignOriginal === "center") {
        inicioX = x - anchoTotal / 2;
    } else if (alignOriginal === "right") {
        inicioX = x - anchoTotal;
    }

    for (let i = 0; i < texto.length; i++) {
        contexto.fillText(texto[i], inicioX, y);
        inicioX += contexto.measureText(texto[i]).width + letterSpacing;
    }

    contexto.textAlign = alignOriginal;
}


// Draws curved text along an arc, respecting left/center/right alignment.
function dibujarTextoCurvado(contexto, texto, radio, curva, letterSpacing, align) {
    texto = String(texto || "");

    if (!texto.trim()) return;

    const chars = texto.split("");
    const intensidad = Math.abs(curva) / 100;
    const direccion = curva >= 0 ? 1 : -1;
    const textAlign = align || "center";

    const radius = Math.max(80, radio);
    const totalWidth = chars.reduce(function (total, char, index) {
        return total + contexto.measureText(char).width + (index < chars.length - 1 ? letterSpacing : 0);
    }, 0);

    let currentWidth;

    if (textAlign === "left") {
        currentWidth = 0;
    } else if (textAlign === "right") {
        currentWidth = -totalWidth;
    } else {
        currentWidth = -totalWidth / 2;
    }

    contexto.textAlign = "center";
    contexto.textBaseline = "middle";

    chars.forEach(function (char) {
        const charWidth = contexto.measureText(char).width;
        const charCenter = currentWidth + charWidth / 2;
        const angle = (charCenter / radius) * intensidad;

        const x = Math.sin(angle) * radius;
        const y = direccion * (1 - Math.cos(angle)) * radius;

        contexto.save();
        contexto.translate(x, y);
        contexto.rotate(angle * direccion);
        contexto.fillText(char, 0, 0);
        contexto.restore();

        currentWidth += charWidth + letterSpacing;
    });
}

// Draws multi-line text with auto-shrink to fit the printable area.
function dibujarTextoMultilinea(contexto, texto, maxWidth, maxHeight, fontBaseSize, fontFamily, fontStyle, fontWeight, align, letterSpacing) {
    let size = fontBaseSize;
    let lineHeight = size * 1.2;
    let lineas = [];

    // Measures text width including letter-spacing gaps.
    function medirTextoConEspaciado(txt) {
        let ancho = contexto.measureText(txt).width;
        ancho += Math.max(0, txt.length - 1) * letterSpacing;
        return ancho;
    }

    // Word-wraps text into lines capped at four rows.
    function crearLineas() {
        const palabras = String(texto || "").split(" ");
        const resultado = [];
        let lineaActual = "";

        palabras.forEach(function (palabra) {
            const prueba = lineaActual + palabra + " ";
            const ancho = medirTextoConEspaciado(prueba);

            if (ancho > maxWidth && lineaActual !== "") {
                resultado.push(lineaActual.trim());
                lineaActual = palabra + " ";
            } else {
                lineaActual = prueba;
            }
        });

        resultado.push(lineaActual.trim());

        if (resultado.length > 4) {
            const extras = resultado.slice(3).join(" ");
            resultado.splice(3);
            resultado.push(extras);
        }

        return resultado;
    }

    do {
        contexto.font = fontStyle + " " + fontWeight + " " + size + "px " + fontFamily;
        lineHeight = size * 1.2;
        lineas = crearLineas();

        if ((lineas.length * lineHeight) > maxHeight) {
            size--;
        } else {
            break;
        }
    } while (size >= 12);

    contexto.textAlign = align || "center";

    const offsetInicial = -((lineas.length - 1) * lineHeight) / 2;

    lineas.forEach(function (linea, index) {
        let x = 0;

        if (align === "left") {
            x = -maxWidth / 2;
        }

        if (align === "right") {
            x = maxWidth / 2;
        }

        dibujarTextoConEspaciado(
            contexto,
            linea,
            x,
            offsetInicial + (index * lineHeight),
            letterSpacing
        );
    });
}

   // --- Freehand paint mode ---
let modoPintar = false;
let modoBorrador = false;
let pintando = false;
let ultimoPuntoPintura = null;
const PAINT_UNDO_MAX = 20;
const MOBILE_CANVAS_MQ = "(max-width: 900px)";

function isMobileCanvasMode() {
    return !!(globalThis.matchMedia && globalThis.matchMedia(MOBILE_CANVAS_MQ).matches);
}

function closeMobileToolsModal() {
    if (!window.IAPC || !IAPC.tabs || typeof IAPC.tabs.closeMobileSheet !== "function") {
        return;
    }
    const root = document.querySelector(".iapc-designer-wrapper");
    if (root) {
        IAPC.tabs.closeMobileSheet(root);
    }
}

function isCanvasFullscreen() {
    return document.body.classList.contains("iapc-canvas-fs-open");
}

function syncCanvasTouchLock() {
    const lock = isMobileCanvasMode() || isCanvasFullscreen() || modoPintar;
    canvas.style.touchAction = lock ? "none" : "";
    if (wrap) {
        wrap.style.touchAction = lock ? "none" : "";
    }
}

function setCanvasFullscreen(open) {
    if (!wrap) {
        return;
    }

    const shouldOpen = !!open && isMobileCanvasMode();
    document.body.classList.toggle("iapc-canvas-fs-open", shouldOpen);
    wrap.classList.toggle("iapc-canvas-fs", shouldOpen);

    const toggle = wrap.querySelector(".iapc-canvas-fs-toggle");
    const exitBtn = wrap.querySelector(".iapc-canvas-fs-exit");
    if (toggle) {
        toggle.classList.toggle("iapc-hidden", shouldOpen || !isMobileCanvasMode());
    }
    if (exitBtn) {
        exitBtn.classList.toggle("iapc-hidden", !shouldOpen);
    }

    syncCanvasTouchLock();

    // Allow natural device rotation while editing full screen.
    if (shouldOpen) {
        try {
            if (screen.orientation && typeof screen.orientation.unlock === "function") {
                screen.orientation.unlock();
            }
        } catch (err) {}
    }

    globalThis.requestAnimationFrame(function () {
        refreshCanvasAfterViewportChange();
    });
}

function refreshCanvasAfterViewportChange() {
    if (typeof dibujar === "function") {
        dibujar();
    }
    if (typeof sincronizarPaintCanvas === "function") {
        sincronizarPaintCanvas();
    }
}

function exitCanvasFullscreen() {
    if (modoPintar) {
        modoPintar = false;
        modoBorrador = false;
        pintando = false;
        ultimoPuntoPintura = null;
        actualizarUIPaint();
    }
    setCanvasFullscreen(false);
    closeMobileToolsModal();
}

function initCanvasFullscreenUi() {
    if (!wrap) {
        return;
    }

    const toggle = wrap.querySelector(".iapc-canvas-fs-toggle");
    const exitBtn = wrap.querySelector(".iapc-canvas-fs-exit");

    if (toggle) {
        toggle.addEventListener("click", function () {
            setCanvasFullscreen(true);
        });
    }

    if (exitBtn) {
        exitBtn.addEventListener("click", function () {
            exitCanvasFullscreen();
        });
    }

    // Expose for dock tabs: any tool open → canvas-only fullscreen.
    window.IAPC = window.IAPC || {};
    IAPC.canvasFs = {
        set: setCanvasFullscreen,
        exit: exitCanvasFullscreen,
        isOpen: isCanvasFullscreen
    };

    syncCanvasTouchLock();
    if (toggle) {
        toggle.classList.toggle("iapc-hidden", !isMobileCanvasMode());
    }
    if (exitBtn) {
        exitBtn.classList.add("iapc-hidden");
    }

    function onViewportChange() {
        if (!isMobileCanvasMode() && isCanvasFullscreen()) {
            setCanvasFullscreen(false);
        }
        if (toggle) {
            toggle.classList.toggle("iapc-hidden", !isMobileCanvasMode() || isCanvasFullscreen());
        }
        if (exitBtn) {
            exitBtn.classList.toggle("iapc-hidden", !isCanvasFullscreen());
        }
        syncCanvasTouchLock();
        if (isCanvasFullscreen()) {
            globalThis.requestAnimationFrame(refreshCanvasAfterViewportChange);
        }
    }

    globalThis.addEventListener("resize", onViewportChange);
    globalThis.addEventListener("orientationchange", function () {
        globalThis.setTimeout(onViewportChange, 120);
        globalThis.setTimeout(onViewportChange, 320);
    });
}

// Returns (or creates) the offscreen paint canvas for the active view.
function getPaintCanvasActual() {
    const s = estado();

    if (!s.paintCanvas) {
        s.paintCanvas = document.createElement("canvas");
    }

    return s.paintCanvas;
}

// Returns the 2D context for the active view paint canvas.
function getPaintCtxActual() {
    const s = estado();

    if (!s.paintCanvas) {
        s.paintCanvas = document.createElement("canvas");
    }

    if (!s.paintCtx) {
        s.paintCtx = s.paintCanvas.getContext("2d");
    }

    return s.paintCtx;
}

// Returns (or creates) the per-view paint undo stack.
function getPaintUndoStack() {
    const s = estado();

    if (!Array.isArray(s.paintUndoStack)) {
        s.paintUndoStack = [];
    }

    return s.paintUndoStack;
}

const brushColor = document.getElementById("iapc_brush_color");
const brushSize = document.getElementById("iapc_brush_size");
const brushOpacity = document.getElementById("iapc_brush_opacity");
const enablePaintBtn = document.getElementById("iapc_enable_paint");
const eraserPaintBtn = document.getElementById("iapc_eraser_paint");
const undoPaintBtn = document.getElementById("iapc_undo_paint");
const clearPaintBtn = document.getElementById("iapc_clear_paint");

// Returns brush opacity as 0.1–1.
function getBrushOpacity() {
    const raw = brushOpacity ? parseInt(brushOpacity.value, 10) : 100;
    const pct = Number.isFinite(raw) ? Math.min(100, Math.max(10, raw)) : 100;
    return pct / 100;
}

// Syncs Enable brush / Eraser button labels and active styles.
function actualizarUIPaint() {
    if (enablePaintBtn) {
        const brushOn = modoPintar && !modoBorrador;
        enablePaintBtn.textContent = brushOn ? t("disableBrush") : t("enableBrush");
        enablePaintBtn.classList.toggle("iapc-paint-tool-active", brushOn);
    }

    if (eraserPaintBtn) {
        eraserPaintBtn.textContent = modoBorrador ? t("disableEraser") : t("eraser");
        eraserPaintBtn.classList.toggle("iapc-paint-tool-active", modoBorrador);
    }

    canvas.style.cursor = modoPintar ? "crosshair" : "default";
    syncCanvasTouchLock();
}

// Applies brush or eraser stroke style on the paint context.
function aplicarEstiloPincel(paintCtx) {
    paintCtx.globalAlpha = getBrushOpacity();
    paintCtx.globalCompositeOperation = modoBorrador ? "destination-out" : "source-over";
    paintCtx.lineCap = "round";
    paintCtx.lineJoin = "round";
}

// Snapshots the paint canvas before a stroke (for undo).
function guardarSnapshotPintura() {
    sincronizarPaintCanvas();

    const paintCanvas = getPaintCanvasActual();
    const paintCtx = getPaintCtxActual();
    const stack = getPaintUndoStack();

    if (!paintCanvas.width || !paintCanvas.height) {
        return;
    }

    try {
        stack.push(paintCtx.getImageData(0, 0, paintCanvas.width, paintCanvas.height));
    } catch (error) {
        return;
    }

    if (stack.length > PAINT_UNDO_MAX) {
        stack.shift();
    }
}

// Restores the previous paint snapshot.
function deshacerUltimoTrazo() {
    const stack = getPaintUndoStack();

    if (!stack.length) {
        return;
    }

    sincronizarPaintCanvas();

    const paintCanvas = getPaintCanvasActual();
    const paintCtx = getPaintCtxActual();
    const snapshot = stack.pop();

    paintCtx.save();
    paintCtx.setTransform(1, 0, 0, 1, 0, 0);
    paintCtx.globalAlpha = 1;
    paintCtx.globalCompositeOperation = "source-over";
    paintCtx.clearRect(0, 0, paintCanvas.width, paintCanvas.height);

    try {
        paintCtx.putImageData(snapshot, 0, 0);
    } catch (error) {}

    paintCtx.restore();
    dibujar();
}

// Returns the active view key used by the paint subsystem (legacy helper).
function getCaraActualPaint() {
    if (typeof vistaActual !== "undefined") {
        return vistaActual;
    }

    return "front";
}

// Resizes the paint canvas to match the main canvas, preserving existing strokes.
function sincronizarPaintCanvas() {
    const paintCanvas = getPaintCanvasActual();
    const paintCtx = getPaintCtxActual();

    if (!canvas.width || !canvas.height) {
        return;
    }

    if (paintCanvas.width === canvas.width && paintCanvas.height === canvas.height) {
        return;
    }

    const copia = document.createElement("canvas");
    copia.width = paintCanvas.width;
    copia.height = paintCanvas.height;

    if (paintCanvas.width && paintCanvas.height) {
        copia.getContext("2d").drawImage(paintCanvas, 0, 0);
    }

    paintCanvas.width = canvas.width;
    paintCanvas.height = canvas.height;

    if (copia.width && copia.height) {
        paintCtx.drawImage(copia, 0, 0, paintCanvas.width, paintCanvas.height);
    }

    // Size change invalidates ImageData snapshots.
    getPaintUndoStack().length = 0;
}

// Converts a pointer event to canvas pixel coordinates.
function getPaintPos(e) {
    const rect = canvas.getBoundingClientRect();

    return {
        x: (e.clientX - rect.left) * (canvas.width / rect.width),
        y: (e.clientY - rect.top) * (canvas.height / rect.height)
    };
}

// Returns true when a canvas point lies inside the printable area.
function puntoDentroArea(pos, area) {
    return (
        pos.x >= area.x &&
        pos.x <= area.x + area.w &&
        pos.y >= area.y &&
        pos.y <= area.y + area.h
    );
}

// Draws a single brush dot clipped to the printable area.
function dibujarPuntoPintura(pos, area) {
    const paintCtx = getPaintCtxActual();
    const grosor = parseInt(brushSize ? brushSize.value : 8, 10);

    paintCtx.save();

    paintCtx.beginPath();
    paintCtx.rect(area.x, area.y, area.w, area.h);
    paintCtx.clip();

    aplicarEstiloPincel(paintCtx);
    paintCtx.fillStyle = brushColor ? brushColor.value : "#000000";

    paintCtx.beginPath();
    paintCtx.arc(pos.x, pos.y, grosor / 2, 0, Math.PI * 2);
    paintCtx.fill();

    paintCtx.restore();
}

// Draws a brush stroke segment between two points.
function dibujarLineaPintura(desde, hasta, area) {
    const paintCtx = getPaintCtxActual();
    const grosor = parseInt(brushSize ? brushSize.value : 8, 10);

    paintCtx.save();

    paintCtx.beginPath();
    paintCtx.rect(area.x, area.y, area.w, area.h);
    paintCtx.clip();

    aplicarEstiloPincel(paintCtx);
    paintCtx.lineWidth = grosor;
    paintCtx.strokeStyle = brushColor ? brushColor.value : "#000000";

    paintCtx.beginPath();
    paintCtx.moveTo(desde.x, desde.y);
    paintCtx.lineTo(hasta.x, hasta.y);
    paintCtx.stroke();

    paintCtx.restore();
}

if (enablePaintBtn) {
    enablePaintBtn.addEventListener("click", function () {
        if (modoPintar && !modoBorrador) {
            modoPintar = false;
            modoBorrador = false;
        } else {
            modoPintar = true;
            modoBorrador = false;
        }

        pintando = false;
        ultimoPuntoPintura = null;
        actualizarUIPaint();
    });
}

if (eraserPaintBtn) {
    eraserPaintBtn.addEventListener("click", function () {
        if (modoBorrador) {
            modoBorrador = false;
            // Keep paint mode on with brush after disabling eraser.
        } else {
            modoPintar = true;
            modoBorrador = true;
        }

        pintando = false;
        ultimoPuntoPintura = null;
        actualizarUIPaint();
    });
}

if (undoPaintBtn) {
    undoPaintBtn.addEventListener("click", function () {
        deshacerUltimoTrazo();
    });
}

if (clearPaintBtn) {
    clearPaintBtn.addEventListener("click", function () {
        sincronizarPaintCanvas();

        const paintCanvas = getPaintCanvasActual();
        const paintCtx = getPaintCtxActual();

        paintCtx.clearRect(0, 0, paintCanvas.width, paintCanvas.height);
        getPaintUndoStack().length = 0;
        dibujar();
    });
}

canvas.addEventListener("pointerdown", function (e) {
    if (!vistaTieneImagenProducto(vistaActual)) return;
    if (!modoPintar) return;

    e.preventDefault();
    e.stopPropagation();

    sincronizarPaintCanvas();

    const pos = getPaintPos(e);
    const area = getZonaImprimible();

    if (!puntoDentroArea(pos, area)) {
        pintando = false;
        ultimoPuntoPintura = null;
        return;
    }

    guardarSnapshotPintura();

    pintando = true;
    ultimoPuntoPintura = pos;

    dibujarPuntoPintura(pos, area);
    dibujar();

    try {
        canvas.setPointerCapture(e.pointerId);
    } catch (error) {}
}, { passive: false });

canvas.addEventListener("pointermove", function (e) {
    if (!modoPintar || !pintando) return;

    e.preventDefault();
    e.stopPropagation();

    const pos = getPaintPos(e);
    const area = getZonaImprimible();

    if (!puntoDentroArea(pos, area)) {
        ultimoPuntoPintura = null;
        return;
    }

    if (!ultimoPuntoPintura) {
        ultimoPuntoPintura = pos;
        dibujarPuntoPintura(pos, area);
        dibujar();
        return;
    }

    dibujarLineaPintura(ultimoPuntoPintura, pos, area);

    ultimoPuntoPintura = pos;

    dibujar();
}, { passive: false });

canvas.addEventListener("pointerup", function (e) {
    if (!modoPintar) return;

    e.preventDefault();
    e.stopPropagation();

    pintando = false;
    ultimoPuntoPintura = null;

    try {
        canvas.releasePointerCapture(e.pointerId);
    } catch (error) {}
});

canvas.addEventListener("pointercancel", function () {
    pintando = false;
    ultimoPuntoPintura = null;
});

    // Draws a sticker or graphic layer onto the design canvas.
    function dibujarSticker(sticker) {
        if (!sticker || !sticker.img) {
            return;
        }

        const size = parseInt(sticker.size, 10);
        const rotation = parseInt(sticker.rotation, 10) * Math.PI / 180;

        const x = designCanvas.width * (parseInt(sticker.x, 10) / 100);
        const y = designCanvas.height * (parseInt(sticker.y, 10) / 100);

        const safeX = clamp(x, size * 0.35, designCanvas.width - size * 0.35);
        const safeY = clamp(y, size * 0.25, designCanvas.height - size * 0.15);

        designCtx.save();
        designCtx.translate(safeX, safeY);
        designCtx.rotate(rotation);
        designCtx.globalAlpha = 0.96;
        designCtx.shadowColor = "rgba(0,0,0,0.18)";
        designCtx.shadowBlur = 8;
        designCtx.shadowOffsetX = 2;
        designCtx.shadowOffsetY = 3;

        const ratio = sticker.img.width / sticker.img.height;
        let drawWidth = size;
        let drawHeight = size / ratio;

        if (drawWidth > designCanvas.width) {
            drawWidth = designCanvas.width;
            drawHeight = drawWidth / ratio;
        }

        if (drawHeight > designCanvas.height) {
            drawHeight = designCanvas.height;
            drawWidth = drawHeight * ratio;
        }

        designCtx.drawImage(sticker.img, -drawWidth / 2, -drawHeight / 2, drawWidth, drawHeight);

        const shine = designCtx.createLinearGradient(-drawWidth / 2, 0, drawWidth / 2, 0);
        shine.addColorStop(0, "rgba(255,255,255,0.22)");
        shine.addColorStop(0.5, "rgba(255,255,255,0.04)");
        shine.addColorStop(1, "rgba(0,0,0,0.08)");

        designCtx.globalCompositeOperation = "source-atop";
        designCtx.fillStyle = shine;
        designCtx.fillRect(-drawWidth / 2, -drawHeight / 2, drawWidth, drawHeight);
        designCtx.globalCompositeOperation = "source-over";
        designCtx.restore();
    }

    // Returns the in-progress sticker image (custom upload or catalog selection).
    function getStickerActualImg() {
        const selectedSticker = stickerSelect ? stickerSelect.value : "";
        return customStickerImage || (selectedSticker ? stickerImages[selectedSticker] : null);
    }

    // True when the design includes a user-uploaded sticker layer (empty catalog key).
    function designHasCustomStickerLayer(stickers) {
        const list = stickers || getStickersTodasLasCaras();

        return list.some(function (layer) {
            return layer.type === "sticker" && !layer.key;
        });
    }

    // Pending sticker preview object (catalog or upload, before Add sticker).
    function getStickerPreviewLayer() {
        const img = getStickerActualImg();

        if (!img) {
            return null;
        }

        const s = estado();

        return {
            img: img,
            x: s.stickerX,
            y: s.stickerY,
            size: s.stickerSize,
            rotation: s.stickerRotation,
            layer: s.stickerLayer || "front"
        };
    }

    // Legacy helper — draws the current sticker form values (unused by layer render).
    function dibujarStickerActual() {
        const s = estado();

        if (!s.useSticker) {
            return;
        }

        const img = getStickerActualImg();

        if (!img) {
            return;
        }

        dibujarSticker({
            type: "image",
            img: img,
            key: stickerSelect ? stickerSelect.value : "",
            x: s.stickerX,
            y: s.stickerY,
            size: s.stickerSize,
            rotation: s.stickerRotation,
            layer: s.stickerLayer
        });

    }

    // Applies a cylindrical warp so the flat design follows the mug surface.
    function dibujarDisenoCurvado(area) {
    if (!area || !area.w || !area.h || !designCanvas.width || !designCanvas.height) {
        return;
    }

    const slices = 120;

    ctx.save();
    ctx.beginPath();
    ctx.rect(area.x, area.y, area.w, area.h);
    ctx.clip();

    for (let i = 0; i < slices; i++) {
        const sx = (designCanvas.width / slices) * i;
        const sw = designCanvas.width / slices;

        const dx = area.x + (area.w / slices) * i;
        const dw = area.w / slices + 0.5;

        const progress = i / (slices - 1);
        const curve = Math.sin(progress * Math.PI);
        const offsetY = -(1 - curve) * 5;

        ctx.drawImage(
            designCanvas,
            sx,
            0,
            sw,
            designCanvas.height,
            dx,
            area.y + offsetY,
            dw,
            area.h
        );
    }

    ctx.restore();
}
// Draws a single text layer onto the design canvas.
function dibujarTextoLayer(layer) {
    if (!layer || !layer.text) {
        return;
    }

    const size = parseInt(layer.size, 10) || 26;
    const weight = layer.bold ? "bold" : "normal";
    const style = layer.italic ? "italic" : "normal";
    const font = layer.font || "Arial";

    const x = designCanvas.width * ((parseInt(layer.x, 10) || 50) / 100);
    const y = designCanvas.height * ((parseInt(layer.y, 10) || 55) / 100);

    const safeX = clamp(x, 20, designCanvas.width - 20);
    const safeY = clamp(y, 20, designCanvas.height - 20);

    designCtx.save();

    designCtx.translate(safeX, safeY);
    designCtx.fillStyle = layer.color || "#000000";
    designCtx.textBaseline = "middle";
    designCtx.globalAlpha = 0.92;
    designCtx.font = style + " " + weight + " " + size + "px " + font;
    designCtx.textAlign = layer.align || "center";

    const curve = parseInt(layer.curve || 0, 10);
const letterSpacing = parseInt(layer.letterSpacing || 0, 10);

if (curve !== 0) {
    dibujarTextoCurvado(
        designCtx,
        layer.text,
        designCanvas.width * 0.8,
        curve,
        letterSpacing,
        layer.align || "center"
    );
} else {
    dibujarTextoMultilinea(
        designCtx,
        layer.text,
        designCanvas.width * 0.75,
        designCanvas.height * 0.50,
        size,
        font,
        style,
        weight,
        layer.align || "center",
        letterSpacing
    );
}

    designCtx.restore();
}
    // Main render: product base + printable area + layers + paint overlay.
    function dibujar(opciones) {
        const opts = opciones || {};

        if (!canvas.width || !canvas.height) {
            return;
        }

        if (!productoEsPersonalizable()) {
            dibujarPlaceholderSinImagen();
            actualizarEstadoCustomizacion();
            return;
        }

        if (!vistaTieneImagenProducto(vistaActual)) {
            dibujarPlaceholderSinImagen();
            actualizarEstadoCustomizacion();
            return;
        }

        sincronizarPaintCanvas();

        if (!opts.skipGuardar) {
            guardarCaraActual();
        }

        const imagenBase = mugImageOverride || mugImage;

        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(imagenBase, 0, 0, canvas.width, canvas.height);
    
        const area = getZonaImprimible();
    
        if (!area || !area.w || !area.h) {
            return;
        }
    
        if (opts.mostrarGuias !== false && !iapcCanvasLoading) {
            ctx.save();
            ctx.strokeStyle = "#606263";
            ctx.setLineDash([8, 6]);
            ctx.lineWidth = 2;
        
            if (area.rotation && area.rotation !== 0) {
                ctx.translate(area.x + area.w / 2, area.y + area.h / 2);
                ctx.rotate(area.rotation * Math.PI / 180);
                ctx.strokeRect(-area.w / 2, -area.h / 2, area.w, area.h);
            } else {
                ctx.strokeRect(area.x, area.y, area.w, area.h);
            }
        
            ctx.restore();
        }
    
        designCanvas.width = area.w;
        designCanvas.height = area.h;
        designCtx.clearRect(0, 0, designCanvas.width, designCanvas.height);

        if (iapcCanvasLoading) {
            designCtx.save();
            const grad = designCtx.createLinearGradient(0, 0, designCanvas.width, designCanvas.height);
            grad.addColorStop(0, "rgba(255,255,255,0.55)");
            grad.addColorStop(0.5, "rgba(243,244,246,0.72)");
            grad.addColorStop(1, "rgba(255,255,255,0.55)");
            designCtx.fillStyle = grad;
            designCtx.fillRect(0, 0, designCanvas.width, designCanvas.height);
            designCtx.strokeStyle = "rgba(17,24,39,0.10)";
            designCtx.lineWidth = 2;
            designCtx.setLineDash([]);
            const inset = Math.max(6, Math.min(designCanvas.width, designCanvas.height) * 0.04);
            designCtx.strokeRect(inset, inset, designCanvas.width - inset * 2, designCanvas.height - inset * 2);
            designCtx.restore();
        }
    
        const s = estado();
 // Image behind text layers
if (s.imageLayer === "behind") {
    dibujarImagen();
}

// Stickers/graphics behind text
s.layers.forEach(function (layer) {
    if (!layer.visible || layer.layer !== "behind") {
        return;
    }

    if (layer.type === "sticker" || layer.type === "graphic") {
        dibujarSticker(layer);
    }
});

const stickerPreview = getStickerPreviewLayer();

if (stickerPreview && stickerPreview.layer === "behind") {
    dibujarSticker(stickerPreview);
}

// Text layers
s.layers.forEach(function (layer) {
    if (!layer.visible || layer.type !== "text") {
        return;
    }

    dibujarTextoLayer(layer);
});

// Image in front of text layers
if (s.imageLayer === "front") {
    dibujarImagen();
}

// Stickers/graphics in front of text
s.layers.forEach(function (layer) {
    if (!layer.visible || layer.layer !== "front") {
        return;
    }

    if (layer.type === "sticker" || layer.type === "graphic") {
        dibujarSticker(layer);
    }
});

if (stickerPreview && stickerPreview.layer === "front") {
    dibujarSticker(stickerPreview);
}
    
        designCtx.drawImage(
            getPaintCanvasActual(),
            area.x,
            area.y,
            area.w,
            area.h,
            0,
            0,
            designCanvas.width,
            designCanvas.height
        );
    
       if (area.rotation && area.rotation !== 0) {

    ctx.save();

    ctx.beginPath();
    ctx.rect(area.x, area.y, area.w, area.h);
    ctx.clip();

    ctx.translate(
        area.x + area.w / 2,
        area.y + area.h / 2
    );

    ctx.rotate(area.rotation * Math.PI / 180);

    ctx.drawImage(
        designCanvas,
        -area.w / 2,
        -area.h / 2,
        area.w,
        area.h
    );

    ctx.restore();

} else {

    dibujarDisenoCurvado(area);
}
    }

    // --- 3D preview animation loop ---
function dibujarVista3D() {
    if (!iapcVista3DActiva) {
        return;
    }

    dibujar();

    iapcAngulo3D += 0.02;

    const escalaX = Math.abs(Math.cos(iapcAngulo3D));
    const escalaFinal = Math.max(escalaX, 0.08);

    canvas.style.transform = "scaleX(" + escalaFinal + ")";
    canvas.style.transition = "none";
    canvas.style.transformOrigin = "center center";

    iapcAnimacion3D = requestAnimationFrame(dibujarVista3D);
}

    // Saves current view state and switches to another product side/view.
    let sideFlipBusy = false;
    const SIDE_VIEW_ORDER = ["front", "back", "left", "right", "top"];
    const SIDE_FLIP_HALF_MS = 290;
    const SIDE_FLIP_TOTAL_MS = 580;

    function getVistaOrderIndex(viewKey) {
        const idx = SIDE_VIEW_ORDER.indexOf(viewKey);
        return idx === -1 ? 0 : idx;
    }

    function aplicarCambioVista(nuevaVista) {
        guardarCaraActual();

        if (!vistas[nuevaVista]) {
            vistas[nuevaVista] = crearEstadoInicial();
        }

        vistaActual = nuevaVista;

        ensureAdditionalSidesVisible(nuevaVista);

        if (currentSideInput) {
            currentSideInput.value = vistaActual;
        }

        cargarCaraActual();
        cambiarImagenBase();
        actualizarOpcionesVisibles();

        pintando = false;
        ultimoPuntoPintura = null;
    }

    function cambiarVista(nuevaVista) {
        if (!nuevaVista || nuevaVista === vistaActual || sideFlipBusy) {
            return;
        }

        const reduceMotion = window.matchMedia &&
            window.matchMedia("(prefers-reduced-motion: reduce)").matches;

        if (reduceMotion || iapcVista3DActiva || !wrap) {
            aplicarCambioVista(nuevaVista);
            return;
        }

        const forward = getVistaOrderIndex(nuevaVista) >= getVistaOrderIndex(vistaActual);
        const flipClass = forward ? "iapc-side-flip-forward" : "iapc-side-flip-back";

        sideFlipBusy = true;
        wrap.classList.remove("iapc-side-flip-forward", "iapc-side-flip-back");
        void wrap.offsetWidth;
        wrap.classList.add(flipClass);

        window.setTimeout(function () {
            aplicarCambioVista(nuevaVista);
        }, SIDE_FLIP_HALF_MS);

        window.setTimeout(function () {
            wrap.classList.remove("iapc-side-flip-forward", "iapc-side-flip-back");
            sideFlipBusy = false;
        }, SIDE_FLIP_TOTAL_MS);
    }

    document.addEventListener("click", function (e) {
        const btn = e.target.closest(".iapc-side-btn");
    
        if (!btn) {
            return;
        }
    
        const view = btn.dataset.view;
    
        if (!view) {
            return;
        }
    
        cambiarVista(view);
    });

// DEBUG ONLY — exposes view switching on window for external scripts.
window.cambiarVista = cambiarVista;

// Returns the currently active view key (front, back, etc.).
window.getVistaActual = function () {
    return vistaActual;
};

    // Clears the current sticker selection and layer.
    function resetSticker() {
        const s = estado();

        customStickerImage = null;
        s.customSticker = null;
        s.selectedSticker = "";
        s.hasCustomSticker = false;
        s.stickers = [];
        s.layers = s.layers.filter(function (layer) {
            return layer.type !== "sticker" && layer.type !== "graphic";
        });
        s.useSticker = false;
        s.rights = false;
        s.stickerSize = 80;
        s.stickerRotation = 0;
        s.stickerLayer = "behind";
        s.stickerX = 50;
        s.stickerY = 45;

        guardarStickersData();
        cargarCaraActual();
        actualizarPrecioPersonalizado();
        
    }

    // Resets the entire design on all views.
    function resetTodo() {
        vistas[vistaActual] = crearEstadoInicial();
        customStickerImage = null;
        cargarCaraActual();
    }
    // Returns true when any view uses an image from the AI generation history.
    function disenoUsaImagenIA() {
        if (typeof imagenIAActiva !== "undefined" && imagenIAActiva) {
            return true;
        }

        return ["front", "back"].some(function (lado) {
            const s = vistas[lado];

            if (!s || !s.image || !s.image.src || !Array.isArray(historialIA)) {
                return false;
            }

            return historialIA.indexOf(s.image.src) !== -1;
        });
    }

    // Updates the displayed price with customization surcharges.
    function actualizarPrecioPersonalizado() {
        const priceBox = document.querySelector(".iapc-product-price");
        if (!priceBox) return;

        const basePrice = parseFloat(priceBox.dataset.basePrice || "0");
        const noBasePrice = priceBox.dataset.noBasePrice === "1";

        const fees = IAPC_DATA && IAPC_DATA.fees ? IAPC_DATA.fees : {};
        const feeText = parseFloat(fees.text || 0);
        const feeImage = parseFloat(fees.image || 0);
        const feeSticker = parseFloat(fees.sticker || 0);
        const feeGraphic = parseFloat(fees.graphic || 0);
        const feeAi = parseFloat(fees.ai || 0);

        let extra = 0;

        if (getTextoCara("front") || getTextoCara("back")) {
            extra += feeText;
        }

        if (caraTieneImagen("front") || caraTieneImagen("back")) {
            extra += feeImage;
        }

        Object.keys(vistas).forEach(function (key) {
            if (!vistas[key] || !Array.isArray(vistas[key].layers)) {
                return;
            }

            vistas[key].layers.forEach(function (layer) {
                if (layer.type === "sticker") {
                    extra += feeSticker;
                }

                if (layer.type === "graphic") {
                    extra += feeGraphic;
                }
            });
        });

        if (disenoUsaImagenIA()) {
            extra += feeAi;
        }

        if (noBasePrice && extra <= 0) {
            priceBox.style.display = "none";
            return;
        }

        priceBox.style.display = "";

        const total = basePrice + extra;

        priceBox.textContent = total.toFixed(2).replace(".", ",") + " €";
    }
// --- AI generation ---
// Updates the AI remaining-quota label and disables generate when exhausted.
function actualizarTextoLimiteIA(remaining) {
    const max = typeof IAPC_DATA !== "undefined" && IAPC_DATA.aiMax !== undefined
        ? parseInt(IAPC_DATA.aiMax, 10)
        : 15;

    if (aiLimitText) {
        aiLimitText.textContent = t("aiLimitText", max, remaining);
    }

    if (generateAiBtn && remaining <= 0) {
        generateAiBtn.disabled = true;
        generateAiBtn.textContent = t("aiLimitReached");
    }
}
actualizarTextoLimiteIA(
    typeof IAPC_DATA !== "undefined" && IAPC_DATA.aiRemaining !== undefined
        ? parseInt(IAPC_DATA.aiRemaining, 10)
        : 15
);

// Requests an AI image from the server and applies it to the active view.
function cargarImagenIA(prompt) {
    if (!prompt.trim()) {
        iapcNotify(t("enterAiPrompt"), "warning");
        return;
    }

    if (generateAiBtn) {
        generateAiBtn.disabled = true;
    }

    setCanvasLoading(true, t("generatingImage"));
    iapcNotify(t("generatingImage"), "info");

    const formData = new FormData();
    formData.append("action", "iapc_generate_ai_image");
    formData.append("nonce", IAPC_DATA.nonce);
    formData.append("product_id", String(IAPC_DATA.productId || 0));
    formData.append("prompt", prompt.trim());
    formData.append("rules", IAPC_DATA.aiRules || "");

    const promptLimpio = prompt.trim();
    const esRegenerar = ultimoPromptGenerado && promptLimpio === ultimoPromptGenerado;

    fetch(IAPC_DATA.ajax_url, {
        method: "POST",
        body: formData
    })
    .then(function (res) {
        return res.json();
    })
    .then(function (data) {
        if (!data.success) {
            const msg = data.data && data.data.message ? data.data.message : data.data;

            setCanvasLoading(false);
            iapcNotify(msg || t("imageGenerateError"), "error");

            if (data.data && typeof data.data.remaining !== "undefined") {
                IAPC_DATA.aiRemaining = parseInt(data.data.remaining, 10);
                actualizarTextoLimiteIA(IAPC_DATA.aiRemaining);
            }

            return;
        }

        const imageUrl = data.data.image_url;

        if (data.data && typeof data.data.remaining !== "undefined") {
            IAPC_DATA.aiRemaining = parseInt(data.data.remaining, 10);
            actualizarTextoLimiteIA(IAPC_DATA.aiRemaining);
        }

        ultimoPromptIA = data.data.prompt;
        ultimoPromptGenerado = promptLimpio;

        setCanvasLoading(true, t("applyingImage"));

        const img = new Image();
        img.crossOrigin = "anonymous";

        img.onload = function () {
            const s = estado();

            s.image = img;
            s.useImage = true;
            iapcHayImagenCliente = true;

            if (aiImageSize) s.imageSize = parseInt(aiImageSize.value, 10);
            if (aiImageRotation) s.imageRotation = parseInt(aiImageRotation.value, 10);
            if (aiImageLayer) s.imageLayer = aiImageLayer.value;
            if (aiImageX) s.imageX = parseInt(aiImageX.value, 10);
            if (aiImageY) s.imageY = parseInt(aiImageY.value, 10);

            if (Number.isNaN(s.imageSize)) s.imageSize = 100;
            if (Number.isNaN(s.imageRotation)) s.imageRotation = 0;
            if (Number.isNaN(s.imageX)) s.imageX = 50;
            if (Number.isNaN(s.imageY)) s.imageY = 45;

            const hasImage = designerRoot.querySelector('[name="iapc_has_image"]');
            if (hasImage) {
                hasImage.value = "yes";
            }

            if (imageInput) {
                imageInput.value = "";
            }
            actualizarNombreArchivoImagen("");

            actualizarFlagsImagenCara(vistaActual);

            if (useImage) {
                useImage.checked = true;
            }

            imagenIAActiva = img.src;
            indiceIAActiva = typeof indiceIAActiva === "number" && indiceIAActiva >= 0 ? indiceIAActiva : 0;
            syncControlesTransformImagen(s);
            actualizarOpcionesVisibles();
            actualizarBotonClearAI();
            setCanvasLoading(false);
            dibujar();
            actualizarPrecioPersonalizado();
            guardarEnHistorialIA(img, esRegenerar);

            iapcNotify(t("imageGenerated"), "success");

            actualizarBotonIA();
        };

        img.onerror = function () {
            setCanvasLoading(false);
            iapcNotify(t("imageGenerateError"), "error");
        };

        img.src = imageUrl;
    })
    .catch(function () {
        setCanvasLoading(false);
        iapcNotify(t("connectionError"), "error");
    })
    .finally(function () {
        if (generateAiBtn && IAPC_DATA.aiRemaining > 0) {
            generateAiBtn.disabled = false;
            actualizarBotonIA();
        }
    });
}

// Builds the final AI prompt from user input and selected style.
function construirPromptIA(prompt, style) {
    let extra = "";

    if (style === "sticker") {
        extra = "cute sticker style, white outline, centered design, high quality, no text";
    }

    if (style === "logo") {
        extra = "minimal vector logo, clean lines, centered design, high quality, no text";
    }

    if (style === "anime") {
        extra = "anime illustration style, vibrant colors, centered character, high quality, no text";
    }

    if (style === "watercolor") {
        extra = "watercolor illustration, soft colors, artistic design, centered, high quality, no text";
    }

    if (style === "realistic") {
        extra = "realistic high quality illustration, detailed, centered, no text";
    }

    if (style === "minimal") {
        extra = "minimalist flat vector illustration, simple shapes, centered, high quality, no text";
    }

    return prompt + ", " + extra;
}

// --- Side view buttons ---
    const EXTRA_VIEW_KEYS = ["left", "right", "top"];
    const ALL_VIEW_KEYS = ["front", "back", "left", "right", "top"];

    function crearBotonVista(viewKey, viewData) {
        const btn = document.createElement("button");

        btn.type = "button";
        btn.className = "iapc-side-btn";
        btn.dataset.view = viewKey;
        btn.textContent = viewData.label || viewKey;

        if (viewKey === vistaActual) {
            btn.classList.add("iapc-active");
        }

        return btn;
    }

    function expandAdditionalSides() {
        // Collapse UI temporarily disabled — all sides render in one row.
    }

    function ensureAdditionalSidesVisible(viewKey) {
        // no-op while additional-sides collapse is hidden
    }

    // Builds side-switch buttons in one dynamic row (3D / collapse hidden for now).
    function crearBotonesVistas() {
        if (!sidesWrap || typeof IAPC_DATA === "undefined" || !IAPC_DATA.views) {
            return;
        }

        const views = IAPC_DATA.views;

        sidesWrap.innerHTML = "";
        sidesWrap.classList.add("iapc-sides--row");

        ALL_VIEW_KEYS.forEach(function (viewKey) {
            if (!views[viewKey]) {
                return;
            }

            if (EXTRA_VIEW_KEYS.indexOf(viewKey) !== -1 && !views[viewKey].image) {
                return;
            }

            sidesWrap.appendChild(crearBotonVista(viewKey, views[viewKey]));
        });
    }

if (addTextBtn) {
    addTextBtn.addEventListener("click", function () {
        const texto = input ? input.value.trim() : "";

        if (!texto) {
            iapcNotify(t("enterTextFirst"), "warning");
            return;
        }

        const s = estado();

    const nuevaCapaTexto = {
    id: crearIdCapa(),
    type: "text",
    visible: true,

    text: texto,
    font: fontSelect ? fontSelect.value : "Arial",
    color: colorPicker ? colorPicker.value : "#000000",
    size: fontSize ? parseInt(fontSize.value, 10) : 26,
    bold: !!(bold && bold.checked),
    italic: !!(italic && italic.checked),
    align: $("iapc_text_align") ? $("iapc_text_align").value : "center",
    letterSpacing: $("iapc_text_spacing") ? parseInt($("iapc_text_spacing").value, 10) || 0 : 0,
    curve: $("iapc_text_curve") ? parseInt($("iapc_text_curve").value, 10) || 0 : 0,

    x: posX ? parseInt(posX.value, 10) : 50,
    y: posY ? parseInt(posY.value, 10) : 55,

    layer: "front"
};

s.layers.push(nuevaCapaTexto);
s.selectedLayerId = nuevaCapaTexto.id;

     input.value = "";

        guardarStickersData();
        dibujar();
        actualizarPrecioPersonalizado();
    });
}
    if (addStickerBtn) {
        addStickerBtn.addEventListener("click", function () {
            const selectedSticker = stickerSelect ? stickerSelect.value : "";
            const imgSticker = customStickerImage || stickerImages[selectedSticker];

            if (!selectedSticker && !customStickerImage) {
                iapcNotify(t("chooseSticker"), "warning");
                return;
            }

            if (!imgSticker) {
                iapcNotify(t("stickerLoading"), "warning");
                return;
            }

            if (customStickerImage && (!rightsConfirm || !rightsConfirm.checked)) {
                iapcNotify(t("confirmStickerRights"), "warning");
                return;
            }

            const s = estado();


s.layers.push({
    id: crearIdCapa(),
    type: "sticker",
    visible: true,

    img: imgSticker,
    key: selectedSticker,

    x: stickerX ? parseInt(stickerX.value, 10) : 50,
    y: stickerY ? parseInt(stickerY.value, 10) : 45,

    size: stickerSize ? parseInt(stickerSize.value, 10) : 80,
    rotation: stickerRotation ? parseInt(stickerRotation.value, 10) : 0,

    layer: stickerLayer ? stickerLayer.value : "front"
});

            s.selectedLayerId = s.layers[s.layers.length - 1].id;

            customStickerImage = null;
            s.customSticker = null;
            s.selectedSticker = "";
            s.hasCustomSticker = false;

            if (customStickerInput) customStickerInput.value = "";
            if (hasCustomStickerInput) hasCustomStickerInput.value = "no";
            if (stickerSelect) stickerSelect.value = "";

            guardarStickersData();
            dibujar({ skipGuardar: true});
            actualizar3DSiEstaActivo();
            actualizarPrecioPersonalizado();
        });
    }

    // Graphic element add handler
    if (addGraphicBtn) {
    addGraphicBtn.addEventListener("click", function () {
        const selectedGraphic = graphicSelect ? graphicSelect.value : "";
        const imgGraphic = selectedGraphic ? graphicImages[selectedGraphic] : null;

        if (!selectedGraphic) {
            iapcNotify(t("chooseGraphic"), "warning");
            return;
        }

        if (!imgGraphic) {
            iapcNotify(t("graphicLoading"), "warning");
            return;
        }

        const s = estado();

        const nuevaCapaGraphic = {
            id: crearIdCapa(),
            type: "graphic",
            visible: true,
        
            img: imgGraphic,
            key: selectedGraphic,
        
            x: graphicX ? parseInt(graphicX.value, 10) : 50,
            y: graphicY ? parseInt(graphicY.value, 10) : 45,
        
            size: graphicSize ? parseInt(graphicSize.value, 10) : 90,
            rotation: graphicRotation ? parseInt(graphicRotation.value, 10) : 0,
        
            layer: graphicLayer ? graphicLayer.value : "front"
        };
        
        s.layers.push(nuevaCapaGraphic);
        s.selectedLayerId = nuevaCapaGraphic.id;

        if (graphicSelect) {
            graphicSelect.value = "";
        }

        guardarStickersData();
        dibujar();
        actualizar3DSiEstaActivo();
        actualizarPrecioPersonalizado();
    });
    }



    if (useText) {
        useText.addEventListener("change", function () {
            actualizarOpcionesVisibles();
            dibujar();
            actualizar3DSiEstaActivo();
            
        });
    }

    if (useImage) {
        useImage.addEventListener("change", function () {
            actualizarOpcionesVisibles();
            dibujar();
            actualizar3DSiEstaActivo();
        });
    }

    if (useSticker) {
        useSticker.addEventListener("change", function () {
            actualizarOpcionesVisibles();
            dibujar();
        });
    }
    if (customStickerInput) {
        customStickerInput.addEventListener("change", function (e) {
            const file = e.target.files[0];

            if (!file) {
                customStickerImage = null;
                estado().customSticker = null;
                if (hasCustomStickerInput) hasCustomStickerInput.value = "no";
                dibujar();
                return;
            }

            if (!validarArchivoImagen(file, t("theSticker"))) {
                customStickerInput.value = "";
                customStickerImage = null;
                estado().customSticker = null;
                if (hasCustomStickerInput) hasCustomStickerInput.value = "no";
                dibujar();
                return;
            }

            leerImagen(file, function (img) {
                customStickerImage = img;
                estado().customSticker = img;
                estado().hasCustomSticker = true;

                if (stickerSelect) stickerSelect.value = "";
                if (hasCustomStickerInput) hasCustomStickerInput.value = "yes";

                dibujar();
            });
        });
    }

if (imageInput) {
    imageInput.addEventListener("change", function (e) {
        const file = e.target.files[0];

        if (!file) {
            estado().image = null;
            iapcHayImagenCliente = false;
            imagenPendiente = null;
            actualizarNombreArchivoImagen("");
            dibujar();
            return;
        }

        if (!validarArchivoImagen(file, t("theImage"))) {
            imageInput.value = "";
            estado().image = null;
            iapcHayImagenCliente = false;
            imagenPendiente = null;
            actualizarNombreArchivoImagen("");
            dibujar();
            return;
        }

        imagenPendiente = file;
        actualizarNombreArchivoImagen(file.name);

        leerImagen(file, function (img) {
            const s = estado();

            s.image = img;
            s.useImage = true;
            iapcHayImagenCliente = true;

            // Upload replaces any active AI image on this view.
            imagenIAActiva = "";
            indiceIAActiva = -1;
            pintarHistorialIA();

            if (useImage) {
                useImage.checked = true;
            }

            s.imageSize = imageSize ? parseInt(imageSize.value, 10) : 100;
            s.imageRotation = imageRotation ? parseInt(imageRotation.value, 10) : 0;
            s.imageLayer = imageLayer ? imageLayer.value : "behind";
            s.imageX = imageX ? parseInt(imageX.value, 10) : 50;
            s.imageY = imageY ? parseInt(imageY.value, 10) : 45;

            const hasImage = designerRoot.querySelector('[name="iapc_has_image"]');
            if (hasImage) {
                hasImage.value = "yes";
            }

            actualizarFlagsImagenCara(vistaActual);
            syncControlesTransformImagen(s);
            guardarCaraActual();
            dibujar();
            actualizarPrecioPersonalizado();

            imagenPendiente = null;

            iapcNotify(t("imageAdded"), "success");
        });
    });
}

if (addImageBtn) {
    addImageBtn.addEventListener("click", function () {
        if (!imagenPendiente) {
            iapcNotify(t("selectImageFirst"), "warning");
            return;
        }

        leerImagen(imagenPendiente, function (img) {
            const s = estado();

            s.image = img;
            s.useImage = true;
            iapcHayImagenCliente = true;

            if (useImage) {
                useImage.checked = true;
            }

            s.imageSize = imageSize ? parseInt(imageSize.value, 10) : 100;
            s.imageRotation = imageRotation ? parseInt(imageRotation.value, 10) : 0;
            s.imageLayer = imageLayer ? imageLayer.value : "behind";
            s.imageX = imageX ? parseInt(imageX.value, 10) : 50;
            s.imageY = imageY ? parseInt(imageY.value, 10) : 45;

            const hasImage = designerRoot.querySelector('[name="iapc_has_image"]');
            if (hasImage) {
                hasImage.value = "yes";
            }

            actualizarFlagsImagenCara(vistaActual);
            imagenIAActiva = "";
            indiceIAActiva = -1;
            pintarHistorialIA();
            syncControlesTransformImagen(s);
            guardarCaraActual();
            dibujar();
            actualizarPrecioPersonalizado();

            imagenPendiente = null;

            iapcNotify(t("imageAdded"), "success");
        });
    });
}


    if (previewBtn) {
        previewBtn.addEventListener("click", function () {
            dibujar();
            if (previewImg && previewOverlay) {
                previewImg.src = canvas.toDataURL("image/png");
                previewOverlay.style.display = "flex";
            }
        });
    }

    if (closePreview) {
        closePreview.addEventListener("click", function () {
            if (previewOverlay) {
                previewOverlay.style.display = "none";
            }
        });
    }

    if (previewOverlay) {
        previewOverlay.addEventListener("click", function (e) {
            if (e.target === previewOverlay) {
                previewOverlay.style.display = "none";
            }
        });
    }

    if (clearTextBtn) {
    clearTextBtn.addEventListener("click", function () {
        const s = estado();

        s.layers = s.layers.filter(function (layer) {
            return layer.type !== "text";
        });

        s.selectedLayerId = null;

        if (input) input.value = "";

        guardarStickersData();
        actualizarPrecioPersonalizado();
        dibujar();
    });
}

if (clearImageBtn) {
    clearImageBtn.addEventListener("click", function () {
        const s = estado();
        const clearedSrc = s.image && s.image.src ? s.image.src : "";

        s.image = null;
        s.useImage = false;

        if (imagenIAActiva && clearedSrc === imagenIAActiva) {
            imagenIAActiva = "";
        }

        iapcHayImagenCliente = false;
        imagenPendiente = null;

        if (imageInput) {
            imageInput.value = "";
        }

        actualizarNombreArchivoImagen("");

        const hasImage = designerRoot.querySelector('[name="iapc_has_image"]');
        if (hasImage) {
            hasImage.value = "no";
        }

        s.imageSize = 100;
        s.imageRotation = 0;
        s.imageLayer = "behind";
        s.imageX = 50;
        s.imageY = 45;

        cargarCaraActual();
        actualizarPrecioPersonalizado();
    });
}

    if (clearStickerBtn) {
        clearStickerBtn.addEventListener("click", resetSticker);
    }

    if (resetDesignBtn) {
        resetDesignBtn.addEventListener("click", resetTodo);
    }

    // AI generate button handler
    if (generateAiBtn) {
        generateAiBtn.addEventListener("click", function () {
            console.log("CLICK EN IA");
            console.log("PROMPT:", aiPrompt ? aiPrompt.value : "NO EXISTE INPUT");
    
            cargarImagenIA(aiPrompt ? aiPrompt.value : "");
        });
    } else {
        console.error("No se encontró el botón iapc_generate_ai");
    }
    if (aiPrompt) {
    aiPrompt.addEventListener("input", actualizarBotonIA);
}
if (clearAiBtn) {
    clearAiBtn.addEventListener("click", function () {
        const s = estado();

        s.image = null;
        s.useImage = false;

        imagenIAActiva = "";
        ultimoPromptGenerado = "";
        indiceIAActiva = -1;

        if (useImage) {
            useImage.checked = false;
        }

        const hasImage = designerRoot.querySelector('[name="iapc_has_image"]');
        if (hasImage) {
            hasImage.value = "no";
        }

        iapcNotify(t("aiImageRemoved"), "info");

        actualizarBotonIA();
        actualizarOpcionesVisibles();
        actualizarBotonClearAI();
        actualizarPrecioPersonalizado();
        dibujar();
    });
}
// Returns the currently selected text layer, if any.
function getTextoSeleccionado() {
    const s = estado();

    if (!s.selectedLayerId) {
        return null;
    }

    return s.layers.find(function (layer) {
        return layer.id === s.selectedLayerId && layer.type === "text";
    }) || null;
}

// Loads the selected text layer properties into the text panel controls.
function cargarControlesTextoSeleccionado() {
    const layer = getTextoSeleccionado();

    if (!layer) {
        return;
    }

    if (input) input.value = layer.text || "";
    if (fontSelect) fontSelect.value = layer.font || "Arial";
    if (colorPicker) colorPicker.value = layer.color || "#000000";
    if (fontSize) fontSize.value = layer.size || 26;
    if (bold) bold.checked = !!layer.bold;
    if (italic) italic.checked = !!layer.italic;
    if ($("iapc_text_align")) $("iapc_text_align").value = layer.align || "center";
    if ($("iapc_text_spacing")) $("iapc_text_spacing").value = layer.letterSpacing || 0;
    if ($("iapc_text_curve")) $("iapc_text_curve").value = layer.curve || 0;
    if (posX) posX.value = layer.x || 50;
    if (posY) posY.value = layer.y || 55;
}
// Writes text panel control values back to the selected text layer.
function actualizarTextoSeleccionado() {
    const s = estado();

    if (!s.selectedLayerId) {
        return;
    }

    const layer = s.layers.find(function (item) {
        return item.id === s.selectedLayerId && item.type === "text";
    });

    if (!layer) {
        return;
    }

    if (input && input.value.trim() !== "") {
    layer.text = input.value;
}
    if (fontSelect) layer.font = fontSelect.value;
    if (colorPicker) layer.color = colorPicker.value;
    if (fontSize) layer.size = parseInt(fontSize.value, 10) || 26;

    layer.bold = !!(bold && bold.checked);
    layer.italic = !!(italic && italic.checked);

    layer.align = $("iapc_text_align") ? $("iapc_text_align").value : "center";
    layer.letterSpacing = $("iapc_text_spacing") ? parseInt($("iapc_text_spacing").value, 10) || 0 : 0;
    layer.curve = $("iapc_text_curve") ? parseInt($("iapc_text_curve").value, 10) || 0 : 0;

    if (posX) layer.x = parseInt(posX.value, 10) || 50;
    if (posY) layer.y = parseInt(posY.value, 10) || 55;

    guardarStickersData();
    dibujar({ skipGuardar: true });
}

[

    fontSelect,
    colorPicker,
    fontSize,
    bold,
    italic,
    $("iapc_text_align"),
    $("iapc_text_spacing"),
    $("iapc_text_curve"),
    posX,
    posY
].forEach(function (element) {
    if (element) {
        element.addEventListener("input", function () {
            actualizarTextoSeleccionado();

            if (IAPC.viewer3d.shouldUpdateDecal()) {
                IAPC.viewer3d.applyDecal();
            }
        });

        element.addEventListener("change", function () {
            actualizarTextoSeleccionado();

            if (IAPC.viewer3d.shouldUpdateDecal()) {
                IAPC.viewer3d.applyDecal();
            }
        });
    }
});

// --- Selected sticker layer ---
function getStickerSeleccionado() {
    const s = estado();

    if (!s.selectedLayerId) {
        return null;
    }

    return s.layers.find(function (layer) {
        return layer.id === s.selectedLayerId && layer.type === "sticker";
    }) || null;
}

function cargarControlesStickerSeleccionado() {
    const layer = getStickerSeleccionado();

    if (!layer) {
        return;
    }

    setValue(stickerSelect, layer.key || "");
    setRange(stickerSize, layer.size);
    setRange(stickerRotation, layer.rotation);
    setValue(stickerLayer, layer.layer);
    setRange(stickerX, layer.x);
    setRange(stickerY, layer.y);
}

function actualizarStickerSeleccionado() {
    const layer = getStickerSeleccionado();
    const s = estado();

    if (layer) {
        if (stickerSize) {
            layer.size = parseInt(stickerSize.value, 10) || 80;
        }

        if (stickerRotation) {
            layer.rotation = parseInt(stickerRotation.value, 10) || 0;
        }

        if (stickerLayer) {
            layer.layer = stickerLayer.value;
        }

        if (stickerX) {
            layer.x = parseInt(stickerX.value, 10) || 50;
        }

        if (stickerY) {
            layer.y = parseInt(stickerY.value, 10) || 45;
        }

        guardarStickersData();
        dibujar({ skipGuardar: true });
        return;
    }

    if (getStickerActualImg()) {
        if (stickerSize) {
            s.stickerSize = parseInt(stickerSize.value, 10) || 80;
        }

        if (stickerRotation) {
            s.stickerRotation = parseInt(stickerRotation.value, 10) || 0;
        }

        if (stickerLayer) {
            s.stickerLayer = stickerLayer.value;
        }

        if (stickerX) {
            s.stickerX = parseInt(stickerX.value, 10) || 50;
        }

        if (stickerY) {
            s.stickerY = parseInt(stickerY.value, 10) || 45;
        }

        dibujar({ skipGuardar: true });
    }
}

// --- Selected graphic layer ---
function getGraphicSeleccionado() {
    const s = estado();

    if (!s.selectedLayerId) {
        return null;
    }

    return s.layers.find(function (layer) {
        return layer.id === s.selectedLayerId && layer.type === "graphic";
    }) || null;
}

function cargarControlesGraphicSeleccionado() {
    const layer = getGraphicSeleccionado();

    if (!layer) {
        return;
    }

    setValue(graphicSelect, layer.key || "");
    setRange(graphicSize, layer.size);
    setRange(graphicRotation, layer.rotation);
    setValue(graphicLayer, layer.layer);
    setRange(graphicX, layer.x);
    setRange(graphicY, layer.y);
}

// Writes graphic panel control values back to the selected graphic layer.
function actualizarGraphicSeleccionado() {
    const layer = getGraphicSeleccionado();

    if (!layer) {
        return;
    }

    if (graphicSize) {
        layer.size = parseInt(graphicSize.value, 10) || 90;
    }

    if (graphicRotation) {
        layer.rotation = parseInt(graphicRotation.value, 10) || 0;
    }

    if (graphicLayer) {
        layer.layer = graphicLayer.value;
    }

    if (graphicX) {
        layer.x = parseInt(graphicX.value, 10) || 50;
    }

    if (graphicY) {
        layer.y = parseInt(graphicY.value, 10) || 45;
    }

    guardarStickersData();
    dibujar({ skipGuardar: true });
}

[
    stickerSelect,
    rightsConfirm,
    stickerSize,
    stickerRotation,
    stickerLayer,
    stickerX,
    stickerY
].forEach(function (element) {
    if (element) {
        element.addEventListener("input", function () {
            actualizarStickerSeleccionado();

            if (IAPC.viewer3d.shouldUpdateDecal()) {
                IAPC.viewer3d.applyDecal();
            }
        });

        element.addEventListener("change", function () {
            actualizarStickerSeleccionado();

            if (IAPC.viewer3d.shouldUpdateDecal()) {
                IAPC.viewer3d.applyDecal();
            }
        });
    }
});

[
    graphicSelect,
    graphicSize,
    graphicRotation,
    graphicLayer,
    graphicX,
    graphicY
].forEach(function (element) {
    if (element) {
        element.addEventListener("input", function () {
            actualizarGraphicSeleccionado();

            if (IAPC.viewer3d.shouldUpdateDecal()) {
                IAPC.viewer3d.applyDecal();
            }
        });

        element.addEventListener("change", function () {
            actualizarGraphicSeleccionado();

            if (IAPC.viewer3d.shouldUpdateDecal()) {
                IAPC.viewer3d.applyDecal();
            }
        });
    }
});

function onImageTransformControlInput(fromAiPanel) {
    const esIA = imagenActualEsIA();

    // Each panel only edits its own image source.
    if (fromAiPanel !== esIA) {
        syncControlesTransformImagen(estado());
        return;
    }

    dibujar();
    syncControlesTransformImagen(estado());

    if (IAPC.viewer3d.shouldUpdateDecal()) {
        IAPC.viewer3d.applyDecal();
    }
}

[
    imageSize,
    imageRotation,
    imageLayer,
    imageX,
    imageY
].forEach(function (element) {
    if (!element) return;

    element.addEventListener("input", function () {
        onImageTransformControlInput(false);
    });
    element.addEventListener("change", function () {
        onImageTransformControlInput(false);
    });
});

[
    aiImageSize,
    aiImageRotation,
    aiImageLayer,
    aiImageX,
    aiImageY
].forEach(function (element) {
    if (!element) return;

    element.addEventListener("input", function () {
        onImageTransformControlInput(true);
    });
    element.addEventListener("change", function () {
        onImageTransformControlInput(true);
    });
});

    // --- Canvas hit-testing & drag ---
    // Converts a pointer/touch event to main canvas pixel coordinates.
    function puntoCanvas(e) {
        const rect = canvas.getBoundingClientRect();
        const touch = e.touches && e.touches.length ? e.touches[0] : e;
        return {
            x: (touch.clientX - rect.left) * (canvas.width / rect.width),
            y: (touch.clientY - rect.top) * (canvas.height / rect.height)
        };
    }

    // Converts canvas pixels to percentage coordinates within the printable area.
    function puntoAPorcentajeDesign(p) {
        const area = getZonaImprimible();
        return {
            x: clamp(((p.x - area.x) / area.w) * 100, 0, 100),
            y: clamp(((p.y - area.y) / area.h) * 100, 0, 100)
        };
    }

    // Returns the pixel distance from point p to a layer position (percent-based).
    function distancia(p, xPct, yPct) {
        const area = getZonaImprimible();
        const x = area.x + area.w * (xPct / 100);
        const y = area.y + area.h * (yPct / 100);
        return Math.hypot(p.x - x, p.y - y);
    }

    // Hit-tests layers and legacy elements at canvas point p (nearest match wins).
    function detectarElemento(p) {
        const s = estado();
        let mejor = null;

        if (s.useText && distancia(p, s.textX, s.textY) < 55) {
            mejor = { type: "text" };
        }

        if (s.useImage && s.image && distancia(p, s.imageX, s.imageY) < Math.max(45, s.imageSize * 0.45)) {
            mejor = { type: "image" };
        }

        s.layers.forEach(function (layer, index) {
    const size = layer.type === "text"
        ? parseInt(layer.size || 40, 10)
        : parseInt(layer.size || 80, 10);

    if (distancia(p, layer.x, layer.y) < Math.max(35, size * 0.75)) {
        mejor = { type: "savedLayer", index: index };
    }
});

        if (getStickerActualImg() && distancia(p, s.stickerX, s.stickerY) < Math.max(35, s.stickerSize * 0.45)) {
            mejor = { type: "currentSticker" };
        }

        return mejor;
    }

    // Canvas drag-and-drop — moves the hit-tested element to a new position.
    function moverElemento(target, pct) {
        const s = estado();

        if (target.type === "text") {
            s.textX = Math.round(pct.x);
            s.textY = Math.round(pct.y);
            setRange(posX, s.textX);
            setRange(posY, s.textY);
        }

        if (target.type === "image") {
            s.imageX = Math.round(pct.x);
            s.imageY = Math.round(pct.y);
            syncControlesTransformImagen(s);
        }

        if (target.type === "currentSticker") {
            s.stickerX = Math.round(pct.x);
            s.stickerY = Math.round(pct.y);
            setRange(stickerX, s.stickerX);
            setRange(stickerY, s.stickerY);
        }

    if (target.type === "savedLayer" && s.layers[target.index]) {
        s.selectedLayerId = s.layers[target.index].id;

        if (s.layers[target.index].type === "text") {
            cargarControlesTextoSeleccionado();
        } else if (s.layers[target.index].type === "sticker") {
            cargarControlesStickerSeleccionado();
        } else if (s.layers[target.index].type === "graphic") {
            cargarControlesGraphicSeleccionado();
        }

        s.layers[target.index].x = Math.round(pct.x);
        s.layers[target.index].y = Math.round(pct.y);

        if (s.layers[target.index].type === "sticker") {
            setRange(stickerX, s.layers[target.index].x);
            setRange(stickerY, s.layers[target.index].y);
        } else if (s.layers[target.index].type === "graphic") {
            setRange(graphicX, s.layers[target.index].x);
            setRange(graphicY, s.layers[target.index].y);
        }
    }

        dibujar();
        actualizar3DSiEstaActivo();
    }

    canvas.addEventListener("pointerdown", function (e) {
    if (!vistaTieneImagenProducto(vistaActual)) return;
    if (modoPintar) return;
        if (isMobileCanvasMode() || isCanvasFullscreen()) {
            e.preventDefault();
        }
        const p = puntoCanvas(e);
        dragTarget = detectarElemento(p);

        if (dragTarget && dragTarget.type === "savedLayer") {
            const s = estado();
            const layer = s.layers[dragTarget.index];

            if (layer) {
                s.selectedLayerId = layer.id;

                if (layer.type === "text") {
                    cargarControlesTextoSeleccionado();
                } else if (layer.type === "sticker") {
                    cargarControlesStickerSeleccionado();
                } else if (layer.type === "graphic") {
                    cargarControlesGraphicSeleccionado();
                }
            }
        }

        if (dragTarget) {
            canvas.setPointerCapture(e.pointerId);
            canvas.style.cursor = "grabbing";
            e.preventDefault();
        }
    }, { passive: false });

    canvas.addEventListener("pointermove", function (e) {
    if (modoPintar) return;
        if (!dragTarget) {
            const hover = detectarElemento(puntoCanvas(e));
            canvas.style.cursor = hover ? "grab" : "default";
            return;
        }

        e.preventDefault();
        moverElemento(dragTarget, puntoAPorcentajeDesign(puntoCanvas(e)));
    }, { passive: false });

    canvas.addEventListener("pointerup", function (e) {
        if (dragTarget) {
            canvas.releasePointerCapture(e.pointerId);
        }

        dragTarget = null;
        canvas.style.cursor = "default";
    });

    canvas.addEventListener("pointercancel", function () {
        dragTarget = null;
        canvas.style.cursor = "default";
    });
   // Exports design data (PNGs, text, layers) into the cart form before submit.
   function sincronizarCamposCarrito() {
    const vistaOriginal = vistaActual;

    guardarCaraActual();

    const pngFront = exportarPngCara("front", true);
    const pngBack = exportarPngCara("back", true);
    const textFront = getTextoCara("front");
    const textBack = getTextoCara("back");
    const hasImageFront = caraTieneImagen("front");
    const hasImageBack = caraTieneImagen("back");
    const allStickers = getStickersTodasLasCaras();
    const pngClienteFront = getImagenClientePng("front");
    const pngClienteBack = getImagenClientePng("back");

    vistaActual = vistaOriginal;
    mugImageOverride = null;

    const srcOriginal = getImagenBaseParaVista(vistaOriginal);
    if (srcOriginal && mugImage.src !== srcOriginal) {
        mugImage.src = srcOriginal;
    }

    cargarCaraActual();

    setCampoCarrito("iapc_design_image_front", pngFront);
    setCampoCarrito("iapc_design_image_back", pngBack);
    setCampoCarrito("iapc_design_image", pngFront || pngBack);

    if (designInput) {
        designInput.value = pngFront || pngBack;
    }

    setCampoCarrito("iapc_text_front", textFront);
    setCampoCarrito("iapc_text_back", textBack);
    setCampoCarrito("iapc_text", textFront || textBack);

    setCampoCarrito("iapc_has_image_front", hasImageFront ? "yes" : "no");
    setCampoCarrito("iapc_has_image_back", hasImageBack ? "yes" : "no");
    setCampoCarrito("iapc_has_image", (hasImageFront || hasImageBack) ? "yes" : "no");

    const hasImage = designerRoot.querySelector('[name="iapc_has_image"]');
    if (hasImage) {
        hasImage.value = (hasImageFront || hasImageBack) ? "yes" : "no";
    }

    const hasImageFrontInput = designerRoot.querySelector('[name="iapc_has_image_front"]');
    if (hasImageFrontInput) {
        hasImageFrontInput.value = hasImageFront ? "yes" : "no";
    }

    const hasImageBackInput = designerRoot.querySelector('[name="iapc_has_image_back"]');
    if (hasImageBackInput) {
        hasImageBackInput.value = hasImageBack ? "yes" : "no";
    }

    setCampoCarrito("iapc_customer_image_front", pngClienteFront);
    setCampoCarrito("iapc_customer_image_back", pngClienteBack);
    setCampoCarrito("iapc_customer_image_data", pngClienteFront || pngClienteBack);

    const hiddenCliente = designerRoot.querySelector('[name="iapc_customer_image_data"]');
    if (hiddenCliente) {
        hiddenCliente.value = pngClienteFront || pngClienteBack;
    }

    guardarStickersData();
    setCampoCarrito("iapc_stickers_data", JSON.stringify(allStickers));

    if (stickersDataInput) {
        stickersDataInput.value = JSON.stringify(allStickers);
    }

const assetsData = allStickers.map(function (layer) {
    return {
        type: layer.type,
        key: layer.key || "",
        src: layer.src || "",
        x: layer.x,
        y: layer.y,
        size: layer.size,
        rotation: layer.rotation,
        layer: layer.layer,
        side: layer.side || ""
    };
});

setCampoCarrito("iapc_used_assets_data", JSON.stringify(assetsData));

    setCampoCarrito("iapc_current_side", vistaOriginal);

    setCampoCarrito("iapc_use_text", (textFront || textBack) ? "yes" : "");
    setCampoCarrito("iapc_use_sticker", allStickers.length > 0 ? "yes" : "");

    const hasCustomStickerLayer = designHasCustomStickerLayer(allStickers);

    if (rightsConfirm && rightsConfirm.checked) {
        setCampoCarrito("iapc_rights_confirm", "yes");
    } else {
        setCampoCarrito("iapc_rights_confirm", "");
    }

    if (hasCustomStickerInput) {
        hasCustomStickerInput.value = hasCustomStickerLayer ? "yes" : "no";
    }

    setCampoCarrito("iapc_has_custom_sticker", hasCustomStickerLayer ? "yes" : "no");

    setCampoCarrito("iapc_used_ai", disenoUsaImagenIA() ? "yes" : "no");
}

    IAPC.cartBridge.bindEvents(sincronizarCamposCarrito);

    const cartForm = getDesignerCartForm();

    if (cartForm) {
        cartForm.addEventListener("submit", function (e) {
            if (!designHasCustomStickerLayer()) {
                return;
            }

            if (rightsConfirm && rightsConfirm.checked) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();
            iapcNotify(t("confirmStickerRights"), "warning");
        }, true);
    }

    vistaActual = obtenerPrimeraVistaDisponible();
    crearBotonesVistas();
    cargarCaraActual();
    cambiarImagenBase();
    actualizarEstadoCustomizacion();
    IAPC.tabs.init();
    initCanvasFullscreenUi();
    });