import { isSameDay } from 'date-fns';
import * as React from 'react';
import ReactSelect, { components, } from 'react-select';
import CreatableSelect from 'react-select/creatable';
import { Icon } from '../icons';
import { Box } from 'rebass';
import { DataSource, InfiniteTable, } from '@infinite-table/infinite-react';
import { useMemo } from 'react';
import join from '../utils/join';
const checkboxStyle = {
    position: 'relative',
    top: 1,
};
const INFINITE_DOM_PROPS = {
    style: {
        height: '100%',
        minHeight: `var(--ab-cmp-select-menu__min-height, 25rem)`,
        maxHeight: '50vh',
        width: '100%',
    },
};
const INFINITE_COLUMNS_WITH_CHECKBOX = {
    label: {
        field: 'label',
        defaultFlex: 1,
        style: {
            lineHeight: '30px',
        },
        resizable: false,
        defaultSortable: false,
        renderSelectionCheckBox: true,
        className: 'ab-Select-CheckboxColumn',
        renderValue: ({ renderBag }) => {
            return React.createElement("div", { className: "InfiniteCell_content_value" }, renderBag.value);
        },
        renderHeader: (headerParams) => {
            return (React.createElement(React.Fragment, null,
                headerParams.renderBag.selectionCheckBox,
                headerParams.allRowsSelected ? '(Deselect All)' : '(Select All)'));
        },
        renderMenuIcon: false,
    },
};
const INFINITE_COLUMNS_WITH_RADIO = {
    label: {
        field: 'label',
        defaultFlex: 1,
    },
};
const isRowDisabled = ({ data, }) => {
    return data ? !!data.isDisabled : false;
};
const rowClassName = ({ data }) => {
    const flags = data.isDisabled ? 'ab-Select-Row--disabled' : '';
    return `ab-Select-Row ${flags}`;
};
const fontCommonStyles = {
    fontSize: 'var(--ab-cmp-select__font-size)',
    fontFamily: 'var(--ab-cmp-select__font-family)',
};
const commonStyles = ({ isDisabled, }) => {
    return {
        color: 'var(--ab-cmp-input__color)',
        background: isDisabled
            ? 'var(--ab-cmp-input--disabled__background)'
            : 'var(--ab-cmp-input__background)',
        ...fontCommonStyles,
    };
};
const doesOptionMatchValue = function (value) {
    return (option) => {
        if (typeof option.value === 'object' && option.value instanceof Date) {
            return isSameDay(option.value, value);
        }
        return option.value === value;
    };
};
export const Select = function (props) {
    let maxLabelLength = 0;
    const ref = React.useRef(null);
    const valueToOptionMap = new Map((props.options || []).map((opt) => {
        let label = opt.label;
        if (typeof label === 'string' || typeof label === 'number' || typeof label === 'boolean') {
            maxLabelLength = Math.max(maxLabelLength, `${label}`.length);
        }
        return [opt.value, opt];
    }));
    const findOptionByValue = (value) => {
        const option = valueToOptionMap.get(value);
        if (option) {
            return option;
        }
        // try date comparison if the value is a date
        // or if it's a number but the options are dates
        if ((typeof value === 'object' && value instanceof Date) ||
            (Array.isArray(props.options) &&
                props.options.length &&
                typeof props.options[0] === 'object' &&
                props.options[0].value instanceof Date)) {
            return (props.options || []).find(doesOptionMatchValue(value)) ?? null;
        }
        return null;
    };
    const renderMultipleValues = props.renderMultipleValues;
    const isMulti = props.isMulti ?? Array.isArray(props.value);
    const showHeaderSelectionCheckbox = isMulti && (props.showHeaderSelectionCheckbox ?? false);
    let selectedOption = null;
    if (isMulti) {
        selectedOption =
            (props.value ?? []).map((value) => {
                const option = findOptionByValue(value);
                if (!option) {
                    return {
                        value,
                        label: value,
                    };
                }
                return option;
            }) ?? null;
    }
    else {
        selectedOption = findOptionByValue(props.value);
        if (!selectedOption && props.value !== undefined && props.value !== null) {
            selectedOption = {
                value: props.value,
                label: props.value,
            };
        }
    }
    let disabled = props.disabled ?? false;
    const accessLevel = props.accessLevel ?? 'Full';
    if (accessLevel === 'Hidden') {
        return null;
    }
    if (accessLevel === 'ReadOnly') {
        disabled = true;
    }
    /**
     * If on each render a new reference is passed, the menu will not open using the keyboard.
     */
    const SelectContainer = React.useMemo(() => {
        return (inputProps) => {
            return (React.createElement(components.SelectContainer, { ...inputProps, innerProps: {
                    // @ts-ignore
                    'data-name': props['data-name'],
                    'data-id': props['data-id'],
                    ...inputProps.innerProps,
                } }));
        };
    }, []);
    const ValueContainer = React.useMemo(() => {
        return (props) => {
            let { children, ...inputProps } = props;
            const initialChildren = children;
            const currentValues = inputProps.getValue();
            const customRenderValue = renderMultipleValues?.(currentValues, {
                focused: focusedRef.current,
                placeholder: props.placeholder,
            });
            const title = typeof customRenderValue === 'string' ? customRenderValue : '';
            children = customRenderValue ? (React.createElement(Box, { display: 'flex', flexDirection: 'row', flex: 1, flexWrap: 'nowrap', alignItems: 'center', overflow: 'hidden', height: '100%' },
                React.createElement("span", { title: title, "data-name": "multi-value-text", style: {
                        flex: '0 1 auto',
                        appearance: 'none',
                        border: 'none',
                        background: 'transparent',
                        outline: 'none',
                        whiteSpace: 'nowrap',
                        overflow: 'hidden',
                        textOverflow: 'ellipsis',
                    } }, customRenderValue),
                initialChildren[1])) : (initialChildren);
            return (React.createElement(components.ValueContainer, { ...inputProps, innerProps: {
                    'data-name': 'value-container',
                    ...inputProps.innerProps,
                } }, children));
        };
    }, [renderMultipleValues, props.placeholder]);
    const MenuComponent = React.useMemo(() => {
        return (inputProps) => {
            const { isLoading } = inputProps;
            return (React.createElement(React.Fragment, null,
                React.createElement(components.Menu, { ...inputProps, innerProps: {
                        'data-name': 'menu-container',
                        ...inputProps.innerProps,
                    } },
                    inputProps.children,
                    React.createElement("div", { style: {
                            display: isLoading ? 'block' : 'none',
                            position: 'absolute',
                            inset: 0,
                            background: `var(--ab-cmp-select-loading__background)`,
                            zIndex: 1,
                        } }))));
        };
    }, []);
    const SelectComponent = props.isCreatable ? CreatableSelect : ReactSelect;
    const ClearIndicator = React.useMemo(() => {
        return (clearIndicatorProps) => {
            return (React.createElement(components.ClearIndicator, { ...clearIndicatorProps },
                React.createElement(Icon, { name: "close" })));
        };
    }, []);
    const MenuList = React.useMemo(() => {
        return (props) => {
            const { setValue, getValue, focusedOption, options, selectProps } = props;
            const { filterOption, inputValue, options: selectOptions, isLoading } = selectProps;
            const filterFunction = React.useCallback(({ data }) => filterOption({ data, label: `${data.label}`, value: data.value }, inputValue), [filterOption, inputValue]);
            const hasFilter = inputValue !== '';
            const filteredOptionsValues = new Set();
            const filteredOptions = !hasFilter
                ? options
                : options.filter((data) => {
                    const included = filterOption({ data, label: `${data.label}`, value: data.value }, inputValue);
                    if (included) {
                        filteredOptionsValues.add(data.value);
                    }
                    return included;
                });
            const focusedOptionIndex = options
                .filter((option) => filterFunction({ data: option }))
                .indexOf(focusedOption);
            const valueStr = getValue()
                .map((option) => option.value)
                .join(',');
            const selectedRows = useMemo(() => {
                const selectedRows = [];
                // we build the Infinite Table selection
                getValue().forEach((option) => {
                    // if there are no filters, include everything that is in the value
                    // but if there is a filter (which will be smaller than the current value)
                    // only include the current options that match the filter
                    if (!hasFilter || filteredOptionsValues.has(option.value)) {
                        selectedRows.push(option.value);
                    }
                });
                return selectedRows;
            }, [valueStr, inputValue, hasFilter]);
            const rowSelection = React.useMemo(() => {
                return isMulti
                    ? {
                        selectedRows,
                        deselectedRows: [],
                        defaultSelection: false,
                    }
                    : selectedRows[0];
            }, [selectedRows, isMulti]);
            const onRowSelectionChange = React.useCallback((selectionParams) => {
                let selection = [];
                const { selectedRows, defaultSelection, deselectedRows } = selectionParams;
                if (hasFilter) {
                    if (defaultSelection === true && deselectedRows.length === 0) {
                        // all selected
                        const currentValue = getValue();
                        const includedKeys = new Set();
                        // add all the options that are in the current value
                        selection = currentValue.map((option) => {
                            includedKeys.add(option.value);
                            return option;
                        });
                        // also add all the options that match the filter
                        filteredOptions.forEach((option) => {
                            // make sure you don't add the same option twice
                            if (!includedKeys.has(option.value)) {
                                selection.push({ value: option.value });
                            }
                        });
                    }
                    else if (defaultSelection === false && selectedRows.length === 0) {
                        // we need to clear the selection
                        // but we have a filter, so we need to clear only those options that are included in the filter
                        // and leave the rest
                        const currentValue = getValue();
                        selection = currentValue.filter((row) => !filteredOptionsValues.has(row.value));
                    }
                    else {
                        // some selected
                        const currentValue = getValue();
                        const includedKeys = new Set();
                        // start with an empty selection
                        selection = [];
                        // add those in the value that are not in the filter
                        selection = currentValue.filter((row) => {
                            const res = !filteredOptionsValues.has(row.value);
                            if (res) {
                                includedKeys.add(row.value);
                            }
                            return res;
                        });
                        // and now also add those in the filter that are actually selected
                        selectedRows.forEach((row) => {
                            if (!includedKeys.has(row)) {
                                selection.push({ value: row });
                            }
                        });
                    }
                }
                else {
                    if (defaultSelection === true && deselectedRows.length === 0) {
                        // all selected
                        selection = options.map((option) => {
                            return { value: option.value };
                        });
                    }
                    else if (defaultSelection === false && selectedRows.length === 0) {
                        // none selected
                        selection = [];
                    }
                    else {
                        // some selected
                        selection = selectedRows.map((row) => {
                            return { value: row };
                        });
                    }
                }
                //@ts-ignore
                setValue(selection, 'select-option');
            }, [setValue, filterOption, inputValue, hasFilter, filteredOptions, rowSelection, valueStr]);
            const onSingleRowSelectionChange = React.useCallback((value) => {
                if (value == undefined && rowSelection != undefined) {
                    return;
                }
                //@ts-ignore
                setValue({ value }, 'select-option');
            }, [setValue]);
            const onCellClick = React.useCallback(({ rowIndex, api, dataSourceApi }) => {
                const pk = dataSourceApi.getPrimaryKeyByIndex(rowIndex);
                api.rowSelectionApi.toggleRowSelection(pk);
                // see #ensure-select-closes-after-clicking-outside
                requestAnimationFrame(() => {
                    ref.current?.focus();
                });
            }, []);
            return (React.createElement(DataSource, { 
                // @ts-ignore
                // data={props.options}
                // @ts-ignore
                data: filteredOptions, primaryKey: "value", selectionMode: isMulti ? 'multi-row' : 'single-row', 
                // @ts-ignore
                onRowSelectionChange: isLoading ? null : isMulti ? onRowSelectionChange : onSingleRowSelectionChange, rowSelection: rowSelection, isRowDisabled: isRowDisabled },
                React.createElement(InfiniteTable, { header: isMulti && showHeaderSelectionCheckbox ? true : false, rowClassName: rowClassName, showZebraRows: false, rowHeight: '--ab-grid-row-height', onCellClick: isLoading ? null : onCellClick, keyboardNavigation: isLoading ? false : 'row', activeRowIndex: focusedOptionIndex, keyboardSelection: true, columns: isMulti ? INFINITE_COLUMNS_WITH_CHECKBOX : INFINITE_COLUMNS_WITH_RADIO, domProps: INFINITE_DOM_PROPS })));
        };
    }, [isMulti, showHeaderSelectionCheckbox]);
    const DropdownIndicator = React.useMemo(() => {
        return (props) => {
            return (React.createElement(components.DropdownIndicator, { ...props },
                React.createElement(Icon, { name: "triangle-down", style: { height: 20, width: 20 } })));
        };
    }, []);
    const focusedRef = React.useRef(false);
    const onFocus = React.useCallback(() => {
        focusedRef.current = true;
        props.onFocus?.();
    }, [props.onFocus]);
    const onBlur = React.useCallback(() => {
        focusedRef.current = false;
        props.onBlur?.();
    }, [props.onBlur]);
    const [inputValue, setInputValue] = React.useState('');
    const onInputChange = React.useCallback((value, actionMeta) => {
        if (isMulti && actionMeta.action === 'set-value') {
            // when selecting an option, don't clear the input filter
            return;
        }
        setInputValue(value);
        props.onInputChange?.(value);
    }, [props.onInputChange, isMulti]);
    return (React.createElement(SelectComponent, { ref: ref, inputValue: inputValue, onInputChange: onInputChange, onFocus: onFocus, onBlur: onBlur, onMenuOpen: props.onMenuOpen, isLoading: props.isLoading, options: props.options, className: join(props.className, 'ab-Select'), isDisabled: disabled, menuPlacement: props.menuPlacement ?? 'auto', isSearchable: props.searchable, hideSelectedOptions: false, isMulti: isMulti, value: selectedOption, blurInputOnSelect: false, menuPosition: props.menuPosition ?? 'absolute', 
        // This needed so the menu is not clipped by overflow: hidden
        menuPortalTarget: props.menuPortalTarget === undefined ? document.body : null, isClearable: props.isClearable, closeMenuOnSelect: props.closeMenuOnSelect, onChange: (option) => {
            if (isMulti) {
                const value = option.map((x) => x?.value);
                props.onChange(value);
                // ensure element keeps focus
                requestAnimationFrame(() => {
                    ref.current?.focus();
                });
            }
            else {
                props.onChange(option?.value);
            }
        }, placeholder: props.placeholder, createOptionPosition: 'first', 
        // formatCreateLabel={(inputValue) => inputValue} // can we make this auto??
        // we use this:  https://react-select.com/creatable
        components: {
            SelectContainer,
            ValueContainer,
            Menu: MenuComponent,
            SingleValue: React.useCallback((singleValueProps) => {
                return (React.createElement(components.SingleValue, { ...singleValueProps }, props.renderSingleValue
                    ? props.renderSingleValue(selectedOption)
                    : singleValueProps.children));
            }, [selectedOption]),
            ClearIndicator,
            DropdownIndicator,
            MenuList,
        }, 
        /**
         * Using styles is the preferred way to style react-select.
         * https://react-select.com/styles#the-styles-prop
         */
        styles: {
            // Typescript issue with csstype@3.1.3
            // https://github.com/JedWatson/react-select/issues/5825#issuecomment-1850472549
            // Remove
            container: (baseStyle) => {
                return {
                    ...baseStyle,
                    ...props.style,
                    ...props.styles?.container,
                };
            },
            // @ts-ignorets-ignore when fixed
            menuPortal: (baseStyle) => {
                return {
                    ...baseStyle,
                    zIndex: 999999,
                    textAlign: 'left',
                };
            },
            // @ts-ignore
            menu: (baseStyle, state) => {
                return {
                    ...baseStyle,
                    zIndex: 999999,
                    boxShadow: 'var(--ab-cmp-select-menu__box-shadow)',
                    minWidth: `var(--ab-cmp-select-menu__min-width)`,
                    width: `${Math.max(maxLabelLength, 10)}ch`,
                    '--ab-cmp-select-menu__min-height': `min(${(props.options || []).length + (showHeaderSelectionCheckbox ? 1 : 0)} * var(--ab-grid-row-height), 20rem)`,
                    maxHeight: 'var(--ab-cmp-select-menu__max-height)',
                    maxWidth: 'var(--ab-cmp-select-menu__max-width)',
                    ...commonStyles(state),
                    ...props.menuStyle,
                };
            },
            // @ts-ignore
            option: (baseStyle, state) => {
                const style = {
                    ...baseStyle,
                    ...commonStyles(state),
                    '&:active': {
                        background: 'var(--ab-cmp-select-option-active__background)',
                    },
                };
                if (state.isSelected) {
                    style.background = 'var(--ab-cmp-select-option-active__background)';
                    style.color = 'var(--ab-cmp-select-option-active__color)';
                }
                if (state.isFocused) {
                    style.background = 'var(--ab-cmp-select-option-focused__background)';
                }
                return style;
            },
            // @ts-ignore
            input: (baseStyle, state) => {
                return {
                    ...baseStyle,
                    padding: props.size === 'small' ? 0 : baseStyle.padding,
                    color: 'var(--ab-cmp-select__color)',
                };
            },
            valueContainer: (baseStyle) => {
                return {
                    ...baseStyle,
                    padding: props.size === 'small' ? `0 var(--ab-space-1)` : baseStyle.padding,
                    ...props.styles?.valueContainer,
                };
            },
            // @ts-ignore
            singleValue: (baseStyle, state) => {
                return {
                    ...baseStyle,
                    ...commonStyles(state),
                };
            },
            // @ts-ignore
            control: (baseStyle, state) => {
                return {
                    ...baseStyle,
                    ...commonStyles(state), // height: 30,
                    minHeight: props.size === 'small' ? 0 : '100%',
                    boxShadow: state.isFocused ? 'var(--ab-cmp-select-focused__box-shadow)' : 'none',
                    outline: state.isFocused ? 'var(--ab-cmp-select-focused__outline)' : 'none',
                    border: 'var(--ab-cmp-select__border)',
                    borderRadius: 'var(--ab-cmp-select__border-radius)',
                    '&:hover': {
                        border: 'var(--ab-cmp-select__border)',
                    },
                    ...props.styles?.control,
                };
            },
            // @ts-ignore
            dropdownIndicator: (baseStyle) => {
                return {
                    ...baseStyle,
                    padding: 0,
                    ...props.styles?.dropdownIndicator,
                };
            },
            // @ts-ignore
            clearIndicator: (baseStyle) => {
                return {
                    ...baseStyle,
                    padding: '2px 3px',
                };
            },
            multiValue: (baseStyle) => {
                return {
                    ...baseStyle,
                    color: 'var(--ab-cmp-select__color)',
                    background: 'var(--ab-cmp-select-multi-value__background)',
                    border: 'var(--ab-cmp-select__border)',
                };
            },
            multiValueLabel: (baseStyle) => {
                return {
                    ...baseStyle,
                    ...fontCommonStyles,
                    color: 'var(--ab-cmp-select__color)',
                };
            },
        } }));
};
