import {
    AfterViewInit,
    ChangeDetectorRef,
    Component,
    Input,
    TemplateRef,
    ViewChild,
    ViewChildren,
    QueryList,
    ElementRef,
    HostBinding,
    ChangeDetectionStrategy,
    ViewRef,
    HostListener,
    OnDestroy
} from '@angular/core';
import { GridColumnDataType, DataUtil } from '../../../data-operations/data-util';
import { IgxDropDownComponent } from '../../../drop-down/drop-down.component';
import { IFilteringOperation } from '../../../data-operations/filtering-condition';
import { FilteringLogic, IFilteringExpression } from '../../../data-operations/filtering-expression.interface';
import { HorizontalAlignment, VerticalAlignment, OverlaySettings } from '../../../services/overlay/utilities';
import { ConnectedPositioningStrategy } from '../../../services/overlay/position/connected-positioning-strategy';
import { IgxDropDownItemComponent } from '../../../drop-down/drop-down-item.component';
import { ISelectionEventArgs } from '../../../drop-down/drop-down.common';
import { IgxFilteringService } from '../grid-filtering.service';
import { AbsoluteScrollStrategy } from '../../../services/overlay/scroll';
import { DisplayDensity } from '../../../core/density';
import { IgxDatePickerComponent } from '../../../date-picker/date-picker.component';
import { IgxTimePickerComponent } from '../../../time-picker/time-picker.component';
import { isEqual, PlatformUtil } from '../../../core/utils';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ExpressionUI } from '../excel-style/common';
import { ColumnType } from '../../common/grid.interface';
import { IgxRippleDirective } from '../../../directives/ripple/ripple.directive';
import { IgxChipComponent, IBaseChipEventArgs } from '../../../chips/chip.component';
import { IgxChipsAreaComponent } from '../../../chips/chips-area.component';
import { IgxButtonDirective } from '../../../directives/button/button.directive';
import { IgxDateTimeEditorDirective } from '../../../directives/date-time-editor/date-time-editor.directive';
import { IgxPickerToggleComponent, IgxPickerClearComponent } from '../../../date-common/picker-icons.common';
import { IgxSuffixDirective } from '../../../directives/suffix/suffix.directive';
import { IgxInputDirective } from '../../../directives/input/input.directive';
import { IgxDropDownItemNavigationDirective } from '../../../drop-down/drop-down-navigation.directive';
import { IgxPrefixDirective } from '../../../directives/prefix/prefix.directive';
import { IgxInputGroupComponent } from '../../../input-group/input-group.component';
import { IgxIconComponent } from '../../../icon/icon.component';
import { NgFor, NgIf, NgTemplateOutlet, NgClass } from '@angular/common';

/**
 * @hidden
 */
@Component({
    changeDetection: ChangeDetectionStrategy.OnPush,
    selector: 'igx-grid-filtering-row',
    templateUrl: './grid-filtering-row.component.html',
    standalone: true,
    imports: [NgFor, IgxDropDownComponent, IgxDropDownItemComponent, IgxChipsAreaComponent, IgxChipComponent, IgxIconComponent, IgxInputGroupComponent, IgxPrefixDirective, IgxDropDownItemNavigationDirective, IgxInputDirective, NgIf, IgxSuffixDirective, IgxDatePickerComponent, IgxPickerToggleComponent, IgxPickerClearComponent, IgxTimePickerComponent, IgxDateTimeEditorDirective, NgTemplateOutlet, IgxButtonDirective, NgClass, IgxRippleDirective]
})
export class IgxGridFilteringRowComponent implements AfterViewInit, OnDestroy {
    @Input()
    public get column(): ColumnType {
        return this._column;
    }

    public set column(val) {
        if (this._column) {
            this.expressionsList.forEach(exp => exp.isSelected = false);
        }
        if (val) {
            this._column = val;

            this.expressionsList = this.filteringService.getExpressions(this._column.field);
            this.resetExpression();

            this.chipAreaScrollOffset = 0;
            this.transform(this.chipAreaScrollOffset);
        }
    }

    @Input()
    public get value(): any {
        return this.expression ? this.expression.searchVal : null;
    }

    public set value(val) {
        if (!val && val !== 0 && this.expression.searchVal) {
            this.expression.searchVal = null;
            const index = this.expressionsList.findIndex(item => item.expression === this.expression);
            if (index === 0 && this.expressionsList.length === 1) {
                this.filteringService.clearFilter(this.column.field);

                if (this.expression.condition.isUnary) {
                    this.resetExpression(this.expression.condition.name);
                }

                return;
            }
        } else {
            const oldValue = this.expression.searchVal;
            if (isEqual(oldValue, val)) {
                return;
            }

            this.expression.searchVal = DataUtil.parseValue(this.column.dataType, val);
            if (this.expressionsList.find(item => item.expression === this.expression) === undefined) {
                this.addExpression(true);
            }
            this.filter();
        }
    }

    public get displayDensity() {
        return this.column.grid.displayDensity === DisplayDensity.comfortable ? DisplayDensity.cosy : this.column.grid.displayDensity;
    }

    @HostBinding('class.igx-grid__filtering-row')
    public defaultCSSClass = true;

    @HostBinding('class.igx-grid__filtering-row--compact')
    public get compactCSSClass() {
        return this.column.grid.displayDensity === DisplayDensity.compact;
    }

    @HostBinding('class.igx-grid__filtering-row--cosy')
    public get cosyCSSClass() {
        return this.column.grid.displayDensity === DisplayDensity.cosy;
    }

    @ViewChild('defaultFilterUI', { read: TemplateRef, static: true })
    protected defaultFilterUI: TemplateRef<any>;

    @ViewChild('defaultDateUI', { read: TemplateRef, static: true })
    protected defaultDateUI: TemplateRef<any>;

    @ViewChild('defaultTimeUI', { read: TemplateRef, static: true })
    protected defaultTimeUI: TemplateRef<any>;

    @ViewChild('defaultDateTimeUI', { read: TemplateRef, static: true })
    protected defaultDateTimeUI: TemplateRef<any>;

    @ViewChild('input', { read: ElementRef })
    protected input: ElementRef<HTMLInputElement>;

    @ViewChild('inputGroupConditions', { read: IgxDropDownComponent, static: true })
    protected dropDownConditions: IgxDropDownComponent;

    @ViewChild('chipsArea', { read: IgxChipsAreaComponent, static: true })
    protected chipsArea: IgxChipsAreaComponent;

    @ViewChildren('operators', { read: IgxDropDownComponent })
    protected dropDownOperators: QueryList<IgxDropDownComponent>;

    @ViewChild('inputGroup', { read: ElementRef })
    protected inputGroup: ElementRef<HTMLElement>;

    @ViewChild('picker')
    protected picker: IgxDatePickerComponent | IgxTimePickerComponent;

    @ViewChild('inputGroupPrefix', { read: ElementRef })
    protected inputGroupPrefix: ElementRef<HTMLElement>;

    @ViewChild('container', { static: true })
    protected container: ElementRef<HTMLElement>;

    @ViewChild('operand')
    protected operand: ElementRef<HTMLElement>;

    @ViewChild('closeButton', { static: true })
    protected closeButton: ElementRef<HTMLElement>;

    public get nativeElement() {
        return this.ref.nativeElement;
    }

    public showArrows: boolean;
    public expression: IFilteringExpression;
    public expressionsList: Array<ExpressionUI>;

    private _positionSettings = {
        horizontalStartPoint: HorizontalAlignment.Left,
        verticalStartPoint: VerticalAlignment.Bottom
    };

    private _conditionsOverlaySettings: OverlaySettings = {
        closeOnOutsideClick: true,
        modal: false,
        scrollStrategy: new AbsoluteScrollStrategy(),
        positionStrategy: new ConnectedPositioningStrategy(this._positionSettings)
    };

    private _operatorsOverlaySettings: OverlaySettings = {
        closeOnOutsideClick: true,
        modal: false,
        scrollStrategy: new AbsoluteScrollStrategy(),
        positionStrategy: new ConnectedPositioningStrategy(this._positionSettings)
    };

    private chipsAreaWidth: number;
    private chipAreaScrollOffset = 0;
    private _column = null;
    private isKeyPressed = false;
    private isComposing = false;
    private _cancelChipClick = false;

