/**
 * Browser entry point for @formepdf/core.
 *
 * Import as `@formepdf/core/browser` — no Node APIs, works in any
 * modern browser with WebAssembly support.
 *
 * WASM is loaded via fetch() from the same origin as the importing
 * module (using import.meta.url resolution in the wasm-pack glue).
 * Bundlers like Vite, esbuild, and webpack handle this automatically.
 * For script-tag usage, call `initWasm()` with an explicit URL first.
 */
import initWasm, { render_pdf as wasmRenderPdf, render_pdf_with_layout as wasmRenderPdfWithLayout, render_template_pdf as wasmRenderTemplatePdf, render_template_pdf_with_layout as wasmRenderTemplatePdfWithLayout, } from '../pkg/forme.js';
// ── WASM initialization ────────────────────────────────────────────
let initialized = false;
/**
 * Initialize the WASM module. Called automatically on first render,
 * but you can call it early to control timing or provide a custom
 * WASM URL/bytes.
 *
 * @example
 * // Auto-resolve (works with bundlers)
 * await initWasm();
 *
 * // Explicit URL (CDN, custom path, etc.)
 * await initWasm('/wasm/forme_bg.wasm');
 * await initWasm(new URL('./forme_bg.wasm', import.meta.url));
 */
export async function init(module) {
    if (initialized)
        return;
    if (module !== undefined) {
        await initWasm({ module_or_path: module });
    }
    else {
        await initWasm();
    }
    initialized = true;
}
// ── Helpers ────────────────────────────────────────────────────────
function uint8ArrayToBase64(bytes) {
    // Browser-native: binary string → btoa
    let binary = '';
    for (let i = 0; i < bytes.length; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return btoa(binary);
}
async function resolveFonts(doc) {
    const fonts = doc.fonts;
    if (!fonts?.length)
        return;
    for (const font of fonts) {
        if (font.src instanceof Uint8Array) {
            font.src = uint8ArrayToBase64(font.src);
        }
        else if (typeof font.src === 'string' && !font.src.startsWith('data:')) {
            // In the browser, non-data-URI strings are treated as URLs
            const res = await fetch(font.src);
            if (!res.ok)
                throw new Error(`Failed to fetch font: ${font.src} (${res.status})`);
            const buf = new Uint8Array(await res.arrayBuffer());
            font.src = uint8ArrayToBase64(buf);
        }
        // data URIs pass through as-is (engine extracts base64 portion)
    }
}
async function resolveImages(doc) {
    const children = doc.children;
    if (!children?.length)
        return;
    for (const child of children) {
        await resolveImagesInNode(child);
    }
}
async function resolveImagesInNode(node) {
    const kind = node.kind;
    if (kind?.type === 'Image' && typeof kind.src === 'string') {
        const src = kind.src;
        if (src.startsWith('http://') || src.startsWith('https://')) {
            const res = await fetch(src);
            if (!res.ok)
                throw new Error(`Failed to fetch image: ${src} (${res.status})`);
            const contentType = res.headers.get('content-type') || 'image/png';
            const buf = new Uint8Array(await res.arrayBuffer());
            kind.src = `data:${contentType};base64,${uint8ArrayToBase64(buf)}`;
        }
    }
    const children = node.children;
    if (children?.length) {
        for (const child of children) {
            await resolveImagesInNode(child);
        }
    }
}
// ── Render functions ───────────────────────────────────────────────
async function ensureInit() {
    if (!initialized)
        await init();
}
export async function renderPdf(json) {
    await ensureInit();
    return wasmRenderPdf(json);
}
export async function renderPdfWithLayout(json) {
    await ensureInit();
    const result = wasmRenderPdfWithLayout(json);
    return result;
}
export async function renderDocument(element, options) {
    const { serialize } = await import('@formepdf/react');
    const doc = serialize(element);
    if (options?.embedData !== undefined) {
        doc.embeddedData = JSON.stringify(options.embedData);
    }
    await Promise.all([resolveFonts(doc), resolveImages(doc)]);
    return renderPdf(JSON.stringify(doc));
}
export async function renderDocumentWithLayout(element, options) {
    const { serialize } = await import('@formepdf/react');
    const doc = serialize(element);
    if (options?.embedData !== undefined) {
        doc.embeddedData = JSON.stringify(options.embedData);
    }
    await Promise.all([resolveFonts(doc), resolveImages(doc)]);
    return renderPdfWithLayout(JSON.stringify(doc));
}
// ── Template rendering ──────────────────────────────────────────────
export async function renderTemplate(templateJson, dataJson) {
    await ensureInit();
    return wasmRenderTemplatePdf(templateJson, dataJson);
}
export async function renderTemplateWithLayout(templateJson, dataJson) {
    await ensureInit();
    const result = wasmRenderTemplatePdfWithLayout(templateJson, dataJson);
    return result;
}
// ── Data extraction (browser-native decompression) ──────────────────
/**
 * Extract embedded JSON data from a Forme-generated PDF.
 * Uses the browser-native DecompressionStream API (no node:zlib).
 */
export async function extractData(pdfBytes) {
    const text = new TextDecoder('latin1').decode(pdfBytes);
    // Find the FileSpec referencing forme-data.json
    const fsMatch = text.match(/\/F\s*\(forme-data\.json\)/);
    if (!fsMatch)
        return null;
    // Extract the EmbeddedFile stream object number from /EF << /F N 0 R >>
    const fsStart = fsMatch.index;
    const fsRegion = text.slice(Math.max(0, fsStart - 200), fsStart + 200);
    const efMatch = fsRegion.match(/\/EF\s*<<\s*\/F\s+(\d+)\s+0\s+R\s*>>/);
    if (!efMatch)
        return null;
    const streamObjId = efMatch[1];
    // Find the stream object
    const objPattern = new RegExp(streamObjId + '\\s+0\\s+obj\\b');
    const objMatch = text.match(objPattern);
    if (!objMatch)
        return null;
    const objStart = objMatch.index;
    // Find stream start
    const streamKeyword = text.indexOf('stream', objStart);
    if (streamKeyword === -1)
        return null;
    let streamDataStart = streamKeyword + 6;
    if (pdfBytes[streamDataStart] === 0x0d)
        streamDataStart++;
    if (pdfBytes[streamDataStart] === 0x0a)
        streamDataStart++;
    const endstreamPos = text.indexOf('\nendstream', streamDataStart);
    if (endstreamPos === -1)
        return null;
    const compressedBytes = pdfBytes.slice(streamDataStart, endstreamPos);
    const objRegion = text.slice(objStart, streamKeyword);
    const isCompressed = objRegion.includes('/FlateDecode');
    let jsonBytes;
    if (isCompressed) {
        const ds = new DecompressionStream('deflate');
        const writer = ds.writable.getWriter();
        writer.write(compressedBytes);
        writer.close();
        const reader = ds.readable.getReader();
        const chunks = [];
        while (true) {
            const { done, value } = await reader.read();
            if (done)
                break;
            chunks.push(value);
        }
        const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
        jsonBytes = new Uint8Array(totalLength);
        let offset = 0;
        for (const chunk of chunks) {
            jsonBytes.set(chunk, offset);
            offset += chunk.length;
        }
    }
    else {
        jsonBytes = compressedBytes;
    }
    const jsonString = new TextDecoder('utf-8').decode(jsonBytes);
    return JSON.parse(jsonString);
}
