"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SlickRowBasedEdit = exports.BTN_ACTION_CANCEL = exports.BTN_ACTION_UPDATE = exports.BTN_ACTION_EDIT = exports.BTN_ACTION_DELETE = exports.ROW_BASED_EDIT_UNSAVED_HIGHLIGHT_PREFIX = exports.ROW_BASED_EDIT_UNSAVED_CELL = exports.ROW_BASED_EDIT_ROW_HIGHLIGHT_CLASS = void 0;
const utils_1 = require("@slickgrid-universal/utils");
const index_1 = require("../core/index");
exports.ROW_BASED_EDIT_ROW_HIGHLIGHT_CLASS = 'slick-rbe-editmode';
exports.ROW_BASED_EDIT_UNSAVED_CELL = 'slick-rbe-unsaved-cell';
exports.ROW_BASED_EDIT_UNSAVED_HIGHLIGHT_PREFIX = 'slick-rbe-unsaved-highlight';
exports.BTN_ACTION_DELETE = 'action-btns--delete';
exports.BTN_ACTION_EDIT = 'action-btns--edit';
exports.BTN_ACTION_UPDATE = 'action-btns--update';
exports.BTN_ACTION_CANCEL = 'action-btns--cancel';
/**
 * Row based edit plugin to add edit/delete buttons to each row and only allow editing rows currently in editmode
 */
