import React, { forwardRef, useCallback, useEffect, useState } from 'react';
import { formatMessage } from '@theorchard/suite-i18n';
import cx from 'classnames';
import Select from 'react-select';
import CreatableSelect from 'react-select/creatable';
import { CLASS_NAME, MAX_MENU_HEIGHT, MAX_MENU_WIDTH, MIN_MENU_WIDTH } from './constants';
import {
    defaultOptionLabelFormatter,
    getNewValue,
    getNewValueOption,
    getSelectedOptions,
} from './utils';
import type { DropdownCustomProps, DropdownOption, DropdownSelectRef } from './types';
import type { OnChangeValue, ActionMeta, GroupBase } from 'react-select';

function DropdownCustom<
    S extends 'Select' | 'SelectCreatable',
    T extends DropdownOption = DropdownOption,
    M extends boolean = false,
    G extends GroupBase<T> = GroupBase<T>,
>(
    {
        alignRight,
        className,
        formatOptionLabel,
        maxMenuHeight = MAX_MENU_HEIGHT,
        maxMenuWidth = MAX_MENU_WIDTH,
        minMenuWidth = MIN_MENU_WIDTH,
        name,
        options,
        separateFirstOption,
        testId = name ? `${CLASS_NAME}_${name}` : CLASS_NAME,
        selectedValue,
        onValueChanged,
        onChange,
        value,
        type,
        styles,
        isSelectable = true,
        ...props
    }: DropdownCustomProps<S, T, M, G>,
    ref: DropdownSelectRef
) {
    const [controlled, setControlled] = useState(!!value || !!selectedValue);

    const optionLabelFormatter = formatOptionLabel ?? defaultOptionLabelFormatter(options);

    const selectedOptions = value ?? getSelectedOptions<T>(selectedValue, options);

    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 === 'Select' ? Select : CreatableSelect;

    useEffect(() => {
        if (value || selectedValue) setControlled(true);
    }, [value, selectedValue]);

    return (
        <div
            className={cx(CLASS_NAME, className, {
                alignRight,
                separateFirstOption,
            })}
            data-testid={testId}
            // Prevent event from bubbling into document and closing menu again
            onMouseDown={(e) => e.stopPropagation()}
        >
            <SelectComponent
                // The react-select library does not export everything we need to wrap it properly.
                // This makes it especially hard for using refs.
                // So as a way around the problem, we use "any" for now.
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                ref={ref as any}
                name={name}
                options={options}
                formatOptionLabel={optionLabelFormatter}
                isOptionDisabled={(option) => !!option.disabled}
                loadingMessage={() => formatMessage('loading_pleaseWait')}
                maxMenuHeight={maxMenuHeight}
                placeholder=""
                menuPlacement="auto"
                styles={{
                    menu: (base) => ({
                        ...base,
                        maxWidth: maxMenuWidth,
                        minWidth: minMenuWidth,
                    }),
                    ...styles,
                }}
                {...props}
                value={controlled || !isSelectable ? selectedOptions ?? null : selectedOptions}
                classNamePrefix="Select"
                className={cx('Select', type !== 'Select' && type)}
                onChange={handleChange}
            />
        </div>
    );
}

const DropdownWithRef = forwardRef(DropdownCustom) as typeof DropdownCustom & {
    displayName: string;
};

DropdownWithRef.displayName = 'DropdownCustom';

export { DropdownWithRef as DropdownCustom };
