"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputFilter = void 0;
const binding_1 = require("@slickgrid-universal/binding");
const utils_1 = require("@slickgrid-universal/utils");
const index_js_1 = require("../enums/index.js");
const filterUtilities_js_1 = require("./filterUtilities.js");
const index_js_2 = require("../services/index.js");
class InputFilter {
    constructor(translaterService) {
        this.translaterService = translaterService;
        this._debounceTypingDelay = 0;
        this._shouldTriggerQuery = true;
        this._inputType = 'text';
        this.inputFilterType = 'single';
        this.searchTerms = [];
        this._bindEventService = new binding_1.BindingEventService();
    }
    /** Getter for the Column Filter */
    get columnFilter() {
        return this.columnDef?.filter ?? {};
    }
    /** Getter to know what would be the default operator when none is specified */
    get defaultOperator() {
        return index_js_1.OperatorType.empty;
    }
    /** Getter of input type (text, number, password) */
    get inputType() {
        return this._inputType;
    }
    /** Setter of input type (text, number, password) */
    set inputType(type) {
        this._inputType = type;
    }
    /** Getter for the Filter Operator */
    get operator() {
        return this.columnFilter?.operator ?? this.defaultOperator;
    }
    /** Setter for the Filter Operator */
    set operator(operator) {
        if (this.columnFilter) {
            this.columnFilter.operator = operator;
        }
    }
    /** Getter for the Grid Options pulled through the Grid Object */
    get gridOptions() {
        return this.grid?.getOptions() ?? {};
    }
    /**
     * Initialize the Filter
     */
    init(args) {
        if (!args) {
            throw new Error('[Slickgrid-Universal] A filter must always have an "init()" with valid arguments.');
        }
        this.grid = args.grid;
        this.callback = args.callback;
        this.columnDef = args.columnDef;
        if (this.inputFilterType === 'compound') {
            this.operator = args.operator || '';
        }
        this.searchTerms = args?.searchTerms ?? [];
        this._cellContainerElm = args.filterContainerElm;
        // analyze if we have any keyboard debounce delay (do we wait for user to finish typing before querying)
        // it is used by default for a backend service but is optional when using local dataset
        const backendApi = this.gridOptions?.backendServiceApi;
        this._debounceTypingDelay =
            (backendApi
                ? (backendApi?.filterTypingDebounce ?? this.gridOptions?.defaultBackendServiceFilterTypingDebounce)
                : this.gridOptions?.filterTypingDebounce) ?? 0;
        // filter input can only have 1 search term, so we will use the 1st array index if it exist
        const searchTerm = Array.isArray(this.searchTerms) && this.searchTerms.length >= 0 ? this.searchTerms[0] : '';
        // step 1, create the DOM Element of the filter & initialize it if searchTerm is filled
        this.createDomFilterElement(searchTerm);
        // step 2, subscribe to the input event and run the callback when that happens
        // also add/remove "filled" class for styling purposes
        // we'll use all necessary events to cover the following (keyup, change, mousewheel & spinner)
        this._bindEventService.bind(this._filterInputElm, ['keyup', 'blur', 'change'], this.onTriggerEvent.bind(this));
        this._bindEventService.bind(this._filterInputElm, 'wheel', this.onTriggerEvent.bind(this), {
            passive: true,
        });
        if (this.inputFilterType === 'compound' && this._selectOperatorElm) {
            this._bindEventService.bind(this._selectOperatorElm, 'change', this.onTriggerEvent.bind(this));
        }
    }
    /**
     * Clear the filter value
     */
    clear(shouldTriggerQuery = true) {
        if (this._filterInputElm) {
            this._shouldTriggerQuery = shouldTriggerQuery;
            this.searchTerms = [];
            this._filterInputElm.value = '';
            this._currentValue = undefined;
            this.updateFilterStyle(false);
            if (this.inputFilterType === 'compound' && this._selectOperatorElm) {
                this._selectOperatorElm.selectedIndex = 0;
            }
            this.onTriggerEvent(undefined, true);
        }
    }
    /**
     * destroy the filter
     */
    destroy() {
        this._bindEventService.unbindAll();
        this._selectOperatorElm?.remove?.();
        this._filterInputElm?.remove?.();
    }
    getValues() {
        return this._filterInputElm.value;
    }
    /** Set value(s) on the DOM element */
    setValues(values, operator, triggerChange = false) {
        const searchValues = Array.isArray(values) ? values : [values];
        let newInputValue = '';
        for (const value of searchValues) {
            if (this.inputFilterType === 'single') {
                newInputValue = operator ? this.addOptionalOperatorIntoSearchString(value, operator) : value;
            }
            else {
                newInputValue = `${value}`;
            }
            this._filterInputElm.value = `${newInputValue ?? ''}`;
            this._currentValue = this._filterInputElm.value;
        }
        // update "filled" CSS class
        this.updateFilterStyle(this.getValues() !== '');
        // set the operator when defined
        this.operator = operator || this.defaultOperator;
        if (operator && this._selectOperatorElm) {
            const operatorShorthand = (0, index_js_2.mapOperatorToShorthandDesignation)(this.operator);
            this._selectOperatorElm.value = operatorShorthand;
        }
        if (triggerChange) {
            this.onTriggerEvent(undefined, false);
        }
    }
    //
    // protected functions
    // ------------------
    /**
     * When loading the search string from the outside into the input text field, we should also add the prefix/suffix of the operator.
     * We do this so that if it was loaded by a Grid Presets then we should also add the operator into the search string
     * Let's take these 3 examples:
     * 1. (operator: '>=', searchTerms:[55]) should display as ">=55"
     * 2. (operator: 'StartsWith', searchTerms:['John']) should display as "John*"
     * 3. (operator: 'EndsWith', searchTerms:['John']) should display as "*John"
     * @param operator - operator string
     */
    addOptionalOperatorIntoSearchString(inputValue, operator) {
        let searchTermPrefix = '';
        let searchTermSuffix = '';
        let outputValue = inputValue === undefined || inputValue === null ? '' : `${inputValue}`;
        if (operator && outputValue) {
            switch (operator) {
                case '<>':
                case '!=':
                case '=':
                case '==':
                case '>':
                case '>=':
                case '<':
                case '<=':
                    searchTermPrefix = operator;
                    break;
                case 'EndsWith':
                case '*z':
                    searchTermPrefix = '*';
                    break;
                case 'StartsWith':
                case 'a*':
                    searchTermSuffix = '*';
                    break;
            }
            outputValue = `${searchTermPrefix}${outputValue}${searchTermSuffix}`;
        }
        return outputValue;
    }
    /** Get the available operator option values to populate the operator select dropdown list */
    getCompoundOperatorOptionValues() {
        const type = this.columnDef.type && this.columnDef.type ? this.columnDef.type : index_js_1.FieldType.string;
        let operatorList;
        let listType = 'text';
        if (this.columnFilter?.compoundOperatorList) {
            operatorList = this.columnFilter.compoundOperatorList;
        }
        else {
            switch (type) {
                case index_js_1.FieldType.string:
                case index_js_1.FieldType.text:
                case index_js_1.FieldType.readonly:
                case index_js_1.FieldType.password:
                    listType = 'text';
                    operatorList = (0, filterUtilities_js_1.compoundOperatorString)(this.gridOptions, this.translaterService);
                    break;
                default:
                    listType = 'numeric';
                    operatorList = (0, filterUtilities_js_1.compoundOperatorNumeric)(this.gridOptions, this.translaterService);
                    break;
            }
        }
        // add alternate texts when provided
        (0, filterUtilities_js_1.applyOperatorAltTextWhenExists)(this.gridOptions, operatorList, listType);
        return operatorList;
    }
    /**
     * From the html template string, create a DOM element
     * @param {Object} searchTerm - filter search term
     * @returns {Object} DOM element filter
     */
    createDomFilterElement(searchTerm) {
        const columnId = this.columnDef?.id ?? '';
        (0, utils_1.emptyElement)(this._cellContainerElm);
        // create the DOM element & add an ID and filter class
        let placeholder = this.gridOptions?.defaultFilterPlaceholder ?? '';
        if (this.columnFilter?.placeholder) {
            placeholder = this.columnFilter.placeholder;
        }
        const searchVal = `${searchTerm ?? ''}`;
        this._filterInputElm = (0, utils_1.createDomElement)('input', {
            type: this._inputType || 'text',
            autocomplete: 'off',
            ariaAutoComplete: 'none',
            placeholder,
            ariaLabel: this.columnFilter?.ariaLabel ?? `${(0, utils_1.toSentenceCase)(columnId + '')} Search Filter`,
            className: `form-control filter-${columnId}`,
            value: searchVal,
            dataset: { columnid: `${columnId}` },
        });
        // if there's a search term, we will add the "filled" class for styling purposes
        this.updateFilterStyle(!!searchTerm);
        if (searchTerm !== undefined) {
            this._currentValue = searchVal;
        }
        // create the DOM Select dropdown for the Operator
        if (this.inputFilterType === 'single') {
            this._filterContainerElm = this._filterInputElm;
            // append the new DOM element to the header row & an empty span
            this._filterInputElm.classList.add('search-filter', 'slick-filter');
            this._cellContainerElm.appendChild(this._filterInputElm);
            this._cellContainerElm.appendChild(document.createElement('span'));
        }
        else {
            // compound filter
            this._filterInputElm.classList.add('compound-input');
            this._selectOperatorElm = (0, filterUtilities_js_1.buildSelectOperator)(this.getCompoundOperatorOptionValues(), this.grid);
            this._filterContainerElm = (0, utils_1.createDomElement)('div', { className: `form-group search-filter slick-filter filter-${columnId}` });
            const containerInputGroupElm = (0, utils_1.createDomElement)('div', { className: 'input-group' }, this._filterContainerElm);
            const operatorInputGroupAddonElm = (0, utils_1.createDomElement)('div', { className: 'input-group-addon input-group-prepend operator' }, containerInputGroupElm);
            // append operator & input DOM element
            operatorInputGroupAddonElm.appendChild(this._selectOperatorElm);
            containerInputGroupElm.appendChild(this._filterInputElm);
            containerInputGroupElm.appendChild((0, utils_1.createDomElement)('span'));
            if (this.operator) {
                this._selectOperatorElm.value = (0, index_js_2.mapOperatorToShorthandDesignation)(this.operator);
            }
            // append the new DOM element to the header row
            if (this._filterContainerElm) {
                this._cellContainerElm.appendChild(this._filterContainerElm);
            }
        }
    }
    /**
     * Event handler to cover the following (keyup, change, mousewheel & spinner)
     * We will trigger the Filter Service callback from this handler
     */
    onTriggerEvent(event, isClearFilterEvent = false) {
        if (isClearFilterEvent) {
            this.callback(event, {
                columnDef: this.columnDef,
                clearFilterTriggered: isClearFilterEvent,
                shouldTriggerQuery: this._shouldTriggerQuery,
            });
            this.updateFilterStyle(false);
        }
        else {
            const eventType = event?.type || '';
            const selectedOperator = (this._selectOperatorElm?.value ?? this.operator);
            let value = this._filterInputElm.value;
            const enableWhiteSpaceTrim = this.gridOptions.enableFilterTrimWhiteSpace || this.columnFilter.enableTrimWhiteSpace;
            if (typeof value === 'string' && enableWhiteSpaceTrim) {
                value = value.trim();
            }
            if (event?.target?.tagName.toLowerCase() !== 'select') {
                this._currentValue = value;
            }
            this.updateFilterStyle(value !== '');
            const callbackArgs = {
                columnDef: this.columnDef,
                operator: selectedOperator,
                searchTerms: value ? [value] : null,
                shouldTriggerQuery: this._shouldTriggerQuery,
            };
            const typingDelay = eventType === 'keyup' && event?.key !== 'Enter' ? this._debounceTypingDelay : 0;
            const skipNullInput = this.columnFilter.skipCompoundOperatorFilterWithNullInput ?? this.gridOptions.skipCompoundOperatorFilterWithNullInput;
            const hasSkipNullValChanged = (skipNullInput && (0, utils_1.isDefined)(this._currentValue)) || (this._currentValue === '' && (0, utils_1.isDefined)(this._lastSearchValue));
            if (this.inputFilterType === 'single' || !skipNullInput || hasSkipNullValChanged) {
                if (typingDelay > 0) {
                    window.clearTimeout(this._timer);
                    this._timer = window.setTimeout(() => this.callback(event, callbackArgs), typingDelay);
                }
                else {
                    this.callback(event, callbackArgs);
                }
            }
            this._lastSearchValue = value;
        }
        // reset both flags for next use
        this._shouldTriggerQuery = true;
    }
    /** add/remove "filled" CSS class */
    updateFilterStyle(isFilled) {
        if (isFilled) {
            this._filterContainerElm?.classList.add('filled');
            this._filterInputElm.classList.add('filled');
        }
        else {
            this._filterContainerElm?.classList.remove('filled');
            this._filterInputElm.classList.remove('filled');
        }
    }
}
exports.InputFilter = InputFilter;
//# sourceMappingURL=inputFilter.js.map