"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TableDesc = void 0;
const tslib_1 = require("tslib");
const is_number_1 = tslib_1.__importDefault(require("is-number"));
const utils_1 = require("./utils.js");
class TableDesc {
    rows;
    cols;
    rowsDesc;
    baseOffset;
    static __cache = new WeakMap();
    static create(table) {
        if (this.__cache.has(table))
            return this.__cache.get(table);
        if (!(0, utils_1.isTableNode)(table))
            return null;
        const tbody = table.lastChild;
        if (!tbody || !(0, utils_1.isTableBodyNode)(tbody))
            return null;
        // table –> tbody -> ...
        const baseOffset = 2;
        const rows = [];
        tbody.forEach((trow, offset) => {
            rows.push({ node: trow, offset, cells: [] });
        });
        tbody.forEach((trow, _1, trowIndex) => {
            trow.forEach((tcell, offset, tcellRealIndex) => {
                const rowDesc = rows[trowIndex];
                let tcellIndex = tcellRealIndex;
                while (rowDesc.cells[tcellIndex]) {
                    tcellIndex++;
                }
                const cellDesc = (rowDesc.cells[tcellIndex] = {
                    type: 'real',
                    node: tcell,
                    offset,
                });
                const map = [trowIndex, tcellIndex];
                if ((0, is_number_1.default)(tcell.attrs['colspan'])) {
                    rowDesc.colspan = true;
                    const colspan = (cellDesc.colspan = parseInt(tcell.attrs['colspan'], 10));
                    for (let i = 1; i < colspan; i++) {
                        rowDesc.cells[tcellIndex + i] = {
                            type: 'virtual',
                            colspan: map,
                        };
                    }
                }
                if ((0, is_number_1.default)(tcell.attrs['rowspan'])) {
                    rowDesc.rowspan = true;
                    const rowspan = (cellDesc.rowspan = parseInt(tcell.attrs['rowspan'], 10));
                    for (let i = 1; i < rowspan; i++) {
                        const colspan = cellDesc.colspan ?? 1;
                        for (let j = 0; j < colspan; j++) {
                            const cell = (rows[trowIndex + i].cells[tcellIndex + j] = {
                                type: 'virtual',
                                rowspan: map,
                            });
                            if (colspan > 1) {
                                cell.colspan = map;
                            }
                        }
                    }
                }
            });
        });
        // ---> validation
        const rowsCount = rows.length;
        const colsCount = rows[0].cells.length;
        for (let r = 0; r < rowsCount; r++) {
            if (!rows[r] || rows[r].cells.length !== colsCount)
                return null;
            for (let c = 0; c < colsCount; c++) {
                if (!rows[r].cells[c])
                    return null;
            }
        }
        // <--- validation
        const desc = new this(rows.length, rows[0].cells.length, rows, baseOffset);
        this.__cache.set(table, desc);
        return desc;
    }
    _rowRanges;
    _columnRanges;
    constructor(
    /* eslint-disable @typescript-eslint/parameter-properties */
    rows, cols, rowsDesc, baseOffset) {
        this.rows = rows;
        this.cols = cols;
        this.rowsDesc = rowsDesc;
        this.baseOffset = baseOffset;
    }
    bind(pos) {
        return new TableDescBinded(pos, this);
    }
    rowHasVirtualCells(rowIndex) {
        return this.rowsDesc[rowIndex]?.cells.some((cell) => cell.type === 'virtual');
    }
    columnHasVirtualCells(columnIndex) {
        return this.rowsDesc.some((row) => row.cells[columnIndex]?.type === 'virtual');
    }
    isSafeColumn(columnIndex) {
        return this.rowsDesc.every((row) => {
            const cell = row.cells[columnIndex];
            if (cell.type === 'real' && (!cell.colspan || cell.colspan === 1))
                return true;
            if (cell.type === 'virtual' && cell.rowspan?.[1] === columnIndex)
                return true;
            return false;
        });
    }
    isSafeRow(rowIndex) {
        return this.rowsDesc[rowIndex]?.cells.every((cell) => {
            if (cell.type === 'real' && (!cell.rowspan || cell.rowspan === 1))
                return true;
            if (cell.type === 'virtual' && cell.colspan?.[0] === rowIndex)
                return true;
            return false;
        });
    }
    getOffsetForRow(rowIndex) {
        return this.baseOffset + this.rowsDesc[rowIndex]?.offset;
    }
    getRelativePosForRow(rowIndex) {
        const from = this.getOffsetForRow(rowIndex);
        const to = from + this.rowsDesc[rowIndex].node.nodeSize;
        return { from, to };
    }
    getRelativePosForCell(rowIndex, columnIndex) {
        const cell = this.rowsDesc[rowIndex]?.cells[columnIndex];
        const rowOffset = this.getOffsetForRow(rowIndex);
        if (cell?.type === 'real') {
            // 1 – open boundary for row node
            const from = rowOffset + 1 + cell.offset;
            const to = from + cell.node.nodeSize;
            return { type: 'real', from, to };
        }
        if (cell?.type === 'virtual') {
            let index = columnIndex;
            while (--index >= 0) {
                const cell = this.rowsDesc[rowIndex].cells[index];
                if (cell.type === 'real') {
                    const closest = rowOffset + 1 + cell.offset + cell.node.nodeSize;
                    return { type: 'virtual', closestPos: closest };
                }
            }
            return { type: 'virtual', closestPos: rowOffset + 1 };
        }
        throw new Error(`Impossible to calculate offset for cell with position [${rowIndex},${columnIndex}]`);
    }
    getRelativePosForRowCells(rowIndex) {
        const cells = [];
        for (let colIndex = 0; colIndex < this.cols; colIndex++) {
            cells.push(this.getRelativePosForCell(rowIndex, colIndex));
        }
        return cells;
    }
    getRelativePosForColumn(columnIndex) {
        const ranges = [];
        for (let i = 0; i < this.rows; i++) {
            ranges.push(this.getRelativePosForCell(i, columnIndex));
        }
        return ranges;
    }
    getCellNodeType() {
        const isRealCell = (cell) => cell.type === 'real';
        return this.rowsDesc[0].cells.find(isRealCell).node.type;
    }
    getRowNodeType() {
        return this.rowsDesc[0].node.type;
    }
    getCellInfo(node) {
        let desc = null;
        for (let i = 0; i < this.rowsDesc.length; i++) {
            const row = this.rowsDesc[i];
            for (let j = 0; j < row.cells.length; j++) {
                const cell = row.cells[j];
                if (cell.type === 'real' && cell.node === node) {
                    desc = {
                        node: cell.node,
                        offset: this.getOffsetForRow(i) + 1 + cell.offset,
                        row: i,
                        column: j,
                    };
                }
            }
        }
        return desc;
    }
    getRowRanges() {
        if (this._rowRanges)
            return this._rowRanges;
        const ranges = [];
        let last = null;
        for (let i = 0; i < this.rowsDesc.length; i++) {
            const row = this.rowsDesc[i];
            const firstCell = row.cells[0];
            if (firstCell.type === 'real') {
                let safeTopBoundary = true;
                for (const cell of row.cells) {
                    if (cell.type === 'virtual')
                        safeTopBoundary &&= !cell.rowspan;
                }
                ranges.push((last = {
                    startIdx: i,
                    endIdx: i,
                    rowsCount: 1,
                    safeTopBoundary,
                    // calculate later
                    safeBottomBoundary: true,
                }));
            }
            else if (firstCell.rowspan) {
                last.endIdx = i;
                last.rowsCount++;
            }
            else {
                throw new Error(`First cell in row is virtual, but without rowspan [row=${i}]`);
            }
        }
        for (let i = 0; i < ranges.length; i++) {
            if (!ranges[i].safeTopBoundary)
                ranges[i - 1].safeBottomBoundary = false;
        }
        this._rowRanges = ranges;
        return ranges;
    }
    getRowRangeByRowIdx(rowIdx) {
        const rangeIdx = this.getRowRangeIdxByRowIdx(rowIdx);
        return this.getRowRanges()[rangeIdx];
    }
    getRowRangeIdxByRowIdx(rowIdx) {
        return this.getRowRanges().findIndex((range) => range.startIdx <= rowIdx && range.endIdx >= rowIdx);
    }
    getColumnRanges() {
        if (this._columnRanges)
            return this._columnRanges;
        const ranges = [];
        let last = null;
        const firstRow = this.rowsDesc[0];
        for (let i = 0; i < firstRow.cells.length; i++) {
            const cell = firstRow.cells[i];
            if (cell.type === 'real') {
                let safeLeftBoundary = true;
                for (const row of this.rowsDesc) {
                    const cell = row.cells[i];
                    if (cell.type === 'virtual' && cell.colspan)
                        safeLeftBoundary &&= cell.colspan[1] === i;
                }
                ranges.push((last = {
                    startIdx: i,
                    endIdx: i,
                    columnsCount: 1,
                    safeLeftBoundary,
                    // calculate later
                    safeRightBoundary: true,
                }));
            }
            else if (cell.colspan) {
                last.endIdx = i;
                last.columnsCount++;
            }
            else {
                throw new Error(`First cell in column is virtual, but without colspan [column=${i}]`);
            }
        }
        for (let i = 0; i < ranges.length; i++) {
            if (!ranges[i].safeLeftBoundary)
                ranges[i - 1].safeRightBoundary = false;
        }
        this._columnRanges = ranges;
        return ranges;
    }
    getColumnRangeByColumnIdx(columnIdx) {
        const rangeIdx = this.getColumnRangeIdxByColumnIdx(columnIdx);
        return this.getColumnRanges()[rangeIdx];
    }
    getColumnRangeIdxByColumnIdx(columnIdx) {
        return this.getColumnRanges().findIndex((range) => range.startIdx <= columnIdx && range.endIdx >= columnIdx);
    }
}
exports.TableDesc = TableDesc;
class TableDescBinded {
    pos;
    base;
    get rows() {
        return this.base.rows;
    }
    get cols() {
        return this.base.cols;
    }
    constructor(pos, desc) {
        this.pos = pos;
        this.base = desc;
    }
    getRowIndexByPos(pos) {
        for (let rowIdx = 0; rowIdx < this.rows; rowIdx++) {
            const rowPos = this.pos + this.base.getOffsetForRow(rowIdx);
            if (pos === rowPos)
                return rowIdx;
        }
        return null;
    }
    getPosForRow(rowIndex) {
        const pos = this.base.getRelativePosForRow(rowIndex);
        pos.from += this.pos;
        pos.to += this.pos;
        return pos;
    }
    getPosForCell(rowIndex, columnIndex) {
        const pos = this.base.getRelativePosForCell(rowIndex, columnIndex);
        if (pos.type === 'real') {
            pos.from += this.pos;
            pos.to += this.pos;
        }
        else if (pos.type === 'virtual') {
            pos.closestPos += this.pos;
        }
        else {
            throw new Error(`Impossible to calculate positions for cell with position [${rowIndex},${columnIndex}]`);
        }
        return pos;
    }
    getPosForRowCells(rowIndex) {
        const cells = [];
        for (let colIndex = 0; colIndex < this.base.cols; colIndex++) {
            cells.push(this.getPosForCell(rowIndex, colIndex));
        }
        return cells;
    }
    getPosForColumn(columnIndex) {
        const ranges = [];
        for (let i = 0; i < this.base.rows; i++) {
            ranges.push(this.getPosForCell(i, columnIndex));
        }
        return ranges;
    }
}
//# sourceMappingURL=table-desc.js.map