class SlickRowBasedEdit {
    /** Constructor of the SlickGrid 3rd party plugin, it can optionally receive options */
    constructor(extensionUtility, pubSubService, options) {
        this.extensionUtility = extensionUtility;
        this.pubSubService = pubSubService;
        this.pluginName = 'RowBasedEdit';
        this._defaults = {
            actionsColumnLabel: 'Actions',
            allowMultipleRows: false,
            columnId: '_slick_rowbasededit_action',
            columnIndexPosition: -1,
            reorderable: false,
        };
        this._editedRows = new Map();
        this._currentLang = 'en';
        this._translations = {};
        this.onBeforeEditCellHandler = (_e, args) => {
            return this._editedRows.has(args.item?.[this.gridOptions.datasetIdPropertyName ?? 'id']);
        };
        this._eventHandler = new index_1.SlickEventHandler();
        this._addonOptions = options;
    }
    get addonOptions() {
        return this._addonOptions;
    }
    get gridOptions() {
        return this._grid.getOptions() || {};
    }
    get eventHandler() {
        return this._eventHandler;
    }
    /** Initialize plugin. */
    init(grid, gridService) {
        this._grid = grid;
        this._gridService = gridService;
        this._addonOptions = { ...this._defaults, ...this.addonOptions };
        const dataView = this._grid.getData();
        this._eventHandler.subscribe(this._grid.onBeforeEditCell, this.onBeforeEditCellHandler);
        this.checkOptionsRequirements(this.gridOptions);
        if (!this.gridOptions.autoEdit) {
            this._grid.setOptions({ autoEdit: true });
            console.warn('[Slickgrid-Universal] The Row Based Edit Plugin works best with the gridOption "autoEdit" enabled, the option has now been set automatically for you.');
        }
        this._existingEditCommandHandler = this.gridOptions.editCommandHandler;
        this._grid.setOptions({
            editCommandHandler: this.rowBasedEditCommandHandler.bind(this),
        });
        if (this.gridOptions.enableExcelCopyBuffer === true) {
            const existingBeforePasteCellHandler = this.gridOptions.excelCopyBufferOptions?.onBeforePasteCell;
            this._grid.setOptions({
                excelCopyBufferOptions: {
                    ...this.gridOptions.excelCopyBufferOptions,
                    onBeforePasteCell: (e, args) => {
                        let userResult = true;
                        if (existingBeforePasteCellHandler) {
                            userResult = existingBeforePasteCellHandler(e, args);
                            if (userResult === false) {
                                return false;
                            }
                        }
                        const item = dataView.getItem(args.row);
                        const idProperty = this.gridOptions.datasetIdPropertyName ?? 'id';
                        if (this._editedRows.has(item[idProperty]) && userResult === true) {
                            return true;
                        }
                        return false;
                    },
                },
            });
        }
        const originalGetItemMetadata = dataView.getItemMetadata;
        dataView.getItemMetadata = this.updateItemMetadata(originalGetItemMetadata?.bind?.(dataView));
        this._eventHandler.subscribe(this._grid.onSetOptions, this.optionsUpdatedHandler.bind(this));
        this._eventHandler.subscribe(dataView.onRowsOrCountChanged, this.handleAllRowRerender.bind(this));
        this.translate();
    }
    destroy() {
        this.dispose();
    }
    /** Dispose (destroy) the SlickGrid 3rd party plugin */
    dispose() {
        this._eventHandler?.unsubscribeAll();
        this.pubSubService?.unsubscribeAll();
    }
    create(columnDefinitions, gridOptions) {
        this._addonOptions = {
            ...this._defaults,
            ...gridOptions.rowBasedEditOptions,
        };
        if (Array.isArray(columnDefinitions) && gridOptions) {
            const selectionColumn = this.getColumnDefinition();
            // add new action column unless it was already added
            if (!columnDefinitions.some((col) => col.id === selectionColumn.id)) {
                // column index position in the grid
                const columnPosition = gridOptions?.rowBasedEditOptions?.columnIndexPosition ?? -1;
                if (columnPosition === -1) {
                    columnDefinitions.push(selectionColumn);
                }
                else if (columnPosition > 0 && columnPosition < columnDefinitions.length) {
                    columnDefinitions.splice(columnPosition, 0, selectionColumn);
                }
                else {
                    columnDefinitions.unshift(selectionColumn);
                }
                this.pubSubService.publish(`onPluginColumnsChanged`, {
                    columns: columnDefinitions,
                    pluginName: this.pluginName,
                });
            }
        }
        return this;
    }
    getColumnDefinition() {
        const columnId = String(this._addonOptions?.columnId ?? this._defaults.columnId);
        return {
            id: columnId,
            name: this._addonOptions?.actionsColumnLabel,
            field: 'action',
            minWidth: 70,
            width: 75,
            maxWidth: 75,
            excludeFromExport: true,
            reorderable: this._addonOptions?.reorderable,
            formatter: this.actionColumnFormatter.bind(this),
            onCellClick: this.onCellClickHandler.bind(this),
            ...(this._addonOptions?.actionColumnConfig ?? {}),
        };
    }
    rowBasedEditCommandHandler(item, column, editCommand) {
        if (this._existingEditCommandHandler) {
            this._existingEditCommandHandler(item, column, editCommand);
        }
        const prevSerializedValues = Array.isArray(editCommand.prevSerializedValue)
            ? editCommand.prevSerializedValue
            : [editCommand.prevSerializedValue];
        const serializedValues = Array.isArray(editCommand.serializedValue)
            ? editCommand.serializedValue
            : [editCommand.serializedValue];
        const editorColumns = this._gridService?.getAllColumnDefinitions().filter((col) => col.editor !== undefined);
        const modifiedColumns = [];
        const idProperty = this.gridOptions.datasetIdPropertyName ?? 'id';
        prevSerializedValues.forEach((_val, index) => {
            const prevSerializedValue = prevSerializedValues[index];
            const serializedValue = serializedValues[index];
            if (prevSerializedValue !== serializedValue || serializedValue === '') {
                /* istanbul ignore next */
                const finalColumn = Array.isArray(editCommand.prevSerializedValue) ? editorColumns?.[index] : column;
                if (!finalColumn) {
                    return;
                }
                this._grid.invalidate();
                editCommand.execute();
                this.renderUnsavedCellStyling(item[idProperty], finalColumn);
                modifiedColumns.push(finalColumn);
            }
        });
        const editedRow = this._editedRows.get(item[idProperty]);
        const newCommands = [...(editedRow?.editCommands || [])];
        if (modifiedColumns.length > 0) {
            newCommands.push(editCommand);
        }
        this._editedRows.set(item[idProperty], {
            columns: [...(editedRow?.columns || []), ...modifiedColumns],
            editCommands: newCommands,
            cssStyleKeys: editedRow?.cssStyleKeys || [],
        });
    }
    /**
     * Translate with current locale when `enableTranslate` is set or use title texts provided by user.
     * We want to translate only once to avoid having to translate on each dataset item, we'll just reuse the translations if it was already translated earlier.
     * User could optionally force a retranslate even if it was already translated
     * @param {Boolean} [forceRetranslate] - even if it was already translate, force a retranslate
     */
    translate(forceRetranslate = false) {
        this._currentLang = this.extensionUtility.translaterService?.getCurrentLanguage() ?? 'en';
        // translate only once or reuse what's in memory if it was translated
        if (!this._translations[this._currentLang] || forceRetranslate) {
            this._translations[this._currentLang] = {
                btnUpdateTitle: this.getTitleOrDefault('updateButtonTitle', 'Update the row'),
                btnEditTitle: this.getTitleOrDefault('editButtonTitle', 'Edit the Row'),
                btnDeleteTitle: this.getTitleOrDefault('deleteButtonTitle', 'Delete the Row'),
                btnCancelTitle: this.getTitleOrDefault('cancelButtonTitle', 'Cancel changes of the Row'),
            };
        }
        return this._translations[this._currentLang];
    }
    checkOptionsRequirements(options) {
        if (!options?.enableCellNavigation) {
            throw new Error(`[Slickgrid-Universal] Row Based Edit Plugin requires the gridOption cell navigation (enableCellNavigation = true)`);
        }
        if (!options?.editable) {
            throw new Error(`[Slickgrid-Universal] Row Based Edit Plugin requires the gridOption editable (editable = true)`);
        }
    }
    undoRowEdit(item) {
        const idProperty = this.gridOptions.datasetIdPropertyName ?? 'id';
        const targetRow = this._editedRows.get(item[idProperty]);
        const row = this._grid.getData().getRowByItem(item);
        if ((row !== undefined && targetRow?.editCommands && targetRow.editCommands.length) ||
            /* istanbul ignore next */
            index_1.SlickGlobalEditorLock.cancelCurrentEdit()) {
            while (targetRow.editCommands.length > 0) {
                const lastEdit = targetRow.editCommands.pop();
                if (lastEdit) {
                    lastEdit.undo();
                }
            }
            targetRow.columns.forEach((column) => {
                this.removeUnsavedStylingFromCell(column, row);
            });
            targetRow.columns = [];
            this._grid.invalidate();
        }
    }
    renderUnsavedCellStyling(id, column) {
        if (column) {
            const row = this._grid.getData()?.getRowById(id);
            if (row !== undefined && row >= 0) {
                const hash = { [row]: { [column.id]: exports.ROW_BASED_EDIT_UNSAVED_CELL } };
                const cssStyleKey = `${exports.ROW_BASED_EDIT_UNSAVED_HIGHLIGHT_PREFIX}_${[column.id]}${row}`;
                this._grid.setCellCssStyles(cssStyleKey, hash);
                this._editedRows.get(id)?.cssStyleKeys.push(cssStyleKey);
            }
        }
    }
    handleAllRowRerender(_e, _args) {
        this._editedRows.forEach((editedRow, key) => {
            editedRow.cssStyleKeys.forEach((cssStyleKey) => {
                this._grid.removeCellCssStyles(cssStyleKey);
            });
            editedRow.cssStyleKeys = [];
            editedRow.columns.forEach((column) => {
                this.renderUnsavedCellStyling(key, column);
            });
        });
    }
    removeUnsavedStylingFromCell(column, row) {
        const cssStyleKey = `${exports.ROW_BASED_EDIT_UNSAVED_HIGHLIGHT_PREFIX}_${[column.id]}${row}`;
        this._grid.removeCellCssStyles(cssStyleKey);
    }
    removeUnsavedStylingFromRow(row) {
        this._grid.getColumns().forEach((column) => {
            this.removeUnsavedStylingFromCell(column, row);
        });
    }
    optionsUpdatedHandler(_e, args) {
        this._addonOptions = {
            ...this._defaults,
            ...args.optionsAfter.rowBasedEditOptions,
        };
    }
    async onCellClickHandler(event, args) {
        const dataContext = args.dataContext;
        const target = event.target;
        const idProperty = this.gridOptions.datasetIdPropertyName ?? 'id';
        const targetRow = this._editedRows.get(dataContext[idProperty]);
        if ((target.classList.contains(exports.BTN_ACTION_DELETE) || target.parentElement?.classList.contains(exports.BTN_ACTION_DELETE)) &&
            this._gridService) {
            if (this._addonOptions?.actionButtons?.deleteButtonPrompt &&
                !window.confirm(this._addonOptions.actionButtons.deleteButtonPrompt)) {
                return;
            }
            this.toggleEditmode(dataContext, false);
            this._gridService.deleteItem(dataContext);
        }
        else if (target.classList.contains(exports.BTN_ACTION_EDIT) ||
            target.parentElement?.classList.contains(exports.BTN_ACTION_EDIT)) {
            if (!this._addonOptions?.allowMultipleRows && this._editedRows.size > 0) {
                return;
            }
            if (typeof this._addonOptions?.onBeforeEditMode === 'function') {
                this._addonOptions.onBeforeEditMode(args);
            }
            this.toggleEditmode(dataContext, true);
        }
        else if (target.classList.contains(exports.BTN_ACTION_UPDATE) ||
            target.parentElement?.classList.contains(exports.BTN_ACTION_UPDATE)) {
            if (this._addonOptions?.actionButtons?.updateButtonPrompt &&
                (targetRow?.editCommands.length || 0) > 0 &&
                !window.confirm(this._addonOptions.actionButtons.updateButtonPrompt)) {
                return;
            }
            if (this._grid.getCellEditor() && this._grid.getActiveCell()?.row === args.row) {
                this._grid.getEditController()?.commitCurrentEdit();
            }
            if (this._addonOptions?.onBeforeRowUpdated) {
                const result = await this._addonOptions.onBeforeRowUpdated(args);
                if (result !== true) {
                    return;
                }
            }
            this.removeUnsavedStylingFromRow(args.row);
            this.toggleEditmode(dataContext, false);
        }
        else if (target.classList.contains(exports.BTN_ACTION_CANCEL) ||
            target.parentElement?.classList.contains(exports.BTN_ACTION_CANCEL)) {
            if (this._addonOptions?.actionButtons?.cancelButtonPrompt &&
                (targetRow?.editCommands.length || 0) > 0 &&
                !window.confirm(this._addonOptions.actionButtons.cancelButtonPrompt)) {
                return;
            }
            this.undoRowEdit(dataContext);
            this.toggleEditmode(dataContext, false);
        }
    }
    actionColumnFormatter(_row, _cell, _value, _columnDef, dataContext) {
        const options = this.gridOptions;
        const isInEditMode = this._editedRows.has(dataContext?.[options.datasetIdPropertyName ?? 'id']);
        const buttonTitles = this._translations[this._currentLang] ?? this.translate();
        const actionFragment = document.createDocumentFragment();
        actionFragment
            .appendChild((0, utils_1.createDomElement)('span', {
            className: `${options.rowBasedEditOptions?.actionButtons?.editButtonClassName || 'button-style padding-1px mr-2'} action-btns ` + exports.BTN_ACTION_EDIT,
            title: buttonTitles.btnEditTitle,
            style: { display: isInEditMode ? 'none' : '' },
        }))
            .appendChild((0, utils_1.createDomElement)('span', {
            className: options.rowBasedEditOptions?.actionButtons?.iconEditButtonClassName || 'mdi mdi-table-edit text-color-primary',
        }));
        actionFragment
            .appendChild((0, utils_1.createDomElement)('span', {
            className: `${options.rowBasedEditOptions?.actionButtons?.deleteButtonClassName || 'button-style padding-1px'} action-btns ` + exports.BTN_ACTION_DELETE,
            title: buttonTitles.btnDeleteTitle,
            style: { display: isInEditMode ? 'none' : '' },
        }))
            .appendChild((0, utils_1.createDomElement)('span', {
            className: options.rowBasedEditOptions?.actionButtons?.iconDeleteButtonClassName || 'mdi mdi-close text-color-danger',
        }));
        actionFragment
            .appendChild((0, utils_1.createDomElement)('span', {
            className: `${options.rowBasedEditOptions?.actionButtons?.updateButtonClassName || 'button-style padding-1px mr-2'} action-btns ` + exports.BTN_ACTION_UPDATE,
            title: buttonTitles.btnUpdateTitle,
            style: { display: !isInEditMode ? 'none' : '' },
        }))
            .appendChild((0, utils_1.createDomElement)('span', {
            className: options.rowBasedEditOptions?.actionButtons?.iconUpdateButtonClassName || 'mdi mdi-check-bold text-color-success',
        }));
        actionFragment
            .appendChild((0, utils_1.createDomElement)('span', {
            className: `${options.rowBasedEditOptions?.actionButtons?.cancelButtonClassName || 'button-style padding-1px'} action-btns ` + exports.BTN_ACTION_CANCEL,
            title: buttonTitles.btnCancelTitle,
            style: { display: !isInEditMode ? 'none' : '' },
        }))
            .appendChild((0, utils_1.createDomElement)('span', {
            className: options.rowBasedEditOptions?.actionButtons?.iconCancelButtonClassName || 'mdi mdi-cancel text-color-danger',
        }));
        return actionFragment;
    }
    toggleEditmode(dataContext, editMode) {
        const idProperty = this.gridOptions.datasetIdPropertyName ?? 'id';
        if (editMode) {
            this._editedRows.set(dataContext[idProperty], {
                columns: [],
                editCommands: [],
                cssStyleKeys: [],
            });
        }
        else {
            this._editedRows.delete(dataContext[idProperty]);
        }
        this._grid.invalidate();
    }
    updateItemMetadata(previousItemMetadata) {
        return (rowNumber) => {
            const item = this._grid.getData().getItem(rowNumber);
            let meta = {
                cssClasses: '',
            };
            if (typeof previousItemMetadata === 'function') {
                const previousMeta = previousItemMetadata(rowNumber);
                if (previousMeta) {
                    meta = previousMeta;
                }
            }
            if (meta && item) {
                const idProperty = this.gridOptions.datasetIdPropertyName ?? 'id';
                if (this._editedRows.has(item[idProperty]) && !meta.cssClasses.includes(exports.ROW_BASED_EDIT_ROW_HIGHLIGHT_CLASS)) {
                    meta.cssClasses = (meta.cssClasses || '') + ' ' + exports.ROW_BASED_EDIT_ROW_HIGHLIGHT_CLASS;
                }
                else if (!this._editedRows.has(item[idProperty]) &&
                    meta.cssClasses.includes(exports.ROW_BASED_EDIT_ROW_HIGHLIGHT_CLASS)) {
                    meta.cssClasses = meta.cssClasses.replace(exports.ROW_BASED_EDIT_ROW_HIGHLIGHT_CLASS, '');
                }
            }
            return meta;
        };
    }
    /**
     * Get button tooltip text 1 of these 3 options:
     * 1. from `btnAbcTitleKey` when `enableTranslate` is set
     * 2. from `btnAbcTitle` when no title Key is found and/or `enableTranslate` is disabled
     * 3. or finally use default title fallback provided
     * @param key - button title key
     * @param defaultTitle - fallback title
     * @returns - final tooltip title text
     */
    getTitleOrDefault(key, defaultTitle) {
        const actionBtnOptions = this.gridOptions.rowBasedEditOptions?.actionButtons;
        return ((actionBtnOptions?.[(key + 'Key')] &&
            this.extensionUtility.translaterService?.translate?.(actionBtnOptions?.[(key + 'Key')] || '')) ||
            actionBtnOptions?.[key] ||
            defaultTitle);
    }
}
exports.SlickRowBasedEdit = SlickRowBasedEdit;
//# sourceMappingURL=slickRowBasedEdit.js.map