import { ReactElement, ReactNode, FC, ChangeEvent } from 'react';
import { runInAction } from 'mobx';
import { observer, useLocalStore } from 'mobx-react';
import { multiselectOperators, isMultiselectOperator } from './multiselect-operators';

import {
    DropDownList,
    DropDownListChangeEvent,
    MultiSelect,
    MultiSelectChangeEvent,
    MultiSelectFilterChangeEvent,
    ListItemProps,
    MultiSelectTagData,
} from '@progress/kendo-react-dropdowns';

import { GridColumnMenuFilterCellProps as TableColumnMenuFilterCellProps } from '@progress/kendo-react-grid/dist/npm/columnMenu/GridColumnMenuFilterCell';
import {
    IsUnaryFilter,
    cellOperatorChange,
} from '@progress/kendo-react-grid/dist/npm/filterCommon';

import { Icon, Tooltip } from '@servicetitan/design-system';

import * as Styles from './filter-cell-ext.module.css';
import classNames from 'classnames';
//

interface ValueWithLowerCase {
    value: any;
    text: string;
    textLC: string;
}

export interface FilterCellExtProps extends TableColumnMenuFilterCellProps {
    options: ValueWithLowerCase[];
    maxFoundValues?: number;
    allowCustomValues?: boolean;
    onlyMultiselectAndEmptyOperators?: boolean;
    handleCustomValuesAsNumber?: boolean;
    multiselectItemRender?(li: ReactElement<HTMLLIElement>, itemProps: ListItemProps): ReactNode;
    multiselectTagRender?(
        tagData: MultiSelectTagData,
        li: ReactElement<HTMLLIElement>
    ): ReactElement<HTMLLIElement>;
    multiselectListNoDataRender?(element: ReactElement<HTMLDivElement>): ReactNode;
}

// like standard filter cell (rendered for string type), but with support of multiselect operators
export const FilterCellExt: FC<FilterCellExtProps> = observer(props => {
    const store = useLocalStore(
        oprops => ({
            get operators() {
                return multiselectOperators.concat(
                    oprops.onlyMultiselectAndEmptyOperators
                        ? oprops.operators.filter(
                              o => o.operator === 'isempty' || o.operator === 'isnotempty'
                          )
                        : oprops.operators
                );
            },
            get operatorValue() {
                return this.operators.find(item => item.operator === oprops.operator);
            },
            searchQuery: '',
            filterChange(event: MultiSelectFilterChangeEvent) {
                runInAction(() => (this.searchQuery = event.filter.value));
            },
            get filteredValuesSet() {
                const queryLC = this.searchQuery.toLowerCase();
                return oprops.options.filter(v => v.textLC.includes(queryLC));
            },
            get onlyFirstValues() {
                return oprops.maxFoundValues &&
                    oprops.maxFoundValues < this.filteredValuesSet.length
                    ? oprops.maxFoundValues
                    : false;
            },
            get filteredVisibleValues() {
                return this.onlyFirstValues
                    ? this.filteredValuesSet.slice(0, this.onlyFirstValues)
                    : this.filteredValuesSet;
            },
            get valueTextMap() {
                return new Map(oprops.options.map(o => [o.value, o.text]));
            },
            get multiselectValue() {
                return Array.isArray(oprops.value)
                    ? oprops.value.map(v => ({
                          value: v,
                          text: this.valueTextMap.get(v) ?? v.toString(),
                      }))
                    : [];
            },
        }),
        props
    );

    const {
        operator = '',
        onChange,
        allowCustomValues,
        multiselectItemRender,
        multiselectTagRender,
        multiselectListNoDataRender,
    } = props;

    const handleInputChange = (e: ChangeEvent<HTMLInputElement>) =>
        onChange({
            operator,
            value: e.target.value,
            syntheticEvent: e,
        });

    const handleMultiselectChange = (e: MultiSelectChangeEvent) => {
        const newValue = e.value.map(
            (v: any) => v.value ?? (props.handleCustomValuesAsNumber ? +v.text : v.text)
        );
        onChange({
            operator,
            value: newValue.length ? newValue : '', // for Kendo to handle empty array as an empty value TODO: check if there is another way
            syntheticEvent: e.syntheticEvent,
        });
    };

    // conversions for better compatibility when switching plain/array operators
    const handleOperatorChange = (event: DropDownListChangeEvent) => {
        const newValue =
            isMultiselectOperator(operator) === isMultiselectOperator(event.target.value.operator)
                ? props.value
                : '';
        cellOperatorChange(event, newValue, onChange);
    };

    const allowCustomAdd =
        (allowCustomValues &&
            store.searchQuery &&
            (!props.handleCustomValuesAsNumber || !isNaN(+store.searchQuery))) ||
        false;

    return (
        <div>
            <DropDownList
                value={store.operatorValue}
                onChange={handleOperatorChange}
                data={store.operators}
                className="m-b-1"
                textField="text"
            />
            {(typeof operator !== 'string' || !IsUnaryFilter(operator)) &&
                (isMultiselectOperator(operator) ? (
                    <MultiSelect
                        value={store.multiselectValue}
                        onChange={handleMultiselectChange}
                        filterable
                        onFilterChange={store.filterChange}
                        allowCustom={allowCustomAdd}
                        data={store.filteredVisibleValues}
                        textField="text"
                        dataItemKey="value"
                        className={classNames('m-b-1', Styles.multiSelect)}
                        header={allowCustomAdd && <div className={Styles.customItemAddMarker} />}
                        footer={
                            allowCustomAdd ? (
                                <div className={Styles.returnToUse}>
                                    <Icon name="keyboard_return" />
                                    to create filter
                                </div>
                            ) : (
                                store.onlyFirstValues && (
                                    <div className={Styles.returnToUse}>
                                        First {store.onlyFirstValues} values are shown. <br />
                                        Type to search for other values.
                                    </div>
                                )
                            )
                        }
                        itemRender={multiselectItemRender}
                        tagRender={multiselectTagRender ?? defaultMultiselectTagRender}
                        listNoDataRender={multiselectListNoDataRender}
                    />
                ) : (
                    <input
                        className="k-input k-input-md k-rounded-md k-input-solid m-b-1"
                        value={props.value}
                        onChange={handleInputChange}
                    />
                ))}
        </div>
    );
});

const defaultMultiselectTagRender = (
    tagData: MultiSelectTagData,
    li: ReactElement<HTMLLIElement>
): ReactElement<HTMLLIElement> => {
    return (
        <Tooltip
            portal
            text={tagData.text}
            className={Styles.multiSelectTagTooltipWrapper}
            popperClassName={Styles.multiSelectTagTooltip}
        >
            {li}
        </Tooltip>
    );
};
