import { useMultipleSelection, useCombobox } from 'downshift';
import { KeyboardEvent, useState } from 'react';
import Input from './../../atoms/Input';
import Block from './../../atoms/Block';
import Button from '../../atoms/Button';
import Text from '../../atoms/Text';
import { Close, ChevronDown, EditOff } from '@carbon/icons-react';
import style from '../../../style';

import { Option } from '../../atoms/InputSelect/InputSelect';
import { isEmpty } from 'lodash';
import Label from '../../atoms/Label';

export type SelectOption = Option<string | number | readonly string[] | undefined>;

interface CommonProps {
    /**
     * Name of the field
     */
    name?: string;
    /**
     * List of available options
     */
    options: SelectOption[];
    /**
     * Select variation that will switch between styles for the Button .
     */
    variation?: 'initial' | 'default' | 'primary' | 'secondary';
    /**
     * className for the select.
     */
    className?: string;
    /**
     * String text for the label.
     */
    label?: string;
    /**
     * String text for the placeholder or a component element.
     */
    placeholder?: string;
    /**
     * Smaller descriptive text that can be added to the input.
     */
    message?: string;
    /**
     * className for the message.
     */
    messageClassName?: string;
    /**
     * className for the wrapping Block element.
     */
    containerCss?: string;
    /**
     * Makes select required.
     */
    required?: boolean;
    /**
     * Allows to reset select value
     */
    isClearable?: boolean;
    /**
     * Disable select
     */
    isDisabled?: boolean;
}

interface SingleValue extends CommonProps {
    defaultValue?: SelectOption;
    value?: SelectOption | null;
    onChange?: (value: SelectOption | null) => void;
    isMulti?: false;
}

interface MultiValue extends CommonProps {
    defaultValue?: SelectOption[];
    value?: SelectOption[];
    onChange?: (value: SelectOption[]) => void;
    isMulti: true;
}

export type SelectProps = SingleValue | MultiValue;

