/**
 * XLSX - Node.js version with full functionality
 *
 * Extends XLSX with:
 * - readFile: Read from file path
 * - writeFile: Write to file path
 * - Constructor injects readFileAsync for filename-based media loading
 *
 * Inherited from XLSX:
 * - read: Read from stream
 * - write: Write to stream
 * - load: Load from buffer
 * - writeBuffer: Write to buffer (Uint8Array)
 * - addMedia: Supports buffer, base64, and filename (via readFileAsync)
 */
import fs from "fs";
import { fileExists } from "../../../utils/utils.browser.js";
import { XLSX as XLSXBase } from "./xlsx.browser.js";
import { Parse } from "../../archive/unzip/stream.browser.js";
import { Writable, pipeline } from "../../stream/index.browser.js";
class XLSX extends XLSXBase {
    constructor(workbook) {
        super(workbook);
        // Provide file reading capability for addMedia
        this.readFileAsync = (filename) => fs.promises.readFile(filename);
    }
    static async *iterateZipEntries(parser) {
        const queue = [];
        let head = 0;
        let done = false;
        let error;
        let notify = null;
        const wake = () => {
            if (notify) {
                const fn = notify;
                notify = null;
                fn();
            }
        };
        parser.on("entry", (entry) => {
            queue.push(entry);
            wake();
        });
        parser.once("error", (err) => {
            error = err;
            wake();
        });
        parser.once("close", () => {
            done = true;
            wake();
        });
        while (!done || head < queue.length) {
            if (error) {
                throw error;
            }
            if (head < queue.length) {
                const entry = queue[head++];
                // Periodically compact to avoid unbounded growth.
                if (head > 1024 && head > queue.length / 2) {
                    queue.splice(0, head);
                    head = 0;
                }
                yield entry;
                continue;
            }
            await new Promise(resolve => {
                notify = resolve;
            });
        }
    }
    // ==========================================================================
    // Node.js specific: TRUE streaming read
    // ==========================================================================
    async read(stream, options) {
        const parser = new Parse();
        const swallowError = () => {
            // Prevent unhandled 'error' events from crashing the process.
            // Errors are surfaced via rejected promises.
        };
        // Always attach an error listener to avoid uncaught exceptions.
        parser.on("error", swallowError);
        if (stream && typeof stream.on === "function") {
            stream.on("error", swallowError);
        }
        // Pump incoming data into the ZIP parser without buffering the whole file.
        // NOTE: `Parse` is a Duplex; passing it directly to pipeline() can make
        // Node treat it like a transform and surface `Premature close` errors.
        // We instead pipeline the input stream into a Writable sink that forwards
        // chunks into the parser with backpressure.
        const sink = new Writable({
            write(chunk, _encoding, callback) {
                try {
                    const ok = parser.write(chunk);
                    if (ok) {
                        callback();
                    }
                    else {
                        parser.once("drain", () => callback());
                    }
                }
                catch (e) {
                    callback(e);
                }
            },
            final(callback) {
                try {
                    parser.end();
                    callback();
                }
                catch (e) {
                    callback(e);
                }
            }
        });
        const onParserError = (err) => {
            try {
                sink.destroy(err);
            }
            catch {
                // ignore
            }
        };
        parser.on("error", onParserError);
        const pump = pipeline(stream, sink);
        const entries = (async function* () {
            for await (const entry of XLSX.iterateZipEntries(parser)) {
                entry.on("error", swallowError);
                const drain = async () => {
                    const anyEntry = entry;
                    if (anyEntry?.readableEnded || anyEntry?.destroyed) {
                        return;
                    }
                    const draining = entry.autodrain();
                    await draining.promise();
                };
                yield {
                    name: entry.path,
                    type: entry.type,
                    stream: entry,
                    drain
                };
            }
        })();
        try {
            const workbook = await this.loadFromZipEntries(entries, options);
            await pump;
            return workbook;
        }
        catch (err) {
            // Stop the ZIP parser so pipeline() can unwind promptly.
            try {
                parser.destroy();
            }
            catch {
                // ignore
            }
            // Ensure pump settles to avoid unhandled rejections.
            try {
                await pump;
            }
            catch {
                // ignore pump failures; the original parse error is more useful
            }
            throw err;
        }
        finally {
            try {
                parser.off("error", onParserError);
            }
            catch {
                // ignore
            }
            try {
                parser.off("error", swallowError);
            }
            catch {
                // ignore
            }
            if (stream && typeof stream.off === "function") {
                try {
                    stream.off("error", swallowError);
                }
                catch {
                    // ignore
                }
            }
        }
    }
    // ===========================================================================
    // Node.js specific: File operations
    // ===========================================================================
    async readFile(filename, options) {
        if (!(await fileExists(filename))) {
            throw new Error(`File not found: ${filename}`);
        }
        const stream = fs.createReadStream(filename);
        return this.read(stream, options);
    }
    writeFile(filename, options) {
        const stream = fs.createWriteStream(filename);
        return new Promise((resolve, reject) => {
            let settled = false;
            const cleanup = () => {
                stream.off("finish", onFinish);
                stream.off("error", onError);
                stream.off("close", onClose);
            };
            const onFinish = () => {
                if (!settled) {
                    settled = true;
                    resolve();
                }
            };
            const onError = (error) => {
                if (!settled) {
                    settled = true;
                    reject(error);
                }
            };
            const onClose = () => {
                cleanup();
            };
            stream.once("finish", onFinish);
            stream.once("error", onError);
            stream.once("close", onClose);
            this.write(stream, options).catch(err => {
                if (!settled) {
                    settled = true;
                    reject(err);
                }
                // Ensure the underlying FD is closed on failure. Keep listeners
                // attached until the stream closes, otherwise the emitted 'error'
                // event can become an uncaught exception.
                stream.destroy(err);
            });
        });
    }
}
export { XLSX };
