/* *
 *
 *  Grid Cell abstract class
 *
 *  (c) 2020-2026 Highsoft AS
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 *  Authors:
 *  - Dawid Draguła
 *  - Sebastian Bochan
 *
 * */
'use strict';
import Globals from '../Globals.js';
import Templating from '../../../Core/Templating.js';
import { fireEvent } from '../../../Shared/Utilities.js';
import { applyUserClassNames } from '../GridUtils.js';
/* *
 *
 *  Abstract Class of Cell
 *
 * */
class Cell {
    /* *
    *
    *  Constructor
    *
    * */
    /**
     * Constructs a cell in the data grid.
     *
     * @param row
     * The row of the cell.
     *
     * @param column
     * The column of the cell.
     */
    constructor(row, column) {
        /**
         * Array of cell events to be removed when the cell is destroyed.
         */
        this.cellEvents = [];
        this.column = column;
        this.row = row;
        this.row.registerCell(this);
        this.htmlElement = this.init();
        this.htmlElement.setAttribute('tabindex', '-1');
        if (!this.column ||
            !this.column.viewport.grid.columnPolicy.isColumnEditable(this.column.id)) {
            this.htmlElement.setAttribute('aria-readonly', 'true');
        }
        this.initEvents();
    }
    /* *
    *
    *  Methods
    *
    * */
    /**
     * Init element.
     * @internal
     */
    init() {
        const isRowHeader = !!this.column?.options.cells?.rowHeader;
        const cell = document.createElement(isRowHeader ? 'th' : 'td', {});
        cell.classList.add(Globals.getClassName('cell'));
        if (isRowHeader) {
            cell.setAttribute('scope', 'row');
            cell.setAttribute('role', 'rowheader');
        }
        else {
            cell.setAttribute('role', 'gridcell');
        }
        return cell;
    }
    /**
     * Initialize event listeners. Events added to the `cellEvents` array will
     * be registered now and unregistered when the cell is destroyed.
     */
    initEvents() {
        this.cellEvents.push(['blur', () => this.onBlur()]);
        this.cellEvents.push(['focus', () => this.onFocus()]);
        this.cellEvents.push(['click', (e) => {
                this.onClick(e);
            }]);
        this.cellEvents.push(['keydown', (e) => {
                this.onKeyDown(e);
            }]);
        this.cellEvents.push(['mouseout', () => {
                this.onMouseOut();
            }]);
        this.cellEvents.push(['mouseover', () => {
                this.onMouseOver();
            }]);
        this.cellEvents.forEach((pair) => {
            this.htmlElement.addEventListener(pair[0], pair[1]);
        });
    }
    /**
     * Handles the focus event on the cell.
     */
    onFocus() {
        this.row.viewport.setFocusAnchorCell(this);
    }
    /**
     * Handles the blur event on the cell.
     */
    onBlur() {
        const vp = this.row.viewport;
        if (!vp.focusCursor?.detached) {
            delete vp.focusCursor;
        }
    }
    /**
     * Handles user keydown on the cell.
     *
     * @param e
     * Keyboard event object.
     *
     * @internal
     */
    onKeyDown(e) {
        const { row, column } = this;
        if (!column) {
            return;
        }
        const vp = row.viewport;
        const { header } = vp;
        const getVerticalPos = () => {
            if (row.index !== void 0) {
                const renderedRowIndex = vp.getRenderedRows()
                    .indexOf(row);
                if (renderedRowIndex !== -1) {
                    return renderedRowIndex;
                }
                return row.index - (vp.rows[0]?.index ?? 0);
            }
            const level = row.level;
            if (!header || level === void 0) {
                return 0;
            }
            return Math.max(level, header.levels) - header.rows.length - 1;
        };
        const changeFocusKeys = {
            ArrowDown: [1, 0],
            ArrowUp: [-1, 0],
            ArrowLeft: [0, -1],
            ArrowRight: [0, 1]
        };
        const dir = changeFocusKeys[e.key];
        if (e.key === 'Enter') {
            this.onClick(e);
        }
        if (dir) {
            e.preventDefault();
            e.stopPropagation();
            const { header } = vp;
            const localRowIndex = getVerticalPos();
            const nextVerticalDir = localRowIndex + dir[0];
            const nextColumnIndex = column.index + dir[1];
            const focusCell = (cell) => {
                cell.htmlElement.focus({
                    preventScroll: true
                });
                vp.ensureColumnFullyVisible(nextColumnIndex);
                if (cell.row.index !== void 0) {
                    vp.ensureRowFullyVisible(cell.row);
                }
            };
            if (nextVerticalDir < 0 && header) {
                const extraRowIdx = header.rows.length + nextVerticalDir;
                const nextCell = extraRowIdx + 1 > header.levels ? (header.rows[extraRowIdx]
                    ?.getCellByColumnIndex(nextColumnIndex)) : (vp.getColumnByIndex(nextColumnIndex)?.header);
                if (nextCell) {
                    focusCell(nextCell);
                }
                return;
            }
            const nextRow = vp.getRenderedRows()[nextVerticalDir];
            if (nextRow) {
                const nextCell = nextRow.getCellByColumnIndex(nextColumnIndex);
                if (nextCell) {
                    focusCell(nextCell);
                }
                else if (nextRow.index !== void 0) {
                    vp.focusCellByRowIndex(nextRow.index, nextColumnIndex);
                }
            }
        }
    }
    /**
     * Handles the mouse over event on the cell.
     * @internal
     */
    onMouseOver() {
        const { grid } = this.row.viewport;
        grid.hoverColumn(this.column?.id);
        fireEvent(this, 'mouseOver', {
            target: this
        });
    }
    /**
     * Handles the mouse out event on the cell.
     * @internal
     */
    onMouseOut() {
        const { grid } = this.row.viewport;
        grid.hoverColumn();
        fireEvent(this, 'mouseOut', {
            target: this
        });
    }
    /**
     * Renders the cell by appending the HTML element to the row.
     */
    async render() {
        this.row.htmlElement.appendChild(this.htmlElement);
        this.reflow();
        return Promise.resolve();
    }
    /**
     * Reflows the cell dimensions.
     */
    reflow() {
        const column = this.column;
        if (!column) {
            return;
        }
        const elementStyle = this.htmlElement.style;
        elementStyle.width = elementStyle.maxWidth = column.getWidth() + 'px';
    }
    /**
     * Returns the formatted string where the templating context is the cell.
     *
     * @param template
     * The template string.
     *
     * @return
     * The formatted string.
     */
    format(template) {
        return Templating.format(template, this, this.row.viewport.grid);
    }
    /**
     * Sets the custom class name of the cell based on the template.
     *
     * @param template
     * The template string.
     */
    setCustomClassName(template) {
        if (!template) {
            this.customClassName = applyUserClassNames(this.htmlElement, this.customClassName);
            return;
        }
        const newClassName = this.format(template);
        this.customClassName = applyUserClassNames(this.htmlElement, this.customClassName, newClassName || void 0);
    }
    /**
     * Sets custom inline styles from options and removes the previously applied
     * custom styles to keep updates deterministic.
     *
     * @param styles
     * A style object to apply.
     */
    setCustomStyles(styles) {
        const elementStyle = this.htmlElement.style;
        const getCSSPropertyName = (property) => (property.indexOf('-') > -1 ?
            property :
            property.replace(/[A-Z]/g, '-$&').toLowerCase());
        if (this.customStyleProperties) {
            for (const property of this.customStyleProperties) {
                elementStyle.removeProperty(property);
            }
        }
        if (!styles) {
            delete this.customStyleProperties;
            return;
        }
        const appliedProperties = [];
        for (const key of Object.keys(styles)) {
            const value = styles[key];
            if (value === void 0 || value === null) {
                continue;
            }
            const property = getCSSPropertyName(String(key));
            elementStyle.setProperty(property, String(value));
            appliedProperties.push(property);
        }
        this.customStyleProperties = appliedProperties;
    }
    /**
     * Destroys the cell.
     */
    destroy() {
        this.cellEvents.forEach((pair) => {
            this.htmlElement.removeEventListener(pair[0], pair[1]);
        });
        this.column?.unregisterCell(this);
        this.row.unregisterCell(this);
        this.htmlElement.remove();
    }
}
/* *
 *
 *  Default Export
 *
 * */
export default Cell;
