"use strict";
/**
 * WorksheetReader - Cross-Platform Streaming Worksheet Reader
 *
 * Works in both Node.js and Browser.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorksheetReader = void 0;
const _stream_1 = require("../../stream/index.js");
const parse_sax_1 = require("../utils/parse-sax.js");
const utils_1 = require("../../../utils/utils.js");
const col_cache_1 = require("../utils/col-cache.js");
const range_1 = require("../range.js");
const row_1 = require("../row.js");
const column_1 = require("../column.js");
class WorksheetReader extends _stream_1.EventEmitter {
    constructor({ workbook, id, iterator, options }) {
        super();
        this.workbook = workbook;
        this.id = id;
        this.sheetNo = typeof id === "number" ? id : parseInt(String(id), 10);
        this.iterator = iterator;
        this.options = options || {};
        // and a name
        this.name = `Sheet${this.id}`;
        // column definitions
        this._columns = null;
        this._keys = Object.create(null);
        // keep a record of dimensions
        this._dimensions = new range_1.Dimensions();
    }
    // destroy - not a valid operation for a streaming writer
    // even though some streamers might be able to, it's a bad idea.
    destroy() {
        throw new Error("Invalid Operation: destroy");
    }
    // return the current dimensions of the reader
    get dimensions() {
        return this._dimensions;
    }
    // =========================================================================
    // Columns
    // get the current columns array.
    get columns() {
        return this._columns;
    }
    // get a single column by col number. If it doesn't exist, it and any gaps before it
    // are created.
    getColumn(c) {
        if (typeof c === "string") {
            // if it matches a key'd column, return that
            const col = this._keys[c];
            if (col) {
                return col;
            }
            // otherwise, assume letter
            c = col_cache_1.colCache.l2n(c);
        }
        if (!this._columns) {
            this._columns = [];
        }
        if (c > this._columns.length) {
            let n = this._columns.length + 1;
            while (n <= c) {
                this._columns.push(new column_1.Column(this, n++));
            }
        }
        return this._columns[c - 1];
    }
    getColumnKey(key) {
        return this._keys[key];
    }
    setColumnKey(key, value) {
        this._keys[key] = value;
    }
    deleteColumnKey(key) {
        delete this._keys[key];
    }
    eachColumnKey(f) {
        const keys = this._keys;
        for (const key in keys) {
            f(keys[key], key);
        }
    }
    async read() {
        try {
            for await (const events of this.parse()) {
                for (let i = 0; i < events.length; i++) {
                    const event = events[i];
                    this.emit(event.eventType, event.value);
                }
            }
            this.emit("finished");
        }
        catch (error) {
            this.emit("error", error);
        }
    }
    async *[Symbol.asyncIterator]() {
        for await (const events of this.parse()) {
            for (let i = 0; i < events.length; i++) {
                const event = events[i];
                if (event.eventType === "row") {
                    yield event.value;
                }
            }
        }
    }
    async *parse() {
        const { iterator, options } = this;
        let emitSheet = false;
        let emitHyperlinks = false;
        let hyperlinks = null;
        switch (options.worksheets) {
            case "emit":
                emitSheet = true;
                break;
            case "prep":
                break;
            default:
                break;
        }
        switch (options.hyperlinks) {
            case "emit":
                emitHyperlinks = true;
                break;
            case "cache":
                this.hyperlinks = hyperlinks = Object.create(null);
                break;
            default:
                break;
        }
        if (!emitSheet && !emitHyperlinks && !hyperlinks) {
            return;
        }
        const shouldHandleHyperlinks = emitHyperlinks || hyperlinks !== null;
        // references
        const { sharedStrings, styles, properties } = this.workbook;
        // xml position
        let inCols = false;
        let inRows = false;
        let inHyperlinks = false;
        // parse state
        let cols = null;
        let row = null;
        let c = null;
        let current = null;
        for await (const events of (0, parse_sax_1.parseSax)(iterator)) {
            let worksheetEvents = null;
            for (let i = 0; i < events.length; i++) {
                const evt = events[i];
                const eventType = evt.eventType;
                const value = evt.value;
                if (eventType === "opentag") {
                    const node = value;
                    if (emitSheet) {
                        switch (node.name) {
                            case "cols":
                                inCols = true;
                                cols = [];
                                break;
                            case "sheetData":
                                inRows = true;
                                break;
                            case "col":
                                if (inCols) {
                                    cols.push({
                                        min: parseInt(node.attributes.min, 10),
                                        max: parseInt(node.attributes.max, 10),
                                        width: parseFloat(node.attributes.width),
                                        styleId: parseInt(node.attributes.style || "0", 10)
                                    });
                                }
                                break;
                            case "row":
                                if (inRows) {
                                    const r = parseInt(node.attributes.r, 10);
                                    row = new row_1.Row(this, r);
                                    if (node.attributes.ht) {
                                        row.height = parseFloat(node.attributes.ht);
                                    }
                                    if (node.attributes.s !== undefined) {
                                        const styleId = parseInt(node.attributes.s, 10);
                                        const style = styles.getStyleModel(styleId);
                                        if (style) {
                                            row.style = style;
                                        }
                                    }
                                }
                                break;
                            case "c":
                                if (row) {
                                    const styleAttr = node.attributes.s;
                                    c = {
                                        ref: node.attributes.r,
                                        s: styleAttr !== undefined ? parseInt(styleAttr, 10) : undefined,
                                        t: node.attributes.t
                                    };
                                }
                                break;
                            case "f":
                                if (c) {
                                    current = c.f = { text: "" };
                                }
                                break;
                            case "v":
                                if (c) {
                                    current = c.v = { text: "" };
                                }
                                break;
                            case "is":
                            case "t":
                                if (c) {
                                    current = c.v = { text: "" };
                                }
                                break;
                            case "mergeCell":
                                break;
                            default:
                                break;
                        }
                    }
                    // =================================================================
                    //
                    if (shouldHandleHyperlinks) {
                        switch (node.name) {
                            case "hyperlinks":
                                inHyperlinks = true;
                                break;
                            case "hyperlink":
                                if (inHyperlinks) {
                                    const hyperlink = {
                                        ref: node.attributes.ref,
                                        rId: node.attributes["r:id"]
                                    };
                                    if (emitHyperlinks) {
                                        (worksheetEvents || (worksheetEvents = [])).push({ eventType: "hyperlink", value: hyperlink });
                                    }
                                    else {
                                        hyperlinks[hyperlink.ref] = hyperlink;
                                    }
                                }
                                break;
                            default:
                                break;
                        }
                    }
                }
                else if (eventType === "text") {
                    // only text data is for sheet values
                    if (emitSheet) {
                        if (current) {
                            current.text += value;
                        }
                    }
                }
                else if (eventType === "closetag") {
                    const node = value;
                    if (emitSheet) {
                        switch (node.name) {
                            case "cols":
                                inCols = false;
                                this._columns = column_1.Column.fromModel(cols);
                                break;
                            case "sheetData":
                                inRows = false;
                                break;
                            case "row":
                                if (row) {
                                    this._dimensions.expandRow(row);
                                    (worksheetEvents || (worksheetEvents = [])).push({ eventType: "row", value: row });
                                }
                                row = null;
                                break;
                            case "c":
                                if (row && c) {
                                    const address = col_cache_1.colCache.decodeAddress(c.ref);
                                    const cell = row.getCell(address.col);
                                    if (c.s !== undefined) {
                                        const style = styles.getStyleModel(c.s);
                                        if (style) {
                                            cell.style = style;
                                        }
                                    }
                                    if (c.f) {
                                        const cellValue = {
                                            formula: c.f.text
                                        };
                                        if (c.v) {
                                            if (c.t === "str") {
                                                cellValue.result = (0, utils_1.xmlDecode)(c.v.text);
                                            }
                                            else {
                                                cellValue.result = parseFloat(c.v.text);
                                            }
                                        }
                                        cell.value = cellValue;
                                    }
                                    else if (c.v) {
                                        switch (c.t) {
                                            case "s": {
                                                const index = parseInt(c.v.text, 10);
                                                if (sharedStrings) {
                                                    cell.value = sharedStrings[index];
                                                }
                                                else {
                                                    // Streaming format - unresolved shared string reference
                                                    cell.value = {
                                                        sharedString: index
                                                    };
                                                }
                                                break;
                                            }
                                            case "inlineStr":
                                            case "str":
                                                cell.value = (0, utils_1.xmlDecode)(c.v.text);
                                                break;
                                            case "e":
                                                cell.value = { error: c.v.text };
                                                break;
                                            case "b":
                                                cell.value = parseInt(c.v.text, 10) !== 0;
                                                break;
                                            default: {
                                                const numFmtStr = typeof cell.numFmt === "string" ? cell.numFmt : cell.numFmt?.formatCode;
                                                if (numFmtStr && (0, utils_1.isDateFmt)(numFmtStr)) {
                                                    cell.value = (0, utils_1.excelToDate)(parseFloat(c.v.text), properties?.model?.date1904);
                                                }
                                                else {
                                                    cell.value = parseFloat(c.v.text);
                                                }
                                                break;
                                            }
                                        }
                                    }
                                    if (hyperlinks) {
                                        const hyperlink = hyperlinks[c.ref];
                                        if (hyperlink) {
                                            // Streaming-specific: assign text and hyperlink for further processing
                                            cell.text = cell.value;
                                            cell.value = undefined;
                                            cell.hyperlink = hyperlink;
                                        }
                                    }
                                    c = null;
                                    current = null;
                                }
                                break;
                            default:
                                break;
                        }
                    }
                    if (shouldHandleHyperlinks) {
                        switch (node.name) {
                            case "hyperlinks":
                                inHyperlinks = false;
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
            if (worksheetEvents && worksheetEvents.length > 0) {
                yield worksheetEvents;
            }
        }
    }
}
exports.WorksheetReader = WorksheetReader;
