"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.XmlStream = void 0;
const utils_1 = require("../../../utils/utils.js");
// constants
const OPEN_ANGLE = "<";
const CLOSE_ANGLE = ">";
const OPEN_ANGLE_SLASH = "</";
const CLOSE_SLASH_ANGLE = "/>";
// Chunk size for periodic consolidation (reduces final join overhead)
const CHUNK_SIZE = 10000;
function pushAttribute(xml, name, value) {
    xml.push(` ${name}="${(0, utils_1.xmlEncode)(value.toString())}"`);
}
function pushAttributes(xml, attributes) {
    if (attributes) {
        const tmp = [];
        for (const name in attributes) {
            if (!Object.prototype.hasOwnProperty.call(attributes, name)) {
                continue;
            }
            const value = attributes[name];
            if (value !== undefined) {
                pushAttribute(tmp, name, value);
            }
        }
        xml.push(tmp.join(""));
    }
}
class XmlStream {
    constructor() {
        this._xml = [];
        this._chunks = [];
        this._stack = [];
        this._rollbacks = [];
        this.leaf = false;
        this.open = false;
    }
    _consolidate() {
        // Consolidation is not rollback-safe: joining and moving fragments into `_chunks`
        // cannot be undone. Avoid consolidating while a rollback marker is active.
        if (this._rollbacks.length > 0) {
            return;
        }
        // Periodically join small strings into larger chunks to reduce final join overhead
        if (this._xml.length >= CHUNK_SIZE) {
            this._chunks.push(this._xml.join(""));
            this._xml.length = 0;
        }
    }
    get tos() {
        return this._stack.length ? this._stack[this._stack.length - 1] : undefined;
    }
    get cursor() {
        // handy way to track whether anything has been added
        return this._chunks.length * CHUNK_SIZE + this._xml.length;
    }
    openXml(docAttributes) {
        const xml = this._xml;
        // <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
        xml.push("<?xml");
        pushAttributes(xml, docAttributes);
        xml.push("?>\n");
    }
    openNode(name, attributes) {
        const parent = this.tos;
        const xml = this._xml;
        if (parent && this.open) {
            xml.push(CLOSE_ANGLE);
        }
        this._stack.push(name);
        // start streaming node
        xml.push(OPEN_ANGLE);
        xml.push(name);
        pushAttributes(xml, attributes);
        this.leaf = true;
        this.open = true;
    }
    addAttribute(name, value) {
        if (!this.open) {
            throw new Error("Cannot write attributes to node if it is not open");
        }
        if (value !== undefined) {
            pushAttribute(this._xml, name, value);
        }
    }
    addAttributes(attrs) {
        if (!this.open) {
            throw new Error("Cannot write attributes to node if it is not open");
        }
        pushAttributes(this._xml, attrs);
    }
    writeText(text) {
        const xml = this._xml;
        if (this.open) {
            xml.push(CLOSE_ANGLE);
            this.open = false;
        }
        this.leaf = false;
        xml.push((0, utils_1.xmlEncode)(text.toString()));
    }
    writeXml(xml) {
        if (this.open) {
            this._xml.push(CLOSE_ANGLE);
            this.open = false;
        }
        this.leaf = false;
        this._xml.push(xml);
    }
    closeNode() {
        const node = this._stack.pop();
        const xml = this._xml;
        if (this.leaf) {
            xml.push(CLOSE_SLASH_ANGLE);
        }
        else {
            xml.push(OPEN_ANGLE_SLASH);
            xml.push(node);
            xml.push(CLOSE_ANGLE);
        }
        this.open = false;
        this.leaf = false;
        this._consolidate();
    }
    leafNode(name, attributes, text) {
        this.openNode(name, attributes);
        if (text !== undefined) {
            // zeros need to be written
            this.writeText(text);
        }
        this.closeNode();
    }
    closeAll() {
        while (this._stack.length) {
            this.closeNode();
        }
    }
    addRollback() {
        this._rollbacks.push({
            xml: this._xml.length,
            stack: this._stack.length,
            leaf: this.leaf ?? false,
            open: this.open ?? false,
            chunksLength: this._chunks.length
        });
        return this.cursor;
    }
    commit() {
        this._rollbacks.pop();
    }
    rollback() {
        const r = this._rollbacks.pop();
        if (this._xml.length > r.xml) {
            this._xml.length = r.xml;
        }
        if (this._stack.length > r.stack) {
            this._stack.length = r.stack;
        }
        if (this._chunks.length > r.chunksLength) {
            this._chunks.length = r.chunksLength;
        }
        this.leaf = r.leaf;
        this.open = r.open;
    }
    get xml() {
        this.closeAll();
        // Join chunks first, then remaining xml array
        if (this._chunks.length === 0) {
            return this._xml.join("");
        }
        if (this._xml.length > 0) {
            this._chunks.push(this._xml.join(""));
        }
        return this._chunks.join("");
    }
}
exports.XmlStream = XmlStream;
XmlStream.StdDocAttributes = {
    version: "1.0",
    encoding: "UTF-8",
    standalone: "yes"
};