function Select({
    options,
    name,
    isMulti,
    defaultValue,
    value,
    onChange,
    isClearable,
    placeholder,
    label,
    message,
    messageClassName,
    className,
    containerCss,
    required,
    isDisabled,
    variation,
}: SelectProps) {
    const [inputValue, setInputValue] = useState('');
    const initialValue = defaultValue ? (isMulti ? defaultValue : [defaultValue]) : undefined;
    const controlledValue = value ? (isMulti ? value : [value]) : undefined;

    const {
        getSelectedItemProps,
        getDropdownProps,
        addSelectedItem,
        removeSelectedItem,
        selectedItems,
        setSelectedItems,
        reset,
    } = useMultipleSelection<SelectOption>({
        initialSelectedItems: initialValue,
        itemToString: (item: SelectOption | null) => (item ? item.label : ''),
        selectedItems: controlledValue,
        onStateChange: ({ selectedItems, type }) => {
            switch (type) {
                case useMultipleSelection.stateChangeTypes.FunctionAddSelectedItem:
                case useMultipleSelection.stateChangeTypes.FunctionRemoveSelectedItem:
                case useMultipleSelection.stateChangeTypes.FunctionSetSelectedItems:
                case useMultipleSelection.stateChangeTypes.FunctionReset:
                    let updatedValue;
                    if (isMulti) {
                        updatedValue = selectedItems ?? [];
                        onChange && onChange(updatedValue);
                    } else {
                        updatedValue = selectedItems && selectedItems[0] ? selectedItems[0] : null;
                        onChange && onChange(updatedValue);
                    }

                    break;
                default:
                    break;
            }
        },
    });

    const selectedLabels = selectedItems.map((item) => item?.label);
    const filteredItems = options.filter(
        (item) =>
            selectedLabels.indexOf(item?.label) < 0 && item?.label.toLowerCase().startsWith(inputValue.toLowerCase()),
    );

    const {
        isOpen,
        getToggleButtonProps,
        getLabelProps,
        getMenuProps,
        getInputProps,
        highlightedIndex,
        getItemProps,
        selectItem,
        closeMenu,
        openMenu,
    } = useCombobox<SelectOption | null>({
        inputValue,
        items: filteredItems,
        itemToString: (item: SelectOption | null) => (item ? item.label : ''),
        onStateChange: ({ inputValue, type, selectedItem }) => {
            switch (type) {
                case useCombobox.stateChangeTypes.InputChange:
                    setInputValue(inputValue ?? '');
                    break;
                case useCombobox.stateChangeTypes.InputKeyDownEnter:
                case useCombobox.stateChangeTypes.ItemClick:
                case useCombobox.stateChangeTypes.InputBlur:
                    if (!selectedItem) {
                        break;
                    }

                    if (isMulti) {
                        addSelectedItem(selectedItem);
                    } else {
                        setSelectedItems([selectedItem]);
                    }

                    selectItem(null);
                    setInputValue('');

                    break;
                default:
                    break;
            }
        },
    });

    const hasSelectedItems = Boolean(selectedItems && selectedItems.length);

    const variationStyles = () => {
        switch (variation) {
            case 'primary':
                return style.select.root.primary;
            case 'secondary':
                return style.select.root.secondary;
            case 'default':
                return style.select.root.default;
            default:
                return style.select.root.init;
        }
    };

    const handleKeyPress = (event: KeyboardEvent<HTMLDivElement>) => {
        const pressedKey = event.key;

        if (
            !isDisabled &&
            !isOpen &&
            (pressedKey === 'ArrowDown' || pressedKey === 'Down' || pressedKey === 'ArrowUp' || pressedKey === 'Up')
        ) {
            openMenu();
        }
    };

    return (
        <Block className={`relative ${containerCss}`}>
            {label && <Label {...getLabelProps()}>{label}</Label>}
            <Block
                onKeyDown={handleKeyPress}
                tabIndex={isDisabled ? -1 : 0}
                className={`relative flex justify-between items-center  select-none ${
                    style.select.root.init
                } ${variationStyles()} ${className} py-[5.5px] `}
            >
                <Block className="relative z-10 inline-flex flex-wrap items-start pointer-events-none ">
                    {selectedItems.map((selectedItem, index) => {
                        const singleSelectedItemsProp = !isMulti
                            ? { ...getSelectedItemProps({ selectedItem, index }) }
                            : {};
                        return (
                            <Block
                                className="flex items-center mr-3 space-x-1 leading-6"
                                key={`selected-item-${index}`}
                            >
                                <Block className="flex items-center" {...singleSelectedItemsProp} tabIndex={-1}>
                                    {selectedItem.icon && <div className="mr-1">{selectedItem.icon}</div>}
                                    {selectedItem?.label}
                                </Block>
                                {isMulti && !isDisabled && (
                                    <Button
                                        {...getSelectedItemProps({ selectedItem, index })}
                                        onClick={() => removeSelectedItem(selectedItem)}
                                        type="button"
                                        className="p-[2px] bg-transparent pointer-events-auto hover:bg-gray-100 border-none"
                                        tabIndex={0}
                                    >
                                        <Close size={16} />
                                    </Button>
                                )}
                            </Block>
                        );
                    })}
                </Block>

                <Block className="relative z-10 flex items-center flex-shrink-0 space-x-2 select-none">
                    {(isClearable || isMulti) && hasSelectedItems && !isDisabled && (
                        <Button type="button" className="p-1" variation={variation} onClick={reset}>
                            <Close size={16} />
                        </Button>
                    )}
                    <Button
                        type="button"
                        className="p-1"
                        variation={variation}
                        {...getToggleButtonProps({ disabled: isDisabled })}
                        aria-label={'toggle menu'}
                    >
                        {isDisabled ? <EditOff size={16} /> : <ChevronDown size={16} />}
                    </Button>
                </Block>

                <Input
                    tabIndex={-1}
                    variation={variation}
                    placeholder="Search ..."
                    disabled={isDisabled}
                    onChange={reset}
                    {...getInputProps(getDropdownProps({ preventKeyAction: isOpen, disabled: isDisabled }))}
                    className={`absolute top-0 bottom-0 left-0 right-0  transform-none transition-none select-none ${
                        isOpen
                            ? ''
                            : 'bg-transparent text-transparent pointer-events-none placeholder-transparent placeholder-opacity-0'
                    } `}
                />
                <Block className="absolute inset-0 z-0 px-3 py-2 " {...getToggleButtonProps({ disabled: isDisabled })}>
                    <span className="inline-block py-px -mt-px opacity-60 text-inherit">
                        {placeholder && !hasSelectedItems ? placeholder : ''}
                    </span>
                </Block>
            </Block>
            {required && (
                <input
                    className="absolute top-0 left-0 h-px pt-2 mt-16 opacity-0 pointer-events-none"
                    tabIndex={-1}
                    autoComplete="off"
                    defaultValue={hasSelectedItems ? String(selectedItems) : ''}
                    disabled={isDisabled}
                    required
                />
            )}
            <Block
                className={` ${
                    isOpen
                        ? 'absolute select-none flex justify-start flex-col w-full mt-px overflow-y-auto max-h-[240px] shadow-xl z-30'
                        : 'hidden'
                }`}
                {...getMenuProps()}
            >
                {isOpen && (
                    <>
                        {filteredItems.map((item, index) => (
                            <Button
                                variation={variation === undefined ? 'initial' : variation}
                                className={`px-3 leading-6 border-none`}
                                highlighted={highlightedIndex === index}
                                type="button"
                                key={`${item.value}${index}`}
                                {...getItemProps({ item, index, disabled: isDisabled })}
                            >
                                {item.label}
                            </Button>
                        ))}
                        {isEmpty(filteredItems) && (
                            <Button
                                type="button"
                                variation={variation}
                                className={`px-3 leading-6 border-none`}
                                onClick={() => closeMenu()}
                            >
                                No items ...
                            </Button>
                        )}
                    </>
                )}
            </Block>
            {message && (
                <Text className={`inline-flex py-2 text-xs font-light ${messageClassName ?? ''}`}>{message}</Text>
            )}
            {selectedItems.map((selectedItem, index) => (
                <input
                    tabIndex={-1}
                    type="hidden"
                    name={name}
                    key={`selected-item-${index}`}
                    value={selectedItem.value}
                />
            ))}
        </Block>
    );
}

export default Select;
