import { ReactNode, useId } from 'react';
import {
	Box,
	Divider,
	FormControl,
	FormControlProps,
	FormHelperText,
	InputLabel,
	InputLabelProps,
	MenuItem,
	MenuItemProps,
	PaperProps,
	Select as MuiSelect,
	SelectChangeEvent,
	SelectProps as MuiSelectProps
} from '@mui/material';
import { FormHelperTextProps } from '@mui/material';
import { isFunction } from 'lodash';

import { useDefaultProps } from '../../hooks';
import { useLocalization } from '../../localization';

export interface SelectOption<Value = unknown> {
	value: Value;
	label: ReactNode;

	menuItemProps?: Omit<MenuItemProps, 'value'>;
}
type ExtractOptionValue<TOptions extends readonly SelectOption<any>[]> =
	TOptions[number] extends SelectOption<infer V> ? V : never;

type SelectValue<
	TOptions extends readonly SelectOption<any>[],
	TEmptyOption,
	TEmptyValue = null
> = TEmptyOption extends true ? TEmptyValue | ExtractOptionValue<TOptions> : ExtractOptionValue<TOptions>;

export type SelectProps<
	TOptions extends readonly SelectOption<any>[] = [],
	TEmptyOption extends boolean = false,
	TEmptyValue = null
> = {
	options: TOptions;
	/**
	 * @default null
	 */
	emptyOptionValue?: TEmptyValue;
	/**
	 * @default false
	 */
	emptyOption?: TEmptyOption;
	value: Readonly<SelectValue<TOptions, TEmptyOption, TEmptyValue>>;
	onChange?: (
		value: Readonly<SelectValue<TOptions, TEmptyOption, TEmptyValue>>,
		event: SelectChangeEvent<Readonly<SelectValue<TOptions, TEmptyOption, TEmptyValue>>>,
		child: React.ReactNode
	) => void;
	helperText?: ReactNode;
	/**
	 * @default translate('select.emptyOptionLabel')
	 */
	emptyOptionLabel?: ReactNode;
	placeholder?: ReactNode;
	slotProps?: {
		formControl?: Omit<FormControlProps, 'name' | 'disabled' | 'error' | 'children' | 'defaultValue' | 'required'>;
		helperText?: FormHelperTextProps;
		inputLabel?: InputLabelProps;
		menuItemProps?: Omit<MenuItemProps, 'value' | 'key'>;
	};
} & Omit<MuiSelectProps<Readonly<SelectValue<TOptions, TEmptyOption, TEmptyValue>>>, 'onChange' | 'value'>;

function Select<
	TOptions extends readonly SelectOption<any>[] = [],
	TEmptyOption extends boolean = false,
	TEmptyValue = null
>(inProps: SelectProps<TOptions, TEmptyOption, TEmptyValue>) {
	const { translate } = useLocalization();

	const {
		value,
		disabled,
		required,
		slotProps,
		helperText,
		label,
		children,
		options,
		displayEmpty,
		placeholder,
		emptyOption = false,
		emptyOptionValue = null,
		emptyOptionLabel = translate('select.emptyOptionLabel'),
		variant,
		fullWidth = true,
		onChange,
		MenuProps,
		error,
		renderValue,
		size,
		color,
		...other
	} = useDefaultProps({ name: 'ReevolveSelect', props: inProps });

	const selectLabelId = useId();

	const onChangeHandler: MuiSelectProps<Readonly<SelectValue<TOptions, TEmptyOption, TEmptyValue>>>['onChange'] = (
		event,
		child
	) => {
		let value = event.target.value as any;

		if (emptyOption && value === '') {
			value = emptyOptionValue;
		}

		onChange?.(value, event, child);
	};

	return (
		<FormControl
			{...slotProps?.formControl}
			variant={variant}
			fullWidth={fullWidth}
			required={required}
			error={error}
			disabled={disabled}
			size={size}
			color={color}
		>
			{label && (
				<InputLabel
					{...((!!placeholder || emptyOption || displayEmpty) && { shrink: true })}
					{...slotProps?.inputLabel}
					id={selectLabelId}
					variant={variant}
					required={required}
					error={error}
					disabled={disabled}
					size={size}
					color={color}
				>
					{label}
				</InputLabel>
			)}

			<MuiSelect
				{...other}
				variant={variant}
				fullWidth={fullWidth}
				required={required}
				error={error}
				disabled={disabled}
				size={size}
				color={color}
				labelId={selectLabelId}
				value={value ?? ''}
				onChange={onChangeHandler}
				displayEmpty={!!placeholder || emptyOption || displayEmpty}
				label={label}
				slotProps={{
					input: slotProps?.input,
					root: slotProps?.root
				}}
				renderValue={(selected: any) => {
					if (renderValue) {
						return renderValue(selected);
					}

					const selectedOptionLabel = options.find(({ value }) => value === selected)?.label;

					if (!selectedOptionLabel) {
						if (placeholder) {
							return <Box sx={{ color: 'text.disabled' }}>{placeholder}</Box>;
						}

						if (emptyOption && selected === '') {
							return emptyOptionLabel;
						}
					}

					return selectedOptionLabel;
				}}
				MenuProps={{
					...MenuProps,
					slotProps: {
						...MenuProps?.slotProps,
						paper: {
							...MenuProps?.slotProps?.paper,
							sx: (theme) => ({
								maxHeight: 220,
								...(isFunction((MenuProps?.slotProps?.paper as PaperProps)?.sx)
									? ((MenuProps!.slotProps!.paper as PaperProps).sx as Function)(theme)
									: (MenuProps?.slotProps?.paper as PaperProps)?.sx)
							})
						}
					}
				}}
			>
				{children}

				{emptyOption && <MenuItem value={(emptyOptionValue as any) ?? ''}>{emptyOptionLabel}</MenuItem>}
				{emptyOption && <Divider sx={{ borderStyle: 'dashed' }} />}

				{options.map((option) => (
					<MenuItem
						{...slotProps?.menuItemProps}
						{...option?.menuItemProps}
						key={String(option.value)}
						value={option.value as string | number}
					>
						{option.label}
					</MenuItem>
				))}
			</MuiSelect>

			{helperText && <FormHelperText {...slotProps?.helperText}>{helperText}</FormHelperText>}
		</FormControl>
	);
}

declare module '@mui/material' {
	interface Components {
		ReevolveSelect?: {
			defaultProps?: Partial<SelectProps>;
		};
	}
}

export default Select;