    /** switch to icon buttons when width is below 432px */
    private readonly NARROW_WIDTH_THRESHOLD = 432;

    private $destroyer = new Subject<boolean>();

    constructor(
        public filteringService: IgxFilteringService,
        public ref: ElementRef<HTMLElement>,
        public cdr: ChangeDetectorRef,
        protected platform: PlatformUtil
    ) { }

    @HostListener('keydown', ['$event'])
    public onKeydownHandler(evt: KeyboardEvent) {
        if (this.platform.isFilteringKeyCombo(evt)) {
                evt.preventDefault();
                evt.stopPropagation();
                this.close();
        }
    }

    public ngAfterViewInit() {
        this._conditionsOverlaySettings.outlet = this.column.grid.outlet;
        this._operatorsOverlaySettings.outlet = this.column.grid.outlet;

        const selectedItem = this.expressionsList.find(expr => expr.isSelected === true);
        if (selectedItem) {
            this.expression = selectedItem.expression;
        }

        this.filteringService.grid.localeChange
        .pipe(takeUntil(this.$destroyer))
        .subscribe(() => {
            this.cdr.markForCheck();
        });

        requestAnimationFrame(() => this.focusEditElement());
    }

    public get disabled(): boolean {
        return !(this.column.filteringExpressionsTree && this.column.filteringExpressionsTree.filteringOperands.length > 0);
    }

    public get template(): TemplateRef<any> {
        if (this.column.dataType === GridColumnDataType.Date) {
            return this.defaultDateUI;
        }
        if (this.column.dataType === GridColumnDataType.Time) {
            return this.defaultTimeUI;
        }
        if (this.column.dataType === GridColumnDataType.DateTime) {
            return this.defaultDateTimeUI;
        }
        return this.defaultFilterUI;
    }

    public get type() {
        switch (this.column.dataType) {
            case GridColumnDataType.String:
            case GridColumnDataType.Boolean:
                return 'text';
            case GridColumnDataType.Number:
            case GridColumnDataType.Currency:
                return 'number';
        }
    }

    public get conditions(): any {
        return this.column.filters.conditionList();
    }

    public get isUnaryCondition(): boolean {
        if (this.expression.condition) {
            return this.expression.condition.isUnary;
        } else {
            return true;
        }
    }

    public get placeholder(): string {
        if (this.expression.condition && this.expression.condition.isUnary) {
            return this.filteringService.getChipLabel(this.expression);
        } else if (this.column.dataType === GridColumnDataType.Date) {
            return this.filteringService.grid.resourceStrings.igx_grid_filter_row_date_placeholder;
        } else if (this.column.dataType === GridColumnDataType.Boolean) {
            return this.filteringService.grid.resourceStrings.igx_grid_filter_row_boolean_placeholder;
        } else {
            return this.filteringService.grid.resourceStrings.igx_grid_filter_row_placeholder;
        }
    }

    /**
     * Event handler for keydown on the input group's prefix.
     */
    public onPrefixKeyDown(event: KeyboardEvent) {
        if (this.platform.isActivationKey(event) && this.dropDownConditions.collapsed) {
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
            event.stopImmediatePropagation();
        } else if (event.key === this.platform.KEYMAP.TAB && !this.dropDownConditions.collapsed) {
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
        }
    }

    /**
     * Event handler for keydown on the input.
     */
    public onInputKeyDown(event: KeyboardEvent) {
        this.isKeyPressed = true;
        event.stopPropagation();
        if (this.column.dataType === GridColumnDataType.Boolean) {
            if (this.platform.isActivationKey(event)) {
                this.inputGroupPrefix.nativeElement.focus();
                this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
                return;
            }
        }
        if (event.key === this.platform.KEYMAP.ENTER) {
            if (this.isComposing) {
                return;
            }
            this.commitInput();
        } else if (event.altKey && (event.key === this.platform.KEYMAP.ARROW_DOWN)) {
            this.inputGroupPrefix.nativeElement.focus();
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
        } else if (this.platform.isFilteringKeyCombo(event)) {
            event.preventDefault();
            this.close();
        }
    }

    /**
     * Event handler for keyup on the input.
     */
    public onInputKeyUp() {
        this.isKeyPressed = false;
    }

