/*!
 * Jodit Editor (https://xdsoft.net/jodit/)
 * Released under MIT see LICENSE.txt in the project root for license information.
 * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
        r = Reflect.decorate(decorators, target, key, desc);
    else
        for (var i = decorators.length - 1; i >= 0; i--)
            if (d = decorators[i])
                r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { KEY_TAB } from "../../core/constants.js";
import { autobind, watch } from "../../core/decorators/index.js";
import { Dom } from "../../core/dom/dom.js";
import { pluginSystem } from "../../core/global.js";
import { $$, alignElement, position } from "../../core/helpers/index.js";
import { Plugin } from "../../core/plugin/index.js";
import { Table } from "../../modules/table/table.js";
import "./config.js";
const key = 'table_processor_observer';
const MOUSE_MOVE_LABEL = 'onMoveTableSelectCell';
export class selectCells extends Plugin {
    constructor() {
        super(...arguments);
        /**
         * First selected cell
         */
        this.__selectedCell = null;
        /**
         * User is selecting cells now
         */
        this.__isSelectionMode = false;
    }
    /**
     * Shortcut for Jodit.modules.Table
     */
    get __tableModule() {
        return this.j.getInstance(Table, this.j.o);
    }
    afterInit(jodit) {
        if (!jodit.o.tableAllowCellSelection) {
            return;
        }
        jodit.e
            .on('keydown.select-cells', (event) => {
            if (event.key === KEY_TAB) {
                this.unselectCells();
            }
        })
            .on('beforeCommand.select-cells', this.onExecCommand)
            .on('afterCommand.select-cells', this.onAfterCommand)
            // see `plugins/select.ts`
            .on([
            'clickEditor',
            'mousedownTd',
            'mousedownTh',
            'touchstartTd',
            'touchstartTh'
        ]
            .map(e => e + '.select-cells')
            .join(' '), this.onStartSelection)
            // For `clickEditor` correct working. Because `mousedown` on first cell
            // and mouseup on another cell call `click` only for `TR` element.
            .on('clickTr clickTbody', () => {
            var _a;
            const cellsCount = this.__tableModule.getAllSelectedCells().length;
            if (cellsCount) {
                if (cellsCount > 1) {
                    (_a = this.j.s.sel) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
                }
                return false;
            }
        });
    }
    /**
     * Mouse click inside the table
     */
    onStartSelection(cell) {
        if (this.j.o.readonly) {
            return;
        }
        this.unselectCells();
        if (cell === this.j.editor) {
            return;
        }
        const table = Dom.closest(cell, 'table', this.j.editor);
        if (!cell || !table) {
            return;
        }
        if (!cell.firstChild) {
            cell.appendChild(this.j.createInside.element('br'));
        }
        this.__isSelectionMode = true;
        this.__selectedCell = cell;
        this.__tableModule.addSelection(cell);
        this.j.e
            .on(table, 'mousemove.select-cells touchmove.select-cells', 
        // Don't use decorator because need clear label on mouseup
        this.j.async.throttle(this.__onMove.bind(this, table), {
            label: MOUSE_MOVE_LABEL,
            timeout: this.j.defaultTimeout / 2
        }))
            .on(table, 'mouseup.select-cells touchend.select-cells', this.__onStopSelection.bind(this, table));
        return false;
    }
    onOutsideClick() {
        this.__selectedCell = null;
        this.__onRemoveSelection();
    }
    onChange() {
        if (!this.j.isLocked && !this.__isSelectionMode) {
            this.__onRemoveSelection();
        }
    }
    /**
     * Mouse move inside the table
     */
    __onMove(table, e) {
        var _a;
        if (this.j.o.readonly && !this.j.isLocked) {
            return;
        }
        if (this.j.isLockedNotBy(key)) {
            return;
        }
        const node = this.j.ed.elementFromPoint(e.clientX, e.clientY);
        if (!node) {
            return;
        }
        const cell = Dom.closest(node, ['td', 'th'], table);
        if (!cell || !this.__selectedCell) {
            return;
        }
        if (cell !== this.__selectedCell) {
            this.j.lock(key);
        }
        this.unselectCells();
        const bound = this.__tableModule.getSelectedBound(table, [
            cell,
            this.__selectedCell
        ]), box = this.__tableModule.formalMatrix(table);
        for (let i = bound[0][0]; i <= bound[1][0]; i += 1) {
            for (let j = bound[0][1]; j <= bound[1][1]; j += 1) {
                this.__tableModule.addSelection(box[i][j]);
            }
        }
        const cellsCount = this.__tableModule.getAllSelectedCells().length;
        if (cellsCount > 1) {
            (_a = this.j.s.sel) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
        }
        this.j.e.fire('hidePopup');
        e.stopPropagation();
        // Hack for FireFox for force redraw selection
        (() => {
            const n = this.j.createInside.fromHTML('<div style="color:rgba(0,0,0,0.01);width:0;height:0">&nbsp;</div>');
            cell.appendChild(n);
            this.j.async.setTimeout(() => {
                var _a;
                (_a = n.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(n);
            }, this.j.defaultTimeout / 5);
        })();
    }
    /**
     * On click in outside - remove selection
     */
    __onRemoveSelection(e) {
        var _a;
        if (!((_a = e === null || e === void 0 ? void 0 : e.buffer) === null || _a === void 0 ? void 0 : _a.actionTrigger) &&
            !this.__selectedCell &&
            this.__tableModule.getAllSelectedCells().length) {
            this.j.unlock();
            this.unselectCells();
            this.j.e.fire('hidePopup', 'cells');
            return;
        }
        this.__isSelectionMode = false;
        this.__selectedCell = null;
    }
    /**
     * Stop a selection process
     */
    __onStopSelection(table, e) {
        var _a, _b;
        if (!this.__selectedCell) {
            return;
        }
        this.__isSelectionMode = false;
        this.j.unlock();
        const node = this.j.ed.elementFromPoint(e.clientX, e.clientY);
        if (!node) {
            return;
        }
        const cell = Dom.closest(node, ['td', 'th'], table);
        if (!cell) {
            return;
        }
        const ownTable = Dom.closest(cell, 'table', table);
        if (ownTable && ownTable !== table) {
            return; // Nested tables
        }
        const bound = this.__tableModule.getSelectedBound(table, [
            cell,
            this.__selectedCell
        ]), box = this.__tableModule.formalMatrix(table);
        const max = (_a = box[bound[1][0]]) === null || _a === void 0 ? void 0 : _a[bound[1][1]], min = (_b = box[bound[0][0]]) === null || _b === void 0 ? void 0 : _b[bound[0][1]];
        // `getSelectedBound` keeps its `Infinity` sentinel when none of the
        // selected cells belong to this table's matrix — e.g. after a
        // drag-and-drop that moved/removed the cells, leaving a stale anchor and
        // a drop target outside the table. Bail out instead of dereferencing an
        // out-of-range matrix slot (which threw `Cannot read properties of
        // undefined`).
        if (!min || !max) {
            return;
        }
        this.j.e.fire('showPopup', table, () => {
            const minOffset = position(min, this.j), maxOffset = position(max, this.j);
            return {
                left: minOffset.left,
                top: minOffset.top,
                width: maxOffset.left - minOffset.left + maxOffset.width,
                height: maxOffset.top - minOffset.top + maxOffset.height
            };
        }, 'cells');
        $$('table', this.j.editor).forEach(table => {
            this.j.e.off(table, 'mousemove.select-cells touchmove.select-cells mouseup.select-cells touchend.select-cells');
        });
        this.j.async.clearTimeout(MOUSE_MOVE_LABEL);
    }
    /**
     * Remove selection for all cells
     */
    unselectCells(currentCell) {
        const module = this.__tableModule;
        const cells = module.getAllSelectedCells();
        if (cells.length) {
            cells.forEach(cell => {
                if (!currentCell || currentCell !== cell) {
                    module.removeSelection(cell);
                }
            });
        }
    }
    /**
     * Execute custom commands for table
     */
    onExecCommand(command) {
        if (/table(splitv|splitg|merge|empty|bin|binrow|bincolumn|addcolumn|addrow)/.test(command)) {
            command = command.replace('table', '');
            const cells = this.__tableModule.getAllSelectedCells();
            if (cells.length) {
                const [cell] = cells;
                if (!cell) {
                    return;
                }
                const table = Dom.closest(cell, 'table', this.j.editor);
                if (!table) {
                    return;
                }
                switch (command) {
                    case 'splitv':
                        this.__tableModule.splitVertical(table);
                        break;
                    case 'splitg':
                        this.__tableModule.splitHorizontal(table);
                        break;
                    case 'merge':
                        this.__tableModule.mergeSelected(table);
                        break;
                    case 'empty':
                        cells.forEach(td => Dom.detach(td));
                        break;
                    case 'bin':
                        Dom.safeRemove(table);
                        break;
                    case 'binrow':
                        new Set(cells.map(td => td.parentNode)).forEach(row => {
                            this.__tableModule.removeRow(table, row.rowIndex);
                        });
                        break;
                    case 'bincolumn':
                        {
                            const columnsSet = new Set();
                            const columns = [];
                            cells.forEach(td => {
                                const [, col] = this.__tableModule.formalCoordinate(table, td);
                                if (!columnsSet.has(col)) {
                                    columns.push(col);
                                    columnsSet.add(col);
                                }
                            });
                            columns
                                .sort((a, b) => b - a)
                                .forEach(col => {
                                this.__tableModule.removeColumn(table, col);
                            });
                        }
                        break;
                    case 'addcolumnafter':
                    case 'addcolumnbefore':
                        this.__tableModule.appendColumn(table, cell, command === 'addcolumnafter');
                        break;
                    case 'addrowafter':
                    case 'addrowbefore':
                        this.__tableModule.appendRow(table, cell.parentNode, command === 'addrowafter');
                        break;
                }
            }
            return false;
        }
    }
    /**
     * Add some align after native command
     */
    onAfterCommand(command) {
        if (/^justify/.test(command)) {
            this.__tableModule
                .getAllSelectedCells()
                .forEach(elm => alignElement(command, elm));
        }
    }
    /** @override */
    beforeDestruct(jodit) {
        this.__onRemoveSelection();
        jodit.e.off('.select-cells');
    }
}
selectCells.requires = ['select'];
__decorate([
    autobind
], selectCells.prototype, "onStartSelection", null);
__decorate([
    watch(':outsideClick')
], selectCells.prototype, "onOutsideClick", null);
__decorate([
    watch(':change')
], selectCells.prototype, "onChange", null);
__decorate([
    autobind
], selectCells.prototype, "__onRemoveSelection", null);
__decorate([
    autobind
], selectCells.prototype, "__onStopSelection", null);
__decorate([
    autobind
], selectCells.prototype, "onExecCommand", null);
__decorate([
    autobind
], selectCells.prototype, "onAfterCommand", null);
pluginSystem.add('selectCells', selectCells);
