import React, { useCallback } from 'react';
import { formatMessage } from '@theorchard/suite-i18n';
import cx from 'classnames';
import debounce from 'lodash.debounce';
import AsyncSelect from 'react-select/async';
import AsyncCreatableSelect from 'react-select/async-creatable';
import {
    CLASS_NAME as BASE_CLASS_NAME,
    MAX_MENU_WIDTH,
    MIN_MENU_WIDTH,
} from '../dropdown/constants';
import { defaultOptionLabelFormatter, getNewValue, getNewValueOption } from '../dropdown/utils';
import {
    createLoadOptionsHandler,
    defaultNoOptionsMessage,
    DropdownIndicator,
    IndicatorSeparator,
} from './utils';
import type { SearchDropdownCustomProps } from './types';
import type { DropdownOption } from '../dropdown/types';
import type { ActionMeta, GroupBase, OnChangeValue, OptionsOrGroups } from 'react-select';

const DEBOUNCE_TIME = 400;
const MAX_MENU_HEIGHT = 800;
const CLASS_NAME = 'SearchDropdown';

export function SearchDropdownCustom<
    S extends 'AsyncSelect' | 'AsyncSelectCreatable',
    T extends DropdownOption = DropdownOption,
    M extends boolean = false,
    G extends GroupBase<T> = GroupBase<T>,
>({
    alignRight,
    separateFirstOption,
    className,
    debounceTime = DEBOUNCE_TIME,
    formatOptionLabel,
    isMulti,
    isClearable = true,
    isSelectable = true,
    onLoadOptions,
    maxMenuHeight = MAX_MENU_HEIGHT,
    maxMenuWidth = MAX_MENU_WIDTH,
    minMenuWidth = MIN_MENU_WIDTH,
    name,
    noOptionsMessage = defaultNoOptionsMessage,
    onChange,
    onValueChanged,
    placeholder,
    testId = name ? `${CLASS_NAME}_${name}` : CLASS_NAME,
    value,
    defaultValue,
    defaultOptions = false,
    type,
    ref,
    styles,
    ...rest
}: SearchDropdownCustomProps<S, T, M, G>) {
    const onLoadOptionsHandler = createLoadOptionsHandler(onLoadOptions);

    const onLoadOptionsDebounced =
        debounceTime > 0 ? debounce(onLoadOptionsHandler, debounceTime) : onLoadOptionsHandler;

    const loadOptions = (term: string, callback: (options: OptionsOrGroups<T, G>) => void) => {
        const trimmed = term?.trim();

        // We want empty searches to fire immediately
        const handler = !trimmed ? onLoadOptionsHandler : onLoadOptionsDebounced;

        handler(term, callback);
    };

    const optionLabelFormatter = formatOptionLabel ?? defaultOptionLabelFormatter();

    const handleChange = useCallback(
        (changedValue: OnChangeValue<T, M>, actionMeta: ActionMeta<T>) => {
            onChange?.(changedValue, actionMeta);

            const newValueOption = getNewValueOption(changedValue);
            const newValue = getNewValue(changedValue);
            onValueChanged?.(name ?? CLASS_NAME, newValue, newValueOption);
        },
        [name, onChange, onValueChanged]
    );

    const SelectComponent = type === 'AsyncSelect' ? AsyncSelect : AsyncCreatableSelect;

    return (
        <div
            className={cx(BASE_CLASS_NAME, CLASS_NAME, className, {
                alignRight,
                separateFirstOption,
            })}
            data-testid={testId}
        >
            <SelectComponent
                cacheOptions={false}
                components={{ DropdownIndicator, IndicatorSeparator }}
                defaultOptions={defaultOptions}
                formatOptionLabel={optionLabelFormatter}
                isClearable={isClearable}
                isMulti={isMulti}
                isOptionDisabled={(option) => !!option.disabled}
                value={isSelectable ? value : null}
                loadingMessage={() => formatMessage('loading_pleaseWait')}
                loadOptions={loadOptions}
                maxMenuHeight={maxMenuHeight}
                menuShouldScrollIntoView={false}
                noOptionsMessage={noOptionsMessage}
                placeholder={placeholder ?? ''}
                defaultValue={defaultValue}
                styles={{
                    menu: (base) => ({
                        ...base,
                        maxWidth: maxMenuWidth,
                        minWidth: minMenuWidth,
                    }),
                    ...styles,
                }}
                {...rest}
                classNamePrefix="Select"
                className={cx('Select', type)}
                onChange={handleChange}
            />
        </div>
    );
}
