"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefinedNames = void 0;
const col_cache_1 = require("./utils/col-cache.js");
const cell_matrix_1 = require("./utils/cell-matrix.js");
const range_1 = require("./range.js");
const rangeRegexp = /[$](\w+)[$](\d+)(:[$](\w+)[$](\d+))?/;
class DefinedNames {
    constructor() {
        this.matrixMap = {};
    }
    getMatrix(name) {
        const matrix = this.matrixMap[name] || (this.matrixMap[name] = new cell_matrix_1.CellMatrix());
        return matrix;
    }
    // add a name to a cell. locStr in the form SheetName!$col$row or SheetName!$c1$r1:$c2:$r2
    add(locStr, name) {
        const location = col_cache_1.colCache.decodeEx(locStr);
        if ("error" in location) {
            return; // Invalid reference, skip
        }
        this.addEx(location, name);
    }
    addEx(location, name) {
        const matrix = this.getMatrix(name);
        if ("top" in location) {
            // It's a range (DecodedRange has top/left/bottom/right from Location)
            for (let col = location.left; col <= location.right; col++) {
                for (let row = location.top; row <= location.bottom; row++) {
                    const address = {
                        sheetName: location.sheetName,
                        address: col_cache_1.colCache.n2l(col) + row,
                        row,
                        col
                    };
                    matrix.addCellEx(address);
                }
            }
        }
        else {
            // It's a single cell address
            matrix.addCellEx(location);
        }
    }
    remove(locStr, name) {
        const location = col_cache_1.colCache.decodeEx(locStr);
        if ("error" in location) {
            return; // Invalid reference, skip
        }
        this.removeEx(location, name);
    }
    removeEx(location, name) {
        const matrix = this.getMatrix(name);
        if ("top" in location) {
            // Range - remove each cell
            for (let col = location.left; col <= location.right; col++) {
                for (let row = location.top; row <= location.bottom; row++) {
                    matrix.removeCellEx({
                        sheetName: location.sheetName,
                        address: col_cache_1.colCache.n2l(col) + row,
                        row,
                        col
                    });
                }
            }
        }
        else {
            matrix.removeCellEx(location);
        }
    }
    removeAllNames(location) {
        Object.values(this.matrixMap).forEach((matrix) => {
            if ("top" in location) {
                // Range - remove each cell
                for (let col = location.left; col <= location.right; col++) {
                    for (let row = location.top; row <= location.bottom; row++) {
                        matrix.removeCellEx({
                            sheetName: location.sheetName,
                            address: col_cache_1.colCache.n2l(col) + row,
                            row,
                            col
                        });
                    }
                }
            }
            else {
                matrix.removeCellEx(location);
            }
        });
    }
    forEach(callback) {
        Object.entries(this.matrixMap).forEach(([name, matrix]) => {
            matrix.forEach((cell) => {
                callback(name, cell);
            });
        });
    }
    // get all the names of a cell
    getNames(addressStr) {
        const location = col_cache_1.colCache.decodeEx(addressStr);
        if ("error" in location || "top" in location) {
            return []; // Invalid reference or range not supported
        }
        return this.getNamesEx(location);
    }
    getNamesEx(address) {
        return Object.entries(this.matrixMap)
            .map(([name, matrix]) => matrix.findCellEx(address, false) && name)
            .filter((name) => Boolean(name));
    }
    _explore(matrix, cell) {
        cell.mark = false;
        const { sheetName } = cell;
        const range = new range_1.Range(cell.row, cell.col, cell.row, cell.col, sheetName);
        let x;
        let y;
        // Helper to get cell with proper type
        const getCell = (row, col) => {
            return matrix.findCellAt(sheetName, row, col);
        };
        // grow vertical - only one col to worry about
        function vGrow(yy, edge) {
            const c = getCell(yy, cell.col);
            if (!c || !c.mark) {
                return false;
            }
            range[edge] = yy;
            c.mark = false;
            return true;
        }
        for (y = cell.row - 1; vGrow(y, "top"); y--) { }
        for (y = cell.row + 1; vGrow(y, "bottom"); y++) { }
        // grow horizontal - ensure all rows can grow
        function hGrow(xx, edge) {
            const cells = [];
            for (y = range.top; y <= range.bottom; y++) {
                const c = getCell(y, xx);
                if (c && c.mark) {
                    cells.push(c);
                }
                else {
                    return false;
                }
            }
            range[edge] = xx;
            for (let i = 0; i < cells.length; i++) {
                cells[i].mark = false;
            }
            return true;
        }
        for (x = cell.col - 1; hGrow(x, "left"); x--) { }
        for (x = cell.col + 1; hGrow(x, "right"); x++) { }
        return range;
    }
    getRanges(name, matrix) {
        matrix = matrix || this.matrixMap[name];
        if (!matrix) {
            return { name, ranges: [] };
        }
        // mark and sweep!
        matrix.forEach((cell) => {
            cell.mark = true;
        });
        const ranges = matrix
            .map((cell) => cell.mark && this._explore(matrix, cell))
            .filter(Boolean)
            .map((range) => range.$shortRange);
        return {
            name,
            ranges
        };
    }
    normaliseMatrix(matrix, sheetName) {
        // some of the cells might have shifted on specified sheet
        // need to reassign rows, cols
        matrix.forEachInSheet(sheetName, (cell, row, col) => {
            if (cell) {
                if (cell.row !== row || cell.col !== col) {
                    cell.row = row;
                    cell.col = col;
                    cell.address = col_cache_1.colCache.n2l(col) + row;
                }
            }
        });
    }
    spliceRows(sheetName, start, numDelete, numInsert) {
        Object.values(this.matrixMap).forEach((matrix) => {
            matrix.spliceRows(sheetName, start, numDelete, numInsert);
            this.normaliseMatrix(matrix, sheetName);
        });
    }
    spliceColumns(sheetName, start, numDelete, numInsert) {
        Object.values(this.matrixMap).forEach((matrix) => {
            matrix.spliceColumns(sheetName, start, numDelete, numInsert);
            this.normaliseMatrix(matrix, sheetName);
        });
    }
    get model() {
        // To get names per cell - just iterate over all names finding cells if they exist
        return Object.entries(this.matrixMap)
            .map(([name, matrix]) => this.getRanges(name, matrix))
            .filter((definedName) => definedName.ranges.length);
    }
    set model(value) {
        // value is [ { name, ranges }, ... ]
        const matrixMap = (this.matrixMap = {});
        value.forEach(definedName => {
            const matrix = (matrixMap[definedName.name] = new cell_matrix_1.CellMatrix());
            definedName.ranges.forEach(rangeStr => {
                if (rangeRegexp.test(rangeStr.split("!").pop() || "")) {
                    matrix.addCell(rangeStr);
                }
            });
        });
    }
}
exports.DefinedNames = DefinedNames;
