"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZipArchive = void 0;
exports.zip = zip;
const defaults_1 = require("../defaults.js");
const stream_1 = require("./stream.js");
const zip_bytes_1 = require("./zip-bytes.js");
const archive_sink_1 = require("../io/archive-sink.js");
const archive_source_1 = require("../io/archive-source.js");
const async_queue_1 = require("../utils/async-queue.js");
const REPRODUCIBLE_ZIP_MOD_TIME = new Date(1980, 0, 1, 0, 0, 0);
class ZipArchive {
    constructor(options = {}) {
        this._entries = [];
        this._sealed = false;
        const reproducible = options.reproducible ?? false;
        this._options = {
            level: options.level ?? defaults_1.DEFAULT_ZIP_LEVEL,
            timestamps: options.timestamps ?? (reproducible ? "dos" : defaults_1.DEFAULT_ZIP_TIMESTAMPS),
            comment: options.comment,
            modTime: options.modTime ?? (reproducible ? REPRODUCIBLE_ZIP_MOD_TIME : new Date()),
            smartStore: options.smartStore ?? true
        };
    }
    add(name, source, options) {
        if (this._sealed) {
            throw new Error("Cannot add entries after output has started");
        }
        if (!name) {
            throw new Error("Entry name is required");
        }
        this._entries.push({ name, source, options });
        return this;
    }
    stream() {
        this._sealed = true;
        const queue = (0, async_queue_1.createAsyncQueue)();
        const zip = new stream_1.StreamingZip((err, data, final) => {
            if (err) {
                queue.fail(err);
                return;
            }
            if (data.length) {
                queue.push(data);
            }
            if (final) {
                queue.close();
            }
        }, { comment: this._options.comment });
        (async () => {
            try {
                for (const entry of this._entries) {
                    const level = entry.options?.level ?? this._options.level;
                    const file = new stream_1.ZipDeflateFile(entry.name, {
                        level,
                        modTime: entry.options?.modTime ?? this._options.modTime,
                        timestamps: this._options.timestamps,
                        comment: entry.options?.comment,
                        smartStore: this._options.smartStore
                    });
                    zip.add(file);
                    // Feed data
                    if (entry.source instanceof Uint8Array ||
                        entry.source instanceof ArrayBuffer ||
                        typeof entry.source === "string" ||
                        (typeof Blob !== "undefined" && entry.source instanceof Blob)) {
                        const bytes = await (0, archive_source_1.toUint8Array)(entry.source);
                        await file.push(bytes, true);
                    }
                    else {
                        for await (const chunk of (0, archive_source_1.toAsyncIterable)(entry.source)) {
                            await file.push(chunk, false);
                        }
                        await file.push(new Uint8Array(0), true);
                    }
                    await file.complete();
                }
                zip.end();
            }
            catch (e) {
                queue.fail(e instanceof Error ? e : new Error(String(e)));
            }
        })();
        return queue.iterable;
    }
    async bytes() {
        this._sealed = true;
        const allSourcesInMemory = this._entries.every(e => e.source instanceof Uint8Array ||
            e.source instanceof ArrayBuffer ||
            typeof e.source === "string" ||
            (typeof Blob !== "undefined" && e.source instanceof Blob));
        const hasBlobSource = this._entries.some(e => typeof Blob !== "undefined" && e.source instanceof Blob);
        // Fast-path: when all sources are already in memory and there are no
        // per-entry compression overrides, use the single-buffer ZIP builder.
        // This avoids the overhead of chunking + collecting from the streaming writer.
        if (allSourcesInMemory) {
            // Prefer the sync builder when possible (Node.js hot path): it avoids
            // async/Promise overhead and uses zlib sync fast paths.
            if (!hasBlobSource) {
                const entries = this._entries.map(e => ({
                    name: e.name,
                    data: (0, archive_source_1.toUint8ArraySync)(e.source),
                    level: e.options?.level,
                    modTime: e.options?.modTime,
                    comment: e.options?.comment
                }));
                return (0, zip_bytes_1.createZipSync)(entries, {
                    level: this._options.level,
                    timestamps: this._options.timestamps,
                    modTime: this._options.modTime,
                    comment: this._options.comment,
                    smartStore: this._options.smartStore
                });
            }
            const entries = await Promise.all(this._entries.map(async (e) => ({
                name: e.name,
                data: await (0, archive_source_1.toUint8Array)(e.source),
                level: e.options?.level,
                modTime: e.options?.modTime,
                comment: e.options?.comment
            })));
            return (0, zip_bytes_1.createZip)(entries, {
                level: this._options.level,
                timestamps: this._options.timestamps,
                modTime: this._options.modTime,
                comment: this._options.comment,
                smartStore: this._options.smartStore
            });
        }
        return (0, archive_sink_1.collect)(this.stream());
    }
    bytesSync() {
        this._sealed = true;
        const entries = this._entries.map(e => {
            if (!(e.source instanceof Uint8Array) &&
                !(e.source instanceof ArrayBuffer) &&
                typeof e.source !== "string") {
                throw new Error("bytesSync() only supports Uint8Array/ArrayBuffer/string sources");
            }
            return {
                name: e.name,
                data: (0, archive_source_1.toUint8ArraySync)(e.source),
                modTime: e.options?.modTime,
                comment: e.options?.comment
            };
        });
        return (0, zip_bytes_1.createZipSync)(entries, {
            level: this._options.level,
            timestamps: this._options.timestamps,
            modTime: this._options.modTime,
            comment: this._options.comment,
            smartStore: this._options.smartStore
        });
    }
    async pipeTo(sink) {
        await (0, archive_sink_1.pipeIterableToSink)(this.stream(), sink);
    }
}
exports.ZipArchive = ZipArchive;
function zip(options) {
    return new ZipArchive(options);
}
