import { createEmptyAseDocument, hexToAseColor, writeAseFile } from './asepriteFormat.js';
import { downloadBlob, celKey } from './pixelEditorTypes.js';

/**
 * pixelEditorExports — pixel editor file export utilities as pure functions.
 */
const log = { error: console.error, warn: console.warn };
async function exportAse(ctx) {
    const { dw, dh, layers, fc, fDur, pal, cels } = ctx;
    const doc = createEmptyAseDocument(dw, dh);
    doc.layers = layers.map((l) => ({
        name: l.name,
        visible: l.visible,
        opacity: l.opacity,
        blendMode: 0,
        childLevel: 0,
        layerType: 0,
    }));
    doc.palette = pal.map(hexToAseColor);
    doc.frames = [];
    for (let fi = 0; fi < fc; fi++) {
        const frameCels = [];
        for (let li = 0; li < layers.length; li++) {
            const cel = cels.get(celKey(fi, li));
            if (!cel)
                continue;
            // tight bounding box
            let x1 = dw, y1 = dh, x2 = -1, y2 = -1;
            for (let y = 0; y < dh; y++)
                for (let x = 0; x < dw; x++) {
                    if (cel.data[(y * dw + x) * 4 + 3] > 0) {
                        x1 = Math.min(x1, x);
                        y1 = Math.min(y1, y);
                        x2 = Math.max(x2, x);
                        y2 = Math.max(y2, y);
                    }
                }
            if (x2 < 0)
                continue;
            const cw2 = x2 - x1 + 1, ch2 = y2 - y1 + 1;
            const px2 = new Uint8Array(cw2 * ch2 * 4);
            for (let y = 0; y < ch2; y++)
                for (let x = 0; x < cw2; x++) {
                    const si = ((y1 + y) * dw + (x1 + x)) * 4;
                    const di = (y * cw2 + x) * 4;
                    px2[di] = cel.data[si];
                    px2[di + 1] = cel.data[si + 1];
                    px2[di + 2] = cel.data[si + 2];
                    px2[di + 3] = cel.data[si + 3];
                }
            frameCels.push({
                layerIndex: li,
                x: x1,
                y: y1,
                opacity: 255,
                width: cw2,
                height: ch2,
                pixels: px2,
            });
        }
        doc.frames.push({ duration: fDur[fi] || 100, cels: frameCels });
    }
    try {
        const buf = await writeAseFile(doc);
        downloadBlob(new Blob([buf]), "sprite.ase");
    }
    catch (err) {
        log.error("Export ASE failed", err);
        alert("Export failed.");
    }
}
function exportPNG(afi, dw, dh, compFrame) {
    const data = compFrame(afi);
    const cv = document.createElement("canvas");
    cv.width = dw;
    cv.height = dh;
    cv.getContext("2d").putImageData(data, 0, 0);
    cv.toBlob((b) => b && downloadBlob(b, "sprite.png"));
}
function exportJPG(afi, dw, dh, compFrame) {
    const data = compFrame(afi);
    const cv = document.createElement("canvas");
    cv.width = dw;
    cv.height = dh;
    const ctx = cv.getContext("2d");
    // JPG doesn't support transparency — fill white background first
    ctx.fillStyle = "#ffffff";
    ctx.fillRect(0, 0, dw, dh);
    // Use drawImage instead of putImageData to properly composite over white
    const tmp = document.createElement("canvas");
    tmp.width = dw;
    tmp.height = dh;
    tmp.getContext("2d").putImageData(data, 0, 0);
    ctx.drawImage(tmp, 0, 0);
    cv.toBlob((b) => b && downloadBlob(b, "sprite.jpg"), "image/jpeg", 0.92);
}
function exportWebP(afi, dw, dh, compFrame) {
    const data = compFrame(afi);
    const cv = document.createElement("canvas");
    cv.width = dw;
    cv.height = dh;
    cv.getContext("2d").putImageData(data, 0, 0);
    cv.toBlob((b) => b && downloadBlob(b, "sprite.webp"), "image/webp", 0.92);
}
function exportBMP(afi, dw, dh, compFrame) {
    const data = compFrame(afi);
    // Build BMP file manually (24-bit uncompressed)
    const w = dw, h = dh;
    const rowSize = Math.ceil((w * 3) / 4) * 4;
    const pixelDataSize = rowSize * h;
    const fileSize = 54 + pixelDataSize;
    const buf = new ArrayBuffer(fileSize);
    const view = new DataView(buf);
    // BMP Header
    view.setUint8(0, 0x42);
    view.setUint8(1, 0x4d); // 'BM'
    view.setUint32(2, fileSize, true);
    view.setUint32(10, 54, true); // pixel data offset
    // DIB Header (BITMAPINFOHEADER)
    view.setUint32(14, 40, true); // header size
    view.setInt32(18, w, true);
    view.setInt32(22, h, true);
    view.setUint16(26, 1, true); // planes
    view.setUint16(28, 24, true); // bpp
    view.setUint32(34, pixelDataSize, true);
    // Pixel data (bottom-up, BGR)
    const d = data.data;
    for (let y = 0; y < h; y++) {
        const srcRow = (h - 1 - y) * w * 4;
        const dstRow = 54 + y * rowSize;
        for (let x = 0; x < w; x++) {
            const si = srcRow + x * 4;
            const di = dstRow + x * 3;
            view.setUint8(di, d[si + 2]); // B
            view.setUint8(di + 1, d[si + 1]); // G
            view.setUint8(di + 2, d[si]); // R
        }
    }
    downloadBlob(new Blob([buf], { type: "image/bmp" }), "sprite.bmp");
}
function exportSVG(afi, dw, dh, compFrame) {
    // Trace pixel art to SVG by emitting 1 rect per non-transparent pixel
    const data = compFrame(afi);
    const d = data.data;
    const rects = [];
    for (let y = 0; y < dh; y++) {
        for (let x = 0; x < dw; x++) {
            const i = (y * dw + x) * 4;
            const a = d[i + 3];
            if (a === 0)
                continue;
            const r = d[i], g = d[i + 1], b = d[i + 2];
            const hex = `#${((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1)}`;
            if (a < 255) {
                rects.push(`<rect x="${x}" y="${y}" width="1" height="1" fill="${hex}" opacity="${(a / 255).toFixed(2)}"/>`);
            }
            else {
                rects.push(`<rect x="${x}" y="${y}" width="1" height="1" fill="${hex}"/>`);
            }
        }
    }
    const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${dw}" height="${dh}" viewBox="0 0 ${dw} ${dh}" shape-rendering="crispEdges">\n${rects.join("\n")}\n</svg>`;
    downloadBlob(new Blob([svg], { type: "image/svg+xml" }), "sprite.svg");
}
function exportSheet(dw, dh, fc, compFrame) {
    const cv = document.createElement("canvas");
    cv.width = dw * fc;
    cv.height = dh;
    const ctx = cv.getContext("2d");
    for (let fi = 0; fi < fc; fi++) {
        const fd = compFrame(fi);
        const tmp = new OffscreenCanvas(dw, dh);
        tmp.getContext("2d").putImageData(fd, 0, 0);
        ctx.drawImage(tmp, fi * dw, 0);
    }
    cv.toBlob((b) => b && downloadBlob(b, "spritesheet.png"));
}
function exportSpriteSheetAdvanced(dw, dh, fc, compFrame, fDur, options = {}) {
    const { columns = Math.ceil(Math.sqrt(fc)), padding = 0, includeMeta = false, fileName = 'spritesheet', } = options;
    const rows = Math.ceil(fc / columns);
    const frameW = dw + padding * 2;
    const frameH = dh + padding * 2;
    const cv = document.createElement("canvas");
    cv.width = columns * frameW;
    cv.height = rows * frameH;
    const ctx = cv.getContext("2d");
    // Transparent background
    ctx.clearRect(0, 0, cv.width, cv.height);
    const frames = [];
    for (let fi = 0; fi < fc; fi++) {
        const col = fi % columns;
        const row = Math.floor(fi / columns);
        const x = col * frameW + padding;
        const y = row * frameH + padding;
        const fd = compFrame(fi);
        const tmp = new OffscreenCanvas(dw, dh);
        tmp.getContext("2d").putImageData(fd, 0, 0);
        ctx.drawImage(tmp, x, y);
        frames.push({
            filename: `frame_${fi.toString().padStart(3, '0')}`,
            frame: { x, y, w: dw, h: dh },
            duration: fDur[fi] || 100,
        });
    }
    cv.toBlob((b) => b && downloadBlob(b, `${fileName}.png`));
    if (includeMeta) {
        const meta = {
            frames,
            meta: {
                app: 'NiceToDev PixelEditor',
                version: '1.0',
                image: `${fileName}.png`,
                format: 'RGBA8888',
                size: { w: cv.width, h: cv.height },
                frameTags: [],
            },
        };
        downloadBlob(new Blob([JSON.stringify(meta, null, 2)], { type: 'application/json' }), `${fileName}.json`);
    }
}
/* ────────────────────────────────────────────────────────────────
   GIF EXPORT
──────────────────────────────────────────────────────────────── */
// Simple GIF encoder (LZW-based)
async function exportGIF(dw, dh, fc, compFrame, fDur, options = {}) {
    const { loop = true, quality = 10 } = options;
    // Build GIF using simple encoding
    const frames = [];
    for (let fi = 0; fi < fc; fi++) {
        frames.push({
            data: compFrame(fi),
            delay: Math.round((fDur[fi] || 100) / 10), // GIF uses centiseconds
        });
    }
    try {
        const gifData = encodeGIF(frames, dw, dh, loop, quality);
        downloadBlob(new Blob([gifData.buffer], { type: 'image/gif' }), 'animation.gif');
    }
    catch (err) {
        log.error('GIF export failed', err);
        alert('GIF export failed. Try reducing colors or frame count.');
    }
}
// Simple GIF encoder implementation
function encodeGIF(frames, width, height, loop, quality) {
    const buffer = [];
    // Helper to write bytes
    const writeByte = (b) => buffer.push(b & 0xff);
    const writeShort = (s) => {
        writeByte(s);
        writeByte(s >> 8);
    };
    const writeString = (str) => {
        for (let i = 0; i < str.length; i++) {
            writeByte(str.charCodeAt(i));
        }
    };
    // Build global color table from first frame
    const firstFrame = frames[0].data;
    const colors = extractColors(firstFrame, 256);
    const colorTableBits = 8;
    // Header
    writeString('GIF89a');
    // Logical Screen Descriptor
    writeShort(width);
    writeShort(height);
    writeByte(0xf0 | (colorTableBits - 1)); // Global color table flag + color resolution
    writeByte(0); // Background color index
    writeByte(0); // Pixel aspect ratio
    // Global Color Table
    for (let i = 0; i < 256; i++) {
        if (i < colors.length) {
            writeByte(colors[i][0]); // R
            writeByte(colors[i][1]); // G
            writeByte(colors[i][2]); // B
        }
        else {
            writeByte(0);
            writeByte(0);
            writeByte(0);
        }
    }
    // NETSCAPE extension for looping
    if (loop) {
        writeByte(0x21); // Extension introducer
        writeByte(0xff); // Application extension
        writeByte(11); // Block size
        writeString('NETSCAPE2.0');
        writeByte(3); // Sub-block size
        writeByte(1); // Loop sub-block ID
        writeShort(0); // Loop count (0 = infinite)
        writeByte(0); // Block terminator
    }
    // Write each frame
    for (const frame of frames) {
        // Graphic Control Extension
        writeByte(0x21); // Extension
        writeByte(0xf9); // Graphic Control
        writeByte(4); // Block size
        writeByte(0x04); // Dispose: do not dispose, no transparency
        writeShort(frame.delay); // Delay
        writeByte(0); // Transparent color index
        writeByte(0); // Block terminator
        // Image Descriptor
        writeByte(0x2c); // Image separator
        writeShort(0); // Left
        writeShort(0); // Top
        writeShort(width);
        writeShort(height);
        writeByte(0); // No local color table
        // Image Data
        const indexedPixels = quantizeFrame(frame.data, colors);
        const lzwMinCodeSize = colorTableBits;
        writeByte(lzwMinCodeSize);
        // LZW encode
        const lzwData = lzwEncode(indexedPixels, lzwMinCodeSize);
        // Write in sub-blocks
        let offset = 0;
        while (offset < lzwData.length) {
            const chunkSize = Math.min(255, lzwData.length - offset);
            writeByte(chunkSize);
            for (let i = 0; i < chunkSize; i++) {
                writeByte(lzwData[offset + i]);
            }
            offset += chunkSize;
        }
        writeByte(0); // Block terminator
    }
    // Trailer
    writeByte(0x3b);
    return new Uint8Array(buffer);
}
// Extract most common colors from image
function extractColors(img, maxColors) {
    const colorCounts = new Map();
    const data = img.data;
    for (let i = 0; i < data.length; i += 4) {
        if (data[i + 3] < 128)
            continue; // Skip transparent
        // Quantize colors to reduce variations
        const r = data[i] & 0xf8;
        const g = data[i + 1] & 0xf8;
        const b = data[i + 2] & 0xf8;
        const key = `${r},${g},${b}`;
        const existing = colorCounts.get(key);
        if (existing) {
            existing.count++;
        }
        else {
            colorCounts.set(key, { count: 1, r, g, b });
        }
    }
    // Sort by frequency and take top colors
    const sorted = Array.from(colorCounts.values())
        .sort((a, b) => b.count - a.count)
        .slice(0, maxColors);
    return sorted.map(c => [c.r, c.g, c.b]);
}
// Map pixels to palette indices
function quantizeFrame(img, palette) {
    const result = new Uint8Array(img.width * img.height);
    const data = img.data;
    for (let i = 0, pi = 0; i < data.length; i += 4, pi++) {
        if (data[i + 3] < 128) {
            result[pi] = 0; // Map transparent to index 0
            continue;
        }
        const r = data[i];
        const g = data[i + 1];
        const b = data[i + 2];
        // Find closest color in palette
        let minDist = Infinity;
        let minIdx = 0;
        for (let j = 0; j < palette.length; j++) {
            const pr = palette[j][0];
            const pg = palette[j][1];
            const pb = palette[j][2];
            const dist = (r - pr) ** 2 + (g - pg) ** 2 + (b - pb) ** 2;
            if (dist < minDist) {
                minDist = dist;
                minIdx = j;
            }
        }
        result[pi] = minIdx;
    }
    return result;
}
// LZW compression for GIF
function lzwEncode(data, minCodeSize) {
    const clearCode = 1 << minCodeSize;
    const eoiCode = clearCode + 1;
    let codeSize = minCodeSize + 1;
    let nextCode = eoiCode + 1;
    let maxCode = 1 << codeSize;
    const dictionary = new Map();
    // Initialize dictionary with single-character codes
    for (let i = 0; i < clearCode; i++) {
        dictionary.set(String.fromCharCode(i), i);
    }
    const output = [];
    let bits = 0;
    let bitCount = 0;
    const writeBits = (code, size) => {
        bits |= code << bitCount;
        bitCount += size;
        while (bitCount >= 8) {
            output.push(bits & 0xff);
            bits >>= 8;
            bitCount -= 8;
        }
    };
    // Write clear code
    writeBits(clearCode, codeSize);
    if (data.length === 0) {
        writeBits(eoiCode, codeSize);
        if (bitCount > 0)
            output.push(bits);
        return new Uint8Array(output);
    }
    let prefix = String.fromCharCode(data[0]);
    for (let i = 1; i < data.length; i++) {
        const k = String.fromCharCode(data[i]);
        const wk = prefix + k;
        if (dictionary.has(wk)) {
            prefix = wk;
        }
        else {
            writeBits(dictionary.get(prefix), codeSize);
            if (nextCode < 4096) {
                dictionary.set(wk, nextCode++);
                if (nextCode > maxCode && codeSize < 12) {
                    codeSize++;
                    maxCode = 1 << codeSize;
                }
            }
            else {
                // Reset dictionary
                writeBits(clearCode, codeSize);
                dictionary.clear();
                for (let j = 0; j < clearCode; j++) {
                    dictionary.set(String.fromCharCode(j), j);
                }
                codeSize = minCodeSize + 1;
                nextCode = eoiCode + 1;
                maxCode = 1 << codeSize;
            }
            prefix = k;
        }
    }
    // Write final code
    writeBits(dictionary.get(prefix), codeSize);
    writeBits(eoiCode, codeSize);
    // Flush remaining bits
    if (bitCount > 0) {
        output.push(bits);
    }
    return new Uint8Array(output);
}

export { exportAse, exportBMP, exportGIF, exportJPG, exportPNG, exportSVG, exportSheet, exportSpriteSheetAdvanced, exportWebP };
//# sourceMappingURL=pixelEditorExports.js.map
