"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Grid = void 0;
var tslib_1 = require("tslib");
var rxjs_1 = require("rxjs");
var operators_1 = require("rxjs/operators");
var commands_1 = require("../../commands");
var common_1 = require("../../common");
var constants_1 = require("../../common/constants");
var keyboard_1 = require("../../keyboard");
var Cell_1 = require("../Cell");
var Grid_calc_1 = require("./Grid.calc");
var Grid = (function () {
    function Grid(args) {
        var _this = this;
        this._ = {
            id: '',
            totalColumns: -1,
            totalRows: -1,
            defaults: undefined,
            keyBindings: undefined,
            cache: common_1.MemoryCache.create(),
            table: undefined,
            dispose$: new rxjs_1.Subject(),
            events$: new rxjs_1.Subject(),
            redraw$: new rxjs_1.Subject(),
            isReady: false,
            isEditing: false,
            ns: constants_1.DEFAULT.NS,
            cells: {},
            columns: {},
            rows: {},
            lastSelection: undefined,
            calc: undefined,
            refs: undefined,
        };
        this.dispose$ = this._.dispose$.pipe(operators_1.share());
        this.events$ = this._.events$.pipe(operators_1.takeUntil(this.dispose$), operators_1.share());
        this.keyboard$ = this._.events$.pipe(operators_1.filter(function (e) { return e.type === 'GRID/keydown'; }), operators_1.map(function (e) { return e.payload; }), operators_1.share());
        this.getValueSync = function (key) {
            var cell = _this.data.cells[key];
            return cell && typeof cell.value === 'string' ? cell.value : undefined;
        };
        this.refsTable = common_1.coord.refs.table({
            getKeys: function () { return tslib_1.__awaiter(_this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
                return [2, Object.keys(this.data.cells)];
            }); }); },
            getValue: function (key) { return tslib_1.__awaiter(_this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
                return [2, this.getValueSync(key)];
            }); }); },
        });
        this.command = function (args) {
            var payload = {
                command: args.command,
                grid: _this,
                selection: _this.selection,
                props: args.props || {},
                isCancelled: false,
                cancel: function () {
                    payload.isCancelled = true;
                    if (args.cancel) {
                        args.cancel();
                    }
                },
            };
            _this.fire({ type: 'GRID/command', payload: payload });
            return _this;
        };
        this.fire = function (e) {
            _this._.events$.next(e);
            return _this;
        };
        this._init(args);
    }
    Grid.create = function (args) {
        return new Grid(args);
    };
    Grid.defaults = function (input) {
        var partial = input || {};
        return {
            ns: common_1.defaultValue(partial.ns, constants_1.DEFAULT.NS),
            totalColumns: common_1.defaultValue(partial.totalColumns, constants_1.DEFAULT.TOTAL_COLUMNS),
            totalRows: common_1.defaultValue(partial.totalRows, constants_1.DEFAULT.TOTAL_ROWS),
            columnWidth: common_1.defaultValue(partial.columnWidth, constants_1.DEFAULT.COLUMN.WIDTH),
            columnWidthMin: common_1.defaultValue(partial.columnWidthMin, constants_1.DEFAULT.COLUMN.WIDTH_MIN),
            rowHeight: common_1.defaultValue(partial.rowHeight, constants_1.DEFAULT.ROW.HEIGHT),
            rowHeightMin: common_1.defaultValue(partial.rowHeightMin, constants_1.DEFAULT.ROW.HEIGHT_MIN),
        };
    };
    Grid.toDataArray = function (args) {
        return Array.from({ length: args.totalRows }).map(function (v, row) {
            return Array.from({ length: args.totalColumns }).map(function (v, column) {
                return args.cells[Cell_1.Cell.toKey({ row: row, column: column })];
            });
        });
    };
    Grid.toNs = function (input) {
        var ns = input === undefined ? constants_1.DEFAULT.NS : typeof input === 'string' ? { id: input } : input;
        ns.id = ns.id.trim().replace(/^ns\:/, '');
        return ns;
    };
    Grid.isDefaultValue = function (args) {
        var kind = args.kind, value = args.value;
        var defaults = Grid.defaults(args.defaults);
        return common_1.util.isDefaultGridValue({ defaults: defaults, kind: kind, value: value });
    };
    Grid.prototype._init = function (args) {
        var _this = this;
        var defaults = (this._.defaults = Grid.defaults(args.defaults));
        this._.totalColumns = common_1.defaultValue(args.totalColumns, defaults.totalColumns);
        this._.totalRows = common_1.defaultValue(args.totalRows, defaults.totalRows);
        this._.ns = Grid.toNs(args.ns);
        this._.cells = args.cells || {};
        this._.columns = args.columns || {};
        this._.rows = args.rows || {};
        this._.calc = Grid_calc_1.calc({ grid: this, getFunc: args.getFunc });
        this.events$
            .pipe(operators_1.filter(function (e) { return e.type === 'GRID/ready'; }))
            .subscribe(function () { return (_this._.isReady = true); });
        this._.keyBindings = common_1.R.uniqBy(common_1.R.prop('command'), tslib_1.__spreadArrays((args.keyBindings || []), constants_1.DEFAULT.KEY_BINDINGS));
        var table = args.table;
        if (table) {
            this.initialize({ table: table });
        }
    };
    Grid.prototype.initialize = function (args) {
        var _this = this;
        var table = args.table;
        this._.table = table;
        this._.id = "grid/" + table.guid.replace(/^ht_/, '');
        keyboard_1.keyboard.init({ grid: this });
        commands_1.commands.init({ grid: this, fire: this.fire });
        this._.redraw$
            .pipe(operators_1.takeUntil(this.dispose$), operators_1.debounceTime(0))
            .subscribe(function (e) { return _this.fire({ type: 'GRID/redraw', payload: { redraw: true } }); });
        this.events$
            .pipe(operators_1.filter(function (e) { return e.type === 'GRID/EDITOR/begin'; }))
            .subscribe(function () { return (_this._.isEditing = true); });
        var editEnd$ = this.events$.pipe(operators_1.filter(function (e) { return e.type === 'GRID/EDITOR/end'; }), operators_1.map(function (e) { return e.payload; }));
        editEnd$.subscribe(function () { return (_this._.isEditing = false); });
        editEnd$.pipe(operators_1.filter(function (e) { return e.isChanged; })).subscribe(function (e) {
            var _a;
            var key = e.cell.key;
            var value = e.value.to;
            var props = tslib_1.__assign(tslib_1.__assign({}, _this.cell(key).data.props), { value: undefined });
            var cell = { value: value, props: props };
            _this.changeCells((_a = {}, _a[key] = cell, _a), { source: 'EDIT' });
        });
        editEnd$
            .pipe(operators_1.filter(function (e) { return !e.isCancelled; }), operators_1.filter(function (e) { return e.cell.key === _this.selection.cell; }))
            .subscribe(function (e) {
            var below = e.cell.siblings.bottom;
            if (below) {
                _this.select({ cell: below });
            }
        });
        var selection$ = this.events$.pipe(operators_1.filter(function (e) { return e.type === 'GRID/selection'; }), operators_1.map(function (e) { return e.payload; }));
        selection$
            .pipe(operators_1.filter(function (e) { return Boolean(e.to.cell); }))
            .subscribe(function (e) { return (_this._.lastSelection = e.to); });
        selection$
            .pipe(operators_1.debounceTime(0), operators_1.filter(function (e) { return !Boolean(e.from.cell) && Boolean(e.to.cell); }))
            .subscribe(function (e) { return _this.fire({ type: 'GRID/focus', payload: { grid: _this } }); });
        selection$
            .pipe(operators_1.debounceTime(0), operators_1.filter(function (e) { return Boolean(e.from.cell) && !Boolean(e.to.cell); }))
            .subscribe(function (e) { return _this.fire({ type: 'GRID/blur', payload: { grid: _this } }); });
        this.events$
            .pipe(operators_1.filter(function (e) { return (_this._.isReady = true); }), operators_1.filter(function (e) { return e.type === 'GRID/cells/change'; }), operators_1.map(function (e) { return e.payload; }))
            .subscribe(function (e) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
            var cells;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        cells = e.changes.map(function (change) { return change.cell.key; });
                        return [4, this.calc.update({ cells: cells })];
                    case 1:
                        _a.sent();
                        return [2];
                }
            });
        }); });
        return this;
    };
    Object.defineProperty(Grid.prototype, "id", {
        get: function () {
            return this._.id;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Grid.prototype, "totalColumns", {
        get: function () {
            return this._.totalColumns;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Grid.prototype, "totalRows", {
        get: function () {
            return this._.totalRows;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Grid.prototype, "defaults", {
        get: function () {
            return this._.defaults;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Grid.prototype, "keyBindings", {
        get: function () {
            return this._.keyBindings;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Grid.prototype, "isInitialized", {
        get: function () {
            return Boolean(this._.table);
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Grid.prototype, "isDisposed", {
        get: function () {
            return this._.table.isDestroyed || this._.dispose$.isStopped;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Grid.prototype, "isReady", {
        get: function () {
            return this.isDisposed ? false : this._.isReady;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Grid.prototype, "isEditing", {
        get: function () {
            return this._.isEditing;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Grid.prototype, "data", {
        get: function () {
            var _a = this._, ns = _a.ns, cells = _a.cells, columns = _a.columns, rows = _a.rows;
            var files = {};
            return { ns: ns, cells: cells, columns: columns, rows: rows, files: files };
        },
        enumerable: false,
        configurable: true
    });
    Grid.prototype.setCells = function (cells) {
        cells = tslib_1.__assign({}, cells);
        var totalColumns = this.totalColumns;
        var totalRows = this.totalRows;
        var data = Grid.toDataArray({ cells: cells, totalColumns: totalColumns, totalRows: totalRows });
        this._.cells = cells;
        this._.table.loadData(data);
    };
    Object.defineProperty(Grid.prototype, "calc", {
        get: function () {
            return this._.calc;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Grid.prototype, "selection", {
        get: function () {
            var toKey = function (coord) {
                return Cell_1.Cell.toKey({ row: coord.row, column: coord.col });
            };
            var last = this._.table.getSelectedRangeLast();
            var cell = last ? toKey(last.highlight) : undefined;
            var selectedRanges = this._.table.getSelectedRange() || [];
            var ranges = selectedRanges.map(function (item) { return toKey(item.from) + ":" + toKey(item.to); });
            ranges = ranges.length === 1 && ranges[0] === cell + ":" + cell ? [] : ranges;
            var all = false;
            if (ranges.length > 0) {
                var min_1 = { row: -1, col: -1 };
                var max_1 = { row: -1, col: -1 };
                selectedRanges.forEach(function (range) {
                    var from = range.from, to = range.to;
                    min_1.row = min_1.row === -1 || from.row < min_1.row ? from.row : min_1.row;
                    min_1.col = min_1.col === -1 || from.col < min_1.col ? from.col : min_1.col;
                    max_1.row = max_1.row === -1 || to.row > max_1.row ? to.row : max_1.row;
                    max_1.col = max_1.col === -1 || to.col > max_1.col ? to.col : max_1.col;
                });
                if (min_1.row === 0 &&
                    min_1.col === 0 &&
                    max_1.row === this.totalRows - 1 &&
                    max_1.col === this.totalColumns - 1) {
                    all = true;
                }
            }
            if (ranges.length > 0) {
                var totalColumns = this.totalColumns;
                var totalRows = this.totalRows;
                var union = common_1.coord.range.union(ranges).formated({ totalColumns: totalColumns, totalRows: totalRows });
                ranges = union.ranges.map(function (range) { return range.key; });
                if (cell) {
                    ranges = ranges.filter(function (range) { return range !== cell + ":" + cell; });
                }
                ranges = common_1.R.uniq(ranges);
            }
            var result = { cell: cell, ranges: ranges };
            result = all ? tslib_1.__assign(tslib_1.__assign({}, result), { all: all }) : result;
            return result;
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(Grid.prototype, "selectionValues", {
        get: function () {
            var cells = this.data.cells;
            var selection = this.selection;
            return common_1.toSelectionValues({ cells: cells, selection: selection });
        },
        enumerable: false,
        configurable: true
    });
    Grid.prototype.dispose = function () {
        var _a = this._, table = _a.table, dispose$ = _a.dispose$;
        if (!table.isDestroyed) {
            table.destroy();
        }
        dispose$.next();
        dispose$.complete();
    };
    Grid.prototype.mergeCells = function (args) {
        var cells = args.cells;
        var list = args.init
            ? []
            : this._.table.getSettings().mergeCells || [];
        var map = list.reduce(function (acc, next) {
            var key = common_1.coord.cell.toKey(next.col, next.row);
            acc[key] = next;
            return acc;
        }, {});
        Object.keys(cells).map(function (key) {
            var item = cells[key];
            if (item) {
                var props = item.props || {};
                var merge = props.merge;
                if (merge) {
                    var rowspan = Math.max(1, common_1.defaultValue(merge.rowspan, 1));
                    var colspan = Math.max(1, common_1.defaultValue(merge.colspan, 1));
                    var _a = common_1.coord.cell.fromKey(key), row = _a.row, col = _a.column;
                    map[key] = { row: row, col: col, rowspan: rowspan, colspan: colspan };
                }
            }
        });
        var mergeCells = Object.keys(map).map(function (key) { return map[key]; });
        this._.table.updateSettings({ mergeCells: mergeCells }, false);
        return this;
    };
    Grid.prototype.changeCells = function (cells, options) {
        var _this = this;
        if (options === void 0) { options = {}; }
        var done = function () { return _this; };
        var format = function (key, to) {
            if (Cell_1.Cell.isEmpty(to)) {
                return undefined;
            }
            if (to) {
                to = tslib_1.__assign(tslib_1.__assign({}, to), { hash: common_1.util.gridCellHash(_this, key, to) });
                if (Cell_1.Cell.isEmptyProps(to.props)) {
                    delete to.props;
                }
            }
            return to;
        };
        if (options.init && cells && Object.keys(cells).length === 0) {
            this.setCells({});
            return done();
        }
        if (cells) {
            var current_1 = tslib_1.__assign({}, (options.init ? {} : this.data.cells));
            cells = tslib_1.__assign({}, cells);
            Object.keys(cells)
                .filter(function (key) { return !common_1.coord.cell.isCell(key); })
                .forEach(function (key) { return delete cells[key]; });
            var formatted_1 = Object.keys(cells).reduce(function (acc, key) {
                acc[key] = {
                    from: format(key, current_1[key]),
                    to: format(key, cells[key]),
                };
                return acc;
            }, {});
            var changes_1 = Object.keys(cells).map(function (key) {
                var cell = _this.cell(key);
                var _a = formatted_1[key], from = _a.from, to = _a.to;
                return Cell_1.Cell.changeEvent({ cell: cell, from: from, to: to });
            });
            var isChanged = changes_1.some(function (e) { return e.isChanged; });
            if (!isChanged) {
                return done();
            }
            if (!options.silent) {
                var payload = {
                    source: common_1.defaultValue(options.source, 'EDIT'),
                    changes: changes_1,
                    get isCancelled() {
                        return changes_1.some(function (change) { return change.isCancelled; });
                    },
                    cancel: function () {
                        changes_1.forEach(function (change) { return change.cancel(); });
                    },
                };
                this.fire({ type: 'GRID/cells/change', payload: payload });
                changes_1
                    .filter(function (change) { return change.isModified; })
                    .forEach(function (change) { return (cells[change.cell.key] = change.value.to); });
                changes_1
                    .filter(function (change) { return change.isCancelled; })
                    .forEach(function (change) { return (cells[change.cell.key] = change.value.from); });
            }
            var mergeChanges_1 = {};
            var updates_1 = tslib_1.__assign(tslib_1.__assign({}, current_1), Object.keys(formatted_1).reduce(function (acc, key) {
                acc[key] = formatted_1[key].to;
                return acc;
            }, {}));
            Object.keys(formatted_1).forEach(function (key) {
                var _a = formatted_1[key], from = _a.from, to = _a.to;
                if (Cell_1.Cell.isEmpty(to)) {
                    delete updates_1[key];
                    return;
                }
                var isMergeChanged = !common_1.R.equals(((from || {}).props || {}).merge, ((to || {}).props || {}).merge);
                if (isMergeChanged) {
                    mergeChanges_1[key] = to;
                }
            });
            if (Object.keys(mergeChanges_1).length > 0) {
                this.mergeCells({ cells: mergeChanges_1 });
            }
            this.setCells(updates_1);
        }
        return done();
    };
    Grid.prototype.changeColumns = function (columns, options) {
        var _this = this;
        if (options === void 0) { options = {}; }
        var _a = options.source, source = _a === void 0 ? 'UPDATE' : _a;
        var from = tslib_1.__assign({}, this._.columns);
        var to = tslib_1.__assign({}, from);
        var changes = [];
        Object.keys(columns).forEach(function (key) {
            var prev = from[key] || { props: { grid: { width: -1 } } };
            var next = columns[key] || { props: { grid: { width: _this.defaults.columnWidth } } };
            var nextProps = next.props || {};
            var isDefault = nextProps.grid && nextProps.grid.width === _this.defaults.columnWidth;
            if (isDefault) {
                delete to[key];
            }
            else {
                to[key] = next;
            }
            if (!common_1.R.equals(prev, next)) {
                changes = tslib_1.__spreadArrays(changes, [{ column: key, source: source, from: prev, to: next }]);
            }
        });
        this._.columns = to;
        if (!common_1.R.equals(from, to)) {
            this.fire({ type: 'GRID/columns/change', payload: { from: from, to: to, changes: changes } });
        }
        return this;
    };
    Grid.prototype.changeRows = function (rows, options) {
        var _this = this;
        if (options === void 0) { options = {}; }
        var _a = options.source, source = _a === void 0 ? 'UPDATE' : _a;
        var from = tslib_1.__assign({}, this._.rows);
        var to = tslib_1.__assign({}, from);
        var changes = [];
        Object.keys(rows).forEach(function (key) {
            var prev = from[key] || { props: { grid: { height: -1 } } };
            var next = rows[key] || { props: { grid: { height: _this.defaults.rowHeight } } };
            var nextProps = next.props || {};
            var isDefault = nextProps.grid && nextProps.grid.height === _this.defaults.rowHeight;
            if (isDefault) {
                delete to[key];
            }
            else {
                to[key] = next;
            }
            if (!common_1.R.equals(prev, next)) {
                var row = common_1.coord.cell.fromKey(key).row;
                changes = tslib_1.__spreadArrays(changes, [{ row: row, source: source, from: prev, to: next }]);
            }
        });
        this._.rows = to;
        if (!common_1.R.equals(from, to)) {
            this.fire({
                type: 'GRID/rows/change',
                payload: { from: from, to: to, changes: changes },
            });
        }
        return this;
    };
    Grid.prototype.cell = function (key) {
        var _this = this;
        var args = typeof key === 'string' ? Cell_1.Cell.fromKey(key) : key;
        var row = args.row, column = args.column;
        if (row < 0 || column < 0) {
            var msg = "Cell does not exist at row:" + row + ", column:" + column + ".";
            msg = typeof key === 'string' ? msg + " key: \"" + key + "\"" : msg;
            throw new Error(msg);
        }
        var ns = this.data.ns.id;
        var cacheKey = ns + ":cell/" + column + ":" + row;
        return this._.cache.get(cacheKey, function () {
            var table = _this._.table;
            return Cell_1.Cell.create({ ns: ns, table: table, row: row, column: column });
        });
    };
    Grid.prototype.scrollTo = function (args) {
        var _a = this.toPosition(args.cell), row = _a.row, column = _a.column;
        var _b = args.snapToBottom, snapToBottom = _b === void 0 ? false : _b, _c = args.snapToRight, snapToRight = _c === void 0 ? false : _c;
        this._.table.scrollViewportTo(row, column, snapToBottom, snapToRight);
        return this;
    };
    Grid.prototype.select = function (args) {
        var totalColumns = this.totalColumns;
        var totalRows = this.totalRows;
        var table = this._.table;
        var scrollToCell = common_1.value.defaultValue(args.scrollToCell, true);
        var ranges = (args.ranges || [])
            .map(function (range) { return Cell_1.Cell.toRangePositions({ range: range, totalColumns: totalColumns, totalRows: totalRows }); })
            .map(function (_a) {
            var start = _a.start, end = _a.end;
            return [start.row, start.column, end.row, end.column];
        });
        var pos = this.toPosition(args.cell);
        var current = [pos.row, pos.column, pos.row, pos.column];
        var selection = tslib_1.__spreadArrays(ranges, [current]);
        table.selectCells(selection, scrollToCell);
        return this;
    };
    Grid.prototype.deselect = function () {
        this._.table.deselectCell();
        return this;
    };
    Grid.prototype.focus = function () {
        var last = this._.lastSelection;
        var cell = (last && last.cell) || 'A1';
        var ranges = (last && last.ranges) || [];
        this.select({ cell: cell, ranges: ranges });
        return this;
    };
    Grid.prototype.blur = function () {
        this.fire({ type: 'GRID/blur', payload: { grid: this } });
        this.deselect();
        return this;
    };
    Grid.prototype.redraw = function () {
        this._.redraw$.next();
        return this;
    };
    Grid.prototype.toPosition = function (ref) {
        var pos = Cell_1.Cell.toPosition(ref);
        var row = common_1.R.clamp(0, this.totalRows - 1, pos.row);
        var column = common_1.R.clamp(0, this.totalColumns - 1, pos.column);
        return { row: row, column: column };
    };
    Grid.prototype.updateHashes = function (options) {
        var _this = this;
        if (options === void 0) { options = {}; }
        var data = this.data;
        var cells = tslib_1.__assign({}, data.cells);
        var isChanged = false;
        Object.keys(cells).forEach(function (key) {
            var cell = cells[key];
            if (cell) {
                var hash = cell.hash;
                if (!hash || options.force) {
                    hash = common_1.util.gridCellHash(_this, key, cell);
                    cells[key] = tslib_1.__assign(tslib_1.__assign({}, cell), { hash: hash });
                    isChanged = true;
                }
            }
        });
        if (isChanged) {
            this.setCells(cells);
        }
        return this;
    };
    return Grid;
}());
exports.Grid = Grid;