    /**
     * Event handler for input on the input.
     */
    public onInput(eventArgs) {
        if (!eventArgs) {
            return;
        }

        // The 'iskeyPressed' flag is needed for a case in IE, because the input event is fired on focus and for some reason,
        // when you have a japanese character as a placeholder, on init the value here is empty string .
        const target = eventArgs.target;
        if (this.column.dataType === GridColumnDataType.DateTime) {
            this.value = eventArgs;
            return;
        }
        if (this.platform.isEdge && target.type !== 'number'
            || this.isKeyPressed || target.value || target.checkValidity()) {
            this.value = target.value;
        }
    }

    /**
     * Event handler for compositionstart on the input.
     */
    public onCompositionStart() {
        this.isComposing = true;
    }

    /**
     * Event handler for compositionend on the input.
     */
    public onCompositionEnd() {
        this.isComposing = false;
    }

    /**
     * Event handler for input click event.
     */
    public onInputClick() {
        if (this.column.dataType === GridColumnDataType.Boolean && this.dropDownConditions.collapsed) {
            this.inputGroupPrefix.nativeElement.focus();
            this.toggleConditionsDropDown(this.inputGroupPrefix.nativeElement);
        }
    }

    /**
     * Returns the filtering operation condition for a given value.
     */
    public getCondition(value: string): IFilteringOperation {
        return this.column.filters.condition(value);
    }

    /**
     * Returns the translated condition name for a given value.
     */
    public translateCondition(value: string): string {
        return this.filteringService.grid.resourceStrings[`igx_grid_filter_${this.getCondition(value).name}`] || value;
    }

    /**
     * Returns the icon name of the current condition.
     */
    public getIconName(): string {
        if (this.column.dataType === GridColumnDataType.Boolean && this.expression.condition === null) {
            return this.getCondition(this.conditions[0]).iconName;
        } else {
            return this.expression.condition.iconName;
        }
    }

    /**
     * Returns whether a given condition is selected in dropdown.
     */
    public isConditionSelected(conditionName: string): boolean {
        if (this.expression.condition) {
            return this.expression.condition.name === conditionName;
        } else {
            return false;
        }
    }

    /**
     * Clears the current filtering.
     */
    public clearFiltering() {
        this.filteringService.clearFilter(this.column.field);
        this.resetExpression();
        if (this.input) {
            this.input.nativeElement.focus();
        }
        this.cdr.detectChanges();

        this.chipAreaScrollOffset = 0;
        this.transform(this.chipAreaScrollOffset);
    }

    /**
     * Commits the value of the input.
     */
    public commitInput() {
        const selectedItem = this.expressionsList.filter(ex => ex.isSelected === true);
        selectedItem.forEach(e => e.isSelected = false);

        let indexToDeselect = -1;
        for (let index = 0; index < this.expressionsList.length; index++) {
            const expression = this.expressionsList[index].expression;
            if (expression.searchVal === null && !expression.condition.isUnary) {
                indexToDeselect = index;
            }
        }
        if (indexToDeselect !== -1) {
            this.removeExpression(indexToDeselect, this.expression);
        }
        this.resetExpression();
        this.scrollChipsWhenAddingExpression();
    }

    /**
     * Clears the value of the input.
     */
    public clearInput(event?: MouseEvent) {
        event?.stopPropagation();
        this.value = null;
    }

    /**
     * Event handler for keydown on clear button.
     */
    public onClearKeyDown(eventArgs: KeyboardEvent) {
        if (this.platform.isActivationKey(eventArgs)) {
            eventArgs.preventDefault();
            this.clearInput();
            this.focusEditElement();
        }
    }

    /**
     * Event handler for click on clear button.
     */
    public onClearClick() {
        this.clearInput();
        this.focusEditElement();
    }

    /**
     * Event handler for keydown on commit button.
     */
    public onCommitKeyDown(eventArgs: KeyboardEvent) {
        if (this.platform.isActivationKey(eventArgs)) {
            eventArgs.preventDefault();
            this.commitInput();
            this.focusEditElement();
        }
    }

