import { TemplateSectionLabel, MagicHeader0, MagicHeader1, } from '../../constants.js';
import { wasmInstance } from '../wasm.js';
let wasmModuleLoadedResolve;
const wasmModuleLoadedPromise = new Promise((resolve) => {
    wasmModuleLoadedResolve = resolve;
});
import { loadStyleFromJSON } from './cssLoader.js';
import { decodeBinaryMap } from '../../common/decodeUtils.js';
class StreamReader {
    #reader;
    #buffer = new Uint8Array(0);
    constructor(reader) {
        this.#reader = reader;
    }
    async read(size) {
        if (this.#buffer.length >= size) {
            const result = this.#buffer.slice(0, size);
            this.#buffer = this.#buffer.slice(size);
            return result;
        }
        while (this.#buffer.length < size) {
            const { done, value } = await this.#reader.read();
            if (value) {
                const newBuffer = new Uint8Array(this.#buffer.length + value.length);
                newBuffer.set(this.#buffer);
                newBuffer.set(value, this.#buffer.length);
                this.#buffer = newBuffer;
            }
            if (done) {
                break;
            }
        }
        if (this.#buffer.length < size) {
            if (this.#buffer.length === 0) {
                return null;
            }
            throw new Error(`Unexpected end of stream. Expected ${size} bytes, got ${this.#buffer.length}`);
        }
        const result = this.#buffer.slice(0, size);
        this.#buffer = this.#buffer.slice(size);
        return result;
    }
    async readRest() {
        while (true) {
            const { done, value } = await this.#reader.read();
            if (value) {
                const newBuffer = new Uint8Array(this.#buffer.length + value.length);
                newBuffer.set(this.#buffer);
                newBuffer.set(value, this.#buffer.length);
                this.#buffer = newBuffer;
            }
            if (done) {
                break;
            }
        }
        const result = this.#buffer;
        this.#buffer = new Uint8Array(0);
        return result;
    }
}
function decodeJSONMap(buffer) {
    const utf16Array = new Uint16Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 2);
    let jsonString = '';
    const CHUNK_SIZE = 8192;
    for (let i = 0; i < utf16Array.length; i += CHUNK_SIZE) {
        jsonString += String.fromCharCode.apply(null, utf16Array.subarray(i, i + CHUNK_SIZE));
    }
    return JSON.parse(jsonString);
}
self.onmessage = async (event) => {
    const data = event.data;
    if (data.type === 'init') {
        const { wasmModule } = data;
        wasmInstance.initSync({ module: wasmModule });
        wasmModuleLoadedResolve();
    }
    else if (data.type === 'load') {
        const { url, fetchUrl, overrideConfig, transformVW, transformVH, transformREM, } = data;
        try {
            const response = await fetch(fetchUrl, {
                headers: {
                    'Accept': 'application/octet-stream, application/json',
                },
            });
            if (!response.body || response.status !== 200) {
                throw new Error(`Failed to fetch template: ${response.statusText}`);
            }
            const reader = response.body.getReader();
            await handleStream(url, reader, transformVW, transformVH, transformREM, overrideConfig);
            postMessage({ type: 'done', url });
        }
        catch (error) {
            postMessage({ type: 'error', url, error: error.message });
        }
    }
};
async function handleStream(url, reader, transformVW, transformVH, transformREM, overrideConfig) {
    const streamReader = new StreamReader(reader);
    let config = {};
    // 1. Check MagicHeader
    const headerBytes = await streamReader.read(8);
    if (!headerBytes) {
        throw new Error('Empty stream');
    }
    // Check if JSON (starts with {)
    if (headerBytes[0] === 123) {
        const rest = await streamReader.readRest();
        const decoder = new TextDecoder();
        const jsonStr = decoder.decode(headerBytes) + decoder.decode(rest);
        const json = JSON.parse(jsonStr);
        await handleJSON(json, url, transformVW, transformVH, transformREM, overrideConfig);
        return;
    }
    const view = new DataView(headerBytes.buffer, headerBytes.byteOffset, headerBytes.byteLength);
    const magic0 = view.getUint32(0, true);
    const magic1 = view.getUint32(4, true);
    if (magic0 !== MagicHeader0 || magic1 !== MagicHeader1) {
        throw new Error('Invalid Magic Header');
    }
    // 2. Check Version
    const versionBytes = await streamReader.read(4);
    if (!versionBytes) {
        throw new Error('Unexpected EOF reading version');
    }
    const versionView = new DataView(versionBytes.buffer, versionBytes.byteOffset, versionBytes.byteLength);
    const version = versionView.getUint32(0, true);
    if (version > 1) {
        throw new Error(`Unsupported version: ${version}`);
    }
    // 3. Read Sections
    while (true) {
        const labelBytes = await streamReader.read(4);
        if (!labelBytes) {
            break; // EOF
        }
        const labelView = new DataView(labelBytes.buffer, labelBytes.byteOffset, labelBytes.byteLength);
        const label = labelView.getUint32(0, true);
        const lengthBytes = await streamReader.read(4);
        if (!lengthBytes) {
            throw new Error('Unexpected EOF reading section length');
        }
        const lengthView = new DataView(lengthBytes.buffer, lengthBytes.byteOffset, lengthBytes.byteLength);
        const length = lengthView.getUint32(0, true);
        const content = await streamReader.read(length);
        if (!content) {
            throw new Error(`Unexpected EOF reading section content. Expected ${length} bytes.`);
        }
        switch (label) {
            case TemplateSectionLabel.Configurations: {
                config = overrideConfig
                    ? { ...decodeJSONMap(content), ...overrideConfig }
                    : decodeJSONMap(content);
                postMessage({ type: 'section', label, url, data: config });
                break;
            }
            case TemplateSectionLabel.StyleInfo: {
                await wasmModuleLoadedPromise;
                const buffer = wasmInstance.decode_style_info(content, config['isLazy'] === 'true' ? url : undefined, config['enableCSSSelector'] === 'true', transformVW, transformVH, transformREM);
                postMessage({
                    type: 'section',
                    label,
                    url,
                    data: buffer.buffer,
                    config,
                }, {
                    transfer: [buffer.buffer],
                });
                break;
            }
            case TemplateSectionLabel.LepusCode: {
                const codeMap = decodeBinaryMap(content);
                const isLazy = config['isLazy'] === 'true';
                const blobMap = {};
                for (const [key, code] of Object.entries(codeMap)) {
                    const blob = new Blob([
                        '//# allFunctionsCalledOnLoad\n(function(){ "use strict"; const navigator=void 0,postMessage=void 0,window=void 0; ',
                        isLazy ? 'module.exports=' : '',
                        code,
                        ' \n })()\n//# sourceURL=',
                        url,
                        '/',
                        key,
                        '\n',
                    ], {
                        type: 'text/javascript; charset=utf-8',
                    });
                    blobMap[key] = URL.createObjectURL(blob);
                }
                postMessage({ type: 'section', label, url, data: blobMap, config });
                break;
            }
            case TemplateSectionLabel.ElementTemplates: {
                postMessage({ type: 'section', label, url, data: content }, [content.buffer]);
                break;
            }
            case TemplateSectionLabel.CustomSections: {
                postMessage({ type: 'section', label, url, data: content.buffer }, {
                    transfer: [content.buffer],
                });
                break;
            }
            case TemplateSectionLabel.Manifest: {
                const codeMap = decodeBinaryMap(content);
                const blobMap = {};
                for (const [key, code] of Object.entries(codeMap)) {
                    const blob = new Blob([
                        code,
                        '//# sourceURL=',
                        url,
                        '/',
                        key,
                    ], {
                        type: 'text/javascript; charset=utf-8',
                    });
                    blobMap[key] = URL.createObjectURL(blob);
                }
                postMessage({ type: 'section', label, url, data: blobMap });
                break;
            }
            default:
                throw new Error(`Unknown section label: ${label}`);
        }
    }
}
async function handleJSON(json, url, transformVW, transformVH, transformREM, overrideConfig) {
    // Configurations
    let config = {};
    if (json.pageConfig) {
        config = { ...json.pageConfig };
    }
    if (json.lepusCode?.root && typeof json.lepusCode.root === 'string') {
        const appType = json.appType
            ?? (json.lepusCode.root.startsWith('(function (globDynamicComponentEntry')
                ? 'lazy'
                : 'card');
        config.appType = config.appType ?? appType;
        config.isLazy = (appType === 'card') ? 'false' : 'true';
    }
    if (overrideConfig) {
        config = { ...config, ...overrideConfig };
    }
    config = Object.fromEntries(Object.entries(config).map(([key, value]) => [key, value.toString()]));
    postMessage({
        type: 'section',
        label: TemplateSectionLabel.Configurations,
        url,
        data: config,
    });
    // StyleInfo
    if (json.styleInfo) {
        await wasmModuleLoadedPromise;
        const buffer = loadStyleFromJSON(json.styleInfo, config['enableCSSSelector'] === 'true', transformVW, transformVH, transformREM, config['isLazy'] === 'true' ? url : undefined);
        postMessage({
            type: 'section',
            label: TemplateSectionLabel.StyleInfo,
            url,
            data: buffer.buffer,
            config,
        }, {
            transfer: [buffer.buffer],
        });
    }
    // LepusCode
    if (json.lepusCode) {
        // Flattened structure in json: { root: "...", chunk1: "..." }
        const isLazy = config['isLazy'] === 'true';
        const blobMap = {};
        for (const [key, code] of Object.entries(json.lepusCode)) {
            if (typeof code !== 'string')
                continue;
            const prefix = `//# allFunctionsCalledOnLoad\n(function(){ "use strict"; const navigator=void 0,postMessage=void 0,window=void 0; ${isLazy ? 'module.exports=' : ''} `;
            const suffix = ` \n })()\n//# sourceURL=${url}/${key}\n`;
            const blob = new Blob([prefix, code, suffix], {
                type: 'text/javascript; charset=utf-8',
            });
            blobMap[key] = URL.createObjectURL(blob);
        }
        postMessage({
            type: 'section',
            label: TemplateSectionLabel.LepusCode,
            url,
            data: blobMap,
            config,
        });
    }
    // Manifest
    if (json.manifest) {
        const blobMap = {};
        for (const [key, code] of Object.entries(json.manifest)) {
            if (typeof code !== 'string')
                continue;
            const blob = new Blob([code], {
                type: 'text/javascript;',
            });
            blobMap[key] = URL.createObjectURL(blob);
        }
        postMessage({
            type: 'section',
            label: TemplateSectionLabel.Manifest,
            url,
            data: blobMap,
        });
    }
    // CustomSections
    if (json.customSections) {
        // Currently we don't have a way to encode custom sections here.
        // If main thread accepts generic object, we send it.
        // But TemplateManager expects buffer?
        // TemplateManager: case CustomSections: #setCustomSection(url, data). data: any.
        // So passing object is fine!
        postMessage({
            type: 'section',
            label: TemplateSectionLabel.CustomSections,
            url,
            data: json.customSections,
        });
    }
    // ElementTemplates
    if (json.elementTemplates && Object.keys(json.elementTemplates).length > 0) {
        // TemplateManager expects Uint8Array for ElementTemplates.
        // We can't support this easily for JSON.
        throw new Error('ElementTemplates in JSON artifacts are not supported yet.');
    }
}
postMessage({ type: 'ready' });
//# sourceMappingURL=decode.worker.js.map