/*!
 * Built by Revolist OU ❤️
 */
import { cropCellToMax, nextCell, SelectionStore } from "../store/index";
export class SelectionStoreConnector {
    constructor() {
        this.stores = {};
        this.columnStores = {};
        this.rowStores = {};
        /**
         * Helpers for data conversion
         */
        this.storesByType = {};
        this.storesXToType = {};
        this.storesYToType = {};
    }
    get focusedStore() {
        var _a;
        for (let y in this.stores) {
            for (let x in this.stores[y]) {
                const focused = (_a = this.stores[y][x]) === null || _a === void 0 ? void 0 : _a.store.get('focus');
                if (focused) {
                    return {
                        entity: this.stores[y][x],
                        cell: focused,
                        position: {
                            x: parseInt(x, 10),
                            y: parseInt(y, 10),
                        },
                    };
                }
            }
        }
        return null;
    }
    get edit() {
        var _a;
        return (_a = this.focusedStore) === null || _a === void 0 ? void 0 : _a.entity.store.get('edit');
    }
    get focused() {
        var _a;
        return (_a = this.focusedStore) === null || _a === void 0 ? void 0 : _a.entity.store.get('focus');
    }
    get selectedRange() {
        var _a;
        return (_a = this.focusedStore) === null || _a === void 0 ? void 0 : _a.entity.store.get('range');
    }
    registerColumn(x, type) {
        this.updateColumnTypeMapping(x, type);
        if (this.columnStores[x]) {
            return this.columnStores[x];
        }
        this.columnStores[x] = new SelectionStore();
        return this.columnStores[x];
    }
    registerRow(y, type) {
        if (this.rowStores[y]) {
            return this.rowStores[y];
        }
        this.rowStores[y] = new SelectionStore();
        // build cross linking type to position
        this.storesByType[type] = y;
        this.storesYToType[y] = type;
        return this.rowStores[y];
    }
    /**
     * Cross store proxy, based on multiple dimensions
     */
    register({ x, y }) {
        if (!this.stores[y]) {
            this.stores[y] = {};
        }
        let store = this.stores[y][x];
        if (store) {
            // Store already registered. Do not register twice
            return store;
        }
        this.stores[y][x] = store = new SelectionStore();
        // proxy update, column store trigger only range area
        store.onChange('range', c => {
            this.columnStores[x].setRangeArea(c);
            this.rowStores[y].setRangeArea(c);
        });
        // clean up on remove
        store.store.on('dispose', () => this.destroy(x, y));
        return store;
    }
    destroy(x, y) {
        var _a, _b;
        (_a = this.columnStores[x]) === null || _a === void 0 ? void 0 : _a.dispose();
        (_b = this.rowStores[y]) === null || _b === void 0 ? void 0 : _b.dispose();
        delete this.rowStores[y];
        delete this.columnStores[x];
        // clear x cross-link
        if (this.storesXToType[x]) {
            const type = this.storesXToType[x];
            delete this.storesXToType[x];
            delete this.storesByType[type];
        }
        // clear y cross-link
        if (this.storesYToType[y]) {
            const type = this.storesYToType[y];
            delete this.storesYToType[y];
            delete this.storesByType[type];
        }
        if (this.stores[y]) {
            delete this.stores[y][x];
        }
        // clear empty rows
        if (!Object.keys(this.stores[y] || {}).length) {
            delete this.stores[y];
        }
    }
    setEditByCell(storePos, editCell) {
        this.focusByCell(storePos, editCell, editCell);
        this.setEdit('');
    }
    /**
     * Sets the next focus cell before the current one.
     *
     * @param focus - The cell to set as the next focus.
     */
    beforeNextFocusCell(focus) {
        var _a;
        // If there is no focused store, return early.
        if (!this.focusedStore) {
            return;
        }
        // Get the next store based on the current focus and the last cell.
        const lastCell = this.focusedStore.entity.store.get('lastCell');
        const next = lastCell && this.getNextStore(focus, this.focusedStore.position, lastCell);
        // Set the next focus cell in the store.
        (_a = next === null || next === void 0 ? void 0 : next.store) === null || _a === void 0 ? void 0 : _a.setNextFocus(Object.assign(Object.assign({}, focus), next.item));
    }
    focusByCell(storePos, start, end) {
        const store = this.stores[storePos.y][storePos.x];
        this.focus(store, { focus: start, end });
    }
    focus(store, { focus, end }) {
        const currentStorePointer = this.getCurrentStorePointer(store);
        if (!currentStorePointer) {
            return null;
        }
        // check for the focus in nearby store/viewport
        const lastCell = store.store.get('lastCell');
        const next = lastCell && this.getNextStore(focus, currentStorePointer, lastCell);
        // if next store present - update
        if (next === null || next === void 0 ? void 0 : next.store) {
            const item = Object.assign(Object.assign({}, focus), next.item);
            this.focus(next.store, { focus: item, end: item });
            return null;
        }
        if (lastCell) {
            focus = cropCellToMax(focus, lastCell);
            end = cropCellToMax(end, lastCell);
        }
        store.setFocus(focus, end);
        return focus;
    }
    /**
     * Retrieves the current store pointer based on the active store.
     * Clears focus from all stores except the active one.
     */
    getCurrentStorePointer(store) {
        let currentStorePointer;
        // Iterate through all stores
        for (let y in this.stores) {
            for (let x in this.stores[y]) {
                const s = this.stores[y][x];
                // Clear focus from stores other than the active one
                if (s !== store) {
                    s.clearFocus();
                }
                else {
                    // Update the current store pointer with the active store coordinates
                    currentStorePointer = {
                        x: parseInt(x, 10),
                        y: parseInt(y, 10)
                    };
                }
            }
        }
        return currentStorePointer;
    }
    /**
     * Retrieves the next store based on the focus cell and current store pointer.
     * If the next store exists, returns an object with the next store and the item in the new store.
     * If the next store does not exist, returns null.
     */
    getNextStore(focus, currentStorePointer, lastCell) {
        // item in new store
        const nextItem = nextCell(focus, lastCell);
        let nextStore;
        if (nextItem) {
            Object.keys(nextItem).forEach((t) => {
                var _a;
                const type = t;
                const nextItemCoord = (_a = nextItem[type]) !== null && _a !== void 0 ? _a : 0;
                let stores;
                switch (type) {
                    case 'x':
                        // Get the X stores for the current Y coordinate of the current store pointer
                        stores = this.getXStores(currentStorePointer.y);
                        break;
                    case 'y':
                        // Get the Y stores for the current X coordinate of the current store pointer
                        stores = this.getYStores(currentStorePointer.x);
                        break;
                }
                // Get the next store based on the item in the new store
                if (nextItemCoord >= 0) {
                    nextStore = stores[++currentStorePointer[type]];
                }
                else {
                    nextStore = stores[--currentStorePointer[type]];
                    const nextLastCell = nextStore === null || nextStore === void 0 ? void 0 : nextStore.store.get('lastCell');
                    if (nextLastCell) {
                        nextItem[type] = nextLastCell[type] + nextItemCoord;
                    }
                }
            });
        }
        // if last cell is empty store is empty, no next store
        const lastCellNext = nextStore === null || nextStore === void 0 ? void 0 : nextStore.store.get('lastCell');
        if (!(lastCellNext === null || lastCellNext === void 0 ? void 0 : lastCellNext.x) || !(lastCellNext === null || lastCellNext === void 0 ? void 0 : lastCellNext.y)) {
            nextStore = undefined;
        }
        return {
            store: nextStore,
            item: nextItem,
        };
    }
    clearAll() {
        var _a;
        for (let y in this.stores) {
            for (let x in this.stores[y]) {
                (_a = this.stores[y][x]) === null || _a === void 0 ? void 0 : _a.clearFocus();
            }
        }
    }
    setEdit(val) {
        if (!this.focusedStore) {
            return;
        }
        this.focusedStore.entity.setEdit(val);
    }
    /**
     * Select all cells across all stores
     */
    selectAll() {
        for (let y in this.stores) {
            for (let x in this.stores[y]) {
                const store = this.stores[y][x];
                if (!store) {
                    continue;
                }
                const lastCell = store.store.get('lastCell');
                if (lastCell) {
                    store.setRange({ x: 0, y: 0 }, { x: lastCell.x - 1, y: lastCell.y - 1 });
                }
            }
        }
    }
    getXStores(y) {
        return this.stores[y];
    }
    getYStores(x) {
        const stores = {};
        for (let i in this.stores) {
            stores[i] = this.stores[i][x];
        }
        return stores;
    }
    /**
     * Keep column viewport positions and types in sync across pin/unpin rerenders.
     * Regression case: when a selected rgCol cell was pinned left, colPinStart
     * could take over x=0 and render the stale rgCol focus store in the pinned area.
     */
    updateColumnTypeMapping(x, type) {
        const previousType = this.storesXToType[x];
        const previousX = this.storesByType[type];
        let shouldClearFocus = false;
        this.storesByType[type] = x;
        this.storesXToType[x] = type;
        if (previousType && previousType !== type) {
            shouldClearFocus = true;
            if (this.storesByType[previousType] === x) {
                delete this.storesByType[previousType];
            }
        }
        if (typeof previousX === 'number' && previousX !== x && this.storesXToType[previousX] === type) {
            delete this.storesXToType[previousX];
            shouldClearFocus = true;
        }
        if (shouldClearFocus) {
            this.clearAll();
        }
    }
}