    /**
     * Event handler for click on commit button.
     */
    public onCommitClick(event?: MouseEvent) {
        event?.stopPropagation();
        this.commitInput();
        this.focusEditElement();
    }

    /**
     * Event handler for focusout on the input group.
     */
    public onInputGroupFocusout() {
        if (!this.value && this.value !== 0 &&
            this.expression.condition && !this.expression.condition.isUnary) {
            return;
        }
        requestAnimationFrame(() => {
            const focusedElement = document.activeElement;

            if (focusedElement.classList.contains('igx-chip__remove') || focusedElement.tagName === 'IGX-DAY-ITEM') {
                return;
            }

            if (!(focusedElement && this.editorsContain(focusedElement))
                && this.dropDownConditions.collapsed) {
                this.commitInput();
            }
        });
    }

    /**
     * Closes the filtering edit row.
     */
    public close() {
        if (this.expressionsList.length === 1 &&
            this.expressionsList[0].expression.searchVal === null &&
            this.expressionsList[0].expression.condition.isUnary === false) {
            this.filteringService.getExpressions(this.column.field).pop();

            this.filter();
        } else {
            const condToRemove = this.expressionsList.filter(ex => ex.expression.searchVal === null && !ex.expression.condition.isUnary);
            if (condToRemove && condToRemove.length > 0) {
                condToRemove.forEach(c => this.filteringService.removeExpression(this.column.field, this.expressionsList.indexOf(c)));
                this.filter();
            }
        }

        this.filteringService.isFilterRowVisible = false;
        this.filteringService.updateFilteringCell(this.column);
        this.filteringService.filteredColumn = null;
        this.filteringService.selectedExpression = null;
        this.filteringService.grid.theadRow.nativeElement.focus();

        this.chipAreaScrollOffset = 0;
        this.transform(this.chipAreaScrollOffset);
    }

    /**
     *  Event handler for date picker's selection.
     */
    public onDateSelected(value: Date) {
        this.value = value;
    }

    /** @hidden @internal */
    public inputGroupPrefixClick(event: MouseEvent) {
        event.stopPropagation();
        (event.currentTarget as HTMLElement).focus();
        this.toggleConditionsDropDown(event.currentTarget);
    }

    /**
     * Opens the conditions dropdown.
     */
    public toggleConditionsDropDown(target: any) {
        this._conditionsOverlaySettings.target = target;
        this._conditionsOverlaySettings.excludeFromOutsideClick = [target as HTMLElement];
        this.dropDownConditions.toggle(this._conditionsOverlaySettings);
    }

    /**
     * Opens the logic operators dropdown.
     */
    public toggleOperatorsDropDown(eventArgs, index) {
        this._operatorsOverlaySettings.target = eventArgs.target.parentElement;
        this._operatorsOverlaySettings.excludeFromOutsideClick = [eventArgs.target.parentElement as HTMLElement];
        this.dropDownOperators.toArray()[index].toggle(this._operatorsOverlaySettings);
    }

    /**
     * Event handler for change event in conditions dropdown.
     */
    public onConditionsChanged(eventArgs) {
        const value = (eventArgs.newSelection as IgxDropDownItemComponent).value;
        this.expression.condition = this.getCondition(value);
        if (this.expression.condition.isUnary) {
            // update grid's filtering on the next cycle to ensure the drop-down is closed
            // if the drop-down is not closed this event handler will be invoked multiple times
            requestAnimationFrame(() => this.unaryConditionChangedCallback());
        } else {
            requestAnimationFrame(() => this.conditionChangedCallback());
        }

        // Add requestAnimationFrame because of an issue in IE, where you are still able to write in the input,
        // if it has been focused and then set to readonly.
        requestAnimationFrame(() => this.focusEditElement());
    }


    public onChipPointerdown(args, chip: IgxChipComponent) {
        const activeElement = document.activeElement;
        this._cancelChipClick = chip.selected
            && activeElement && this.editorsContain(activeElement);
    }

    public onChipClick(args, item: ExpressionUI) {
        if (this._cancelChipClick) {
            this._cancelChipClick = false;
            return;
        }

        this.expressionsList.forEach(ex => ex.isSelected = false);

        this.toggleChip(item);
    }

