import React, { FC } from 'react';
import { Select } from '@orchard/frontend-react-components';
import cx from 'classnames';
import { flatMap, get } from 'lodash';

export interface DropdownOption<T extends object = object> {
    value: string;
    label: string;
    disabled?: boolean;
    data?: T;
    type?: string;
}

export interface DropdownGroup<T extends object = object> {
    label: string;
    options?: DropdownOption<T>[];
}

export type InputChangeCallback = (term: string, actions: { action: string }) => void;

export interface DropdownOptionFormatContext {
    context: 'value' | 'menu';
}

export interface DropdownProps<T extends object> {
    name: string;
    className?: string;
    value?: null | string | string[] | DropdownOption<T> | DropdownOption<T>[];
    isPlainValue?: boolean;
    options: DropdownOption<T>[] | DropdownGroup<T>[];
    onChange?: (name: string, value: string, data?: T) => void
    onInputChange?: InputChangeCallback;
    clearable?: boolean;
    enabled?: boolean;
    placeholder?: string;
    closeMenuOnSelect?: boolean;
    isSearchable?: boolean;
    maxMenuHeight?: number;
    formatOptionLabel?: (option: DropdownOption<T>, context: DropdownOptionFormatContext) => React.ReactNode;
    loading?: boolean;
}

export const CLASSNAME = 'Dropdown';
export const CLASSNAME_OPTION = `${CLASSNAME}-option`;
const MAX_MENU_HEIGHT = 350;

interface SelectProps {
    isClearable: boolean;
    onChange: (option?: DropdownOption) => void;
    onInputChange?: InputChangeCallback;
    placeholder: string;
    isDisabled: boolean;
    isOptionDisabled: (option?: DropdownOption) => boolean;
    closeMenuOnSelect?: boolean;
    isSearchable?: boolean;
    maxMenuHeight?: number;
    formatOptionLabel?: (option: DropdownOption, context: DropdownOptionFormatContext) => React.ReactNode;
    isLoading?: boolean;
}
const SelectEx = Select as (typeof Select & FC<SelectProps>);

export const defaultOptionLabelFormatter = (optionsOrGroups: DropdownOption[] | DropdownGroup[]) => {
    const hasGroups = optionsOrGroups.some((group: DropdownGroup) => group.options !== undefined);
    const options = !hasGroups
        ? optionsOrGroups as DropdownOption[]
        : flatMap<DropdownOption | DropdownGroup, DropdownOption>(optionsOrGroups,
            group => get(group, 'options') ?? [group]);

    return (option: DropdownOption) => {
        const originalOption = options.find(item => item.value === option.value);
        const { label } = (originalOption ?? option);

        return (
            <div className={CLASSNAME_OPTION}>{label}</div>
        );
    };
};

function Dropdown<T extends object = object>({
    name,
    className,
    value,
    options,
    onChange,
    onInputChange,
    isPlainValue = true,
    clearable = false,
    enabled = true,
    placeholder,
    closeMenuOnSelect = true,
    maxMenuHeight = MAX_MENU_HEIGHT,
    isSearchable = true,
    formatOptionLabel,
    loading
}: DropdownProps<T>) {
    const valueProps = isPlainValue ? { selectedValue: value } : { value };
    const optionLabelFormatter = formatOptionLabel ?? defaultOptionLabelFormatter(options);

    return (
        <div className={cx(CLASSNAME, className)} data-testid={`${CLASSNAME}_${name}`}>
            <SelectEx
                {...valueProps}
                options={options}
                isClearable={clearable}
                onChange={(option: DropdownOption<T>) => onChange && onChange(name, option?.value, option?.data)}
                onInputChange={onInputChange}
                placeholder={placeholder}
                isDisabled={!enabled}
                isOptionDisabled={(option: DropdownOption) => option.disabled}
                isSearchable={isSearchable}
                maxMenuHeight={maxMenuHeight}
                closeMenuOnSelect={closeMenuOnSelect}
                formatOptionLabel={optionLabelFormatter}
                isLoading={loading}
            />
        </div>
    );
}

export default Dropdown;