    public toggleChip(item: ExpressionUI) {
        item.isSelected = !item.isSelected;
        if (item.isSelected) {
            this.expression = item.expression;

            this.focusEditElement();
        }
    }

    /**
     * Event handler for chip keydown event.
     */
    public onChipKeyDown(eventArgs: KeyboardEvent, item: ExpressionUI) {
        if (eventArgs.key === this.platform.KEYMAP.ENTER) {
            eventArgs.preventDefault();

            this.toggleChip(item);
        }
    }

    /**
     * Scrolls the first chip into view if the tab key is pressed on the left arrow.
     */
    public onLeftArrowKeyDown(event: KeyboardEvent) {
        if (event.key === this.platform.KEYMAP.TAB) {
            this.chipAreaScrollOffset = 0;
            this.transform(this.chipAreaScrollOffset);
        }
    }

    /**
     * Event handler for chip removed event.
     */
    public onChipRemoved(eventArgs: IBaseChipEventArgs, item: ExpressionUI) {
        const indexToRemove = this.expressionsList.indexOf(item);
        this.removeExpression(indexToRemove, item.expression);

        this.scrollChipsOnRemove();
    }

    /**
     * Event handler for logic operator changed event.
     */
    public onLogicOperatorChanged(eventArgs: ISelectionEventArgs, expression: ExpressionUI) {
        if (eventArgs.oldSelection) {
            expression.afterOperator = (eventArgs.newSelection as IgxDropDownItemComponent).value;
            this.expressionsList[this.expressionsList.indexOf(expression) + 1].beforeOperator = expression.afterOperator;

            // update grid's filtering on the next cycle to ensure the drop-down is closed
            // if the drop-down is not closed this event handler will be invoked multiple times
            requestAnimationFrame(() => this.filter());
        }
    }

    /**
     * Scrolls the chips into the chip area when left or right arrows are pressed.
     */
    public scrollChipsOnArrowPress(arrowPosition: string) {
        let count = 0;
        const chipAraeChildren = this.chipsArea.element.nativeElement.children;
        const containerRect = this.container.nativeElement.getBoundingClientRect();

        if (arrowPosition === 'right') {
            for (const chip of chipAraeChildren) {
                if (Math.ceil(chip.getBoundingClientRect().right) < Math.ceil(containerRect.right)) {
                    count++;
                }
            }

            if (count < chipAraeChildren.length) {
                this.chipAreaScrollOffset -= Math.ceil(chipAraeChildren[count].getBoundingClientRect().right) -
                    Math.ceil(containerRect.right) + 1;
                this.transform(this.chipAreaScrollOffset);
            }
        }

        if (arrowPosition === 'left') {
            for (const chip of chipAraeChildren) {
                if (Math.ceil(chip.getBoundingClientRect().left) < Math.ceil(containerRect.left)) {
                    count++;
                }
            }

            if (count > 0) {
                this.chipAreaScrollOffset += Math.ceil(containerRect.left) -
                    Math.ceil(chipAraeChildren[count - 1].getBoundingClientRect().left) + 1;
                this.transform(this.chipAreaScrollOffset);
            }
        }
    }

    /**
     * @hidden
     * Resets the chips area
     * @memberof IgxGridFilteringRowComponent
     */
    public resetChipsArea() {
        this.chipAreaScrollOffset = 0;
        this.transform(this.chipAreaScrollOffset);
        this.showHideArrowButtons();
    }

    /** @hidden @internal */
    public focusEditElement() {
        if (this.input) {
            this.input.nativeElement.focus();
        } else if (this.picker) {
            this.picker.getEditElement().focus();
        }
    }

    public ngOnDestroy() {
        this.$destroyer.next();
    }

    private showHideArrowButtons() {
        requestAnimationFrame(() => {
            if (this.filteringService.isFilterRowVisible) {
                const containerWidth = this.container.nativeElement.getBoundingClientRect().width;
                this.chipsAreaWidth = this.chipsArea.element.nativeElement.getBoundingClientRect().width;

                this.showArrows = this.chipsAreaWidth >= containerWidth && this.isColumnFiltered;

                // TODO: revise the cdr.detectChanges() usage here
                if (!(this.cdr as ViewRef).destroyed) {
                    this.cdr.detectChanges();
                }
            }
        });
    }

    private addExpression(isSelected: boolean) {
        const exprUI = new ExpressionUI();
        exprUI.expression = this.expression;
        exprUI.beforeOperator = this.expressionsList.length > 0 ? FilteringLogic.And : null;
        exprUI.isSelected = isSelected;

        this.expressionsList.push(exprUI);

        const length = this.expressionsList.length;
        if (this.expressionsList[length - 2]) {
            this.expressionsList[length - 2].afterOperator = this.expressionsList[length - 1].beforeOperator;
        }

        this.showHideArrowButtons();
    }

    private removeExpression(indexToRemove: number, expression: IFilteringExpression) {
        if (indexToRemove === 0 && this.expressionsList.length === 1) {
            this.clearFiltering();
            return;
        }

        this.filteringService.removeExpression(this.column.field, indexToRemove);

        this.filter();

        if (this.expression === expression) {
            this.resetExpression();
        }

        this.showHideArrowButtons();
    }

    private resetExpression(condition?: string) {
        this.expression = {
            fieldName: this.column.field,
            condition: null,
            searchVal: null,
            ignoreCase: this.column.filteringIgnoreCase
        };

        if (this.column.dataType !== GridColumnDataType.Boolean) {
            this.expression.condition = this.getCondition(condition ?? this.conditions[0]);
        }

        if (this.column.dataType === GridColumnDataType.Date && this.input) {
            this.input.nativeElement.value = null;
        }

        this.showHideArrowButtons();
    }

    private scrollChipsWhenAddingExpression() {
        const chipAraeChildren = this.chipsArea.element.nativeElement.children;
        if (!chipAraeChildren || chipAraeChildren.length === 0) {
            return;
        }

        const chipsContainerWidth = this.container.nativeElement.offsetWidth;
        const chipsAreaWidth = this.chipsArea.element.nativeElement.offsetWidth;

        if (chipsAreaWidth > chipsContainerWidth) {
            this.chipAreaScrollOffset = chipsContainerWidth - chipsAreaWidth;
            this.transform(this.chipAreaScrollOffset);
        }
    }

    private transform(offset: number) {
        requestAnimationFrame(() => {
            this.chipsArea.element.nativeElement.style.transform = `translate(${offset}px)`;
        });
    }

    private scrollChipsOnRemove() {
        let count = 0;
        const chipAraeChildren = this.chipsArea.element.nativeElement.children;
        const containerRect = this.container.nativeElement.getBoundingClientRect();

        for (const chip of chipAraeChildren) {
            if (Math.ceil(chip.getBoundingClientRect().right) < Math.ceil(containerRect.left)) {
                count++;
            }
        }

        if (count <= 2) {
            this.chipAreaScrollOffset = 0;
        } else {
            const dif = chipAraeChildren[count].id === 'chip' ? count - 2 : count - 1;
            this.chipAreaScrollOffset += Math.ceil(containerRect.left) - Math.ceil(chipAraeChildren[dif].getBoundingClientRect().left) + 1;
        }

        this.transform(this.chipAreaScrollOffset);
    }

    private conditionChangedCallback() {
        if (!!this.expression.searchVal || this.expression.searchVal === 0) {
            this.filter();
        } else if (this.value) {
            this.value = null;
        }
    }

    private unaryConditionChangedCallback() {
        if (this.value) {
            this.value = null;
        }
        if (this.expressionsList.find(item => item.expression === this.expression) === undefined) {
            this.addExpression(true);
        }
        this.filter();
    }

    private filter() {
        this.filteringService.filterInternal(this.column.field);
    }

    private editorsContain(child: Element): boolean {
        // if the first check is false and the second is undefined this will return undefined
        // make sure it always returns boolean
        return !!(this.inputGroup && this.inputGroup.nativeElement.contains(child)
            || this.picker && this.picker.element.nativeElement.contains(child));
    }

    private get isColumnFiltered() {
        return this.column.filteringExpressionsTree && this.column.filteringExpressionsTree.filteringOperands.length > 0;
    }

    public get isNarrowWidth(): boolean {
        return this.nativeElement.offsetWidth < this.NARROW_WIDTH_THRESHOLD;
    }
}
