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

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

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

	menuItemProps?: Omit<MenuItemProps, 'value'>;
}

type ExtractOptionValue<TOptions extends readonly MultiSelectOption<any>[]> =
	TOptions[number] extends MultiSelectOption<infer V> ? V : never;

type MultiSelectValue<TOptions extends readonly MultiSelectOption<any>[]> = ExtractOptionValue<TOptions>[];

export type MultiSelectProps<TOptions extends readonly MultiSelectOption<any>[] = []> = Omit<
	MuiSelectProps<MultiSelectValue<TOptions>>,
	'value' | 'onChange' | 'multiple'
> & {
	value: MultiSelectValue<TOptions>;
	onChange?: (
		value: MultiSelectValue<TOptions>,
		event: SelectChangeEvent<MultiSelectValue<TOptions>>,
		child: React.ReactNode
	) => void;
	/**
	 * @default false
	 */
	checkbox?: boolean;
	/**
	 * @default false
	 */
	chip?: boolean;
	placeholder?: ReactNode;

	helperText?: ReactNode;
	options: TOptions;

	slotProps?: {
		formControl?: Omit<FormControlProps, 'name' | 'disabled' | 'error' | 'children' | 'defaultValue' | 'required'>;
		helperText?: FormHelperTextProps;
		inputLabel?: InputLabelProps;
		menuItemProps?: Omit<MenuItemProps, 'value' | 'key'>;
		checkbox?: Omit<CheckboxProps, 'checked'>;
		chip?: Omit<ChipProps, 'key'>;
	};
};

function MultiSelect<TOptions extends readonly MultiSelectOption<any>[] = []>(inProps: MultiSelectProps<TOptions>) {
	const {
		value,
		disabled,
		error,
		required,
		slotProps,
		helperText,
		label,
		children,
		options,
		variant,
		placeholder,
		fullWidth = true,
		checkbox = false,
		chip = false,
		onChange,
		MenuProps,
		displayEmpty,
		renderValue,
		size,
		color,
		...other
	} = useDefaultProps({ name: 'ReevolveMultiSelect', props: inProps });

	const selectLabelId = useId();

	const onChangeHandler: MuiSelectProps<MultiSelectValue<TOptions>>['onChange'] = (event, child) => {
		const value = event.target.value as MultiSelectValue<TOptions>;

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

	const _value = value ?? [];

	return (
		<FormControl
			{...slotProps?.formControl}
			variant={variant}
			fullWidth={fullWidth}
			required={required}
			error={error}
			disabled={disabled}
			size={size}
			color={color}
		>
			{label && (
				<InputLabel
					{...slotProps?.inputLabel}
					{...((!!placeholder || displayEmpty) && { shrink: true })}
					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}
				renderValue={(selected) => {
					if (renderValue) {
						return renderValue(selected);
					}

					const selectedItems = options.filter((item) => selected.includes(item.value));

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

					if (chip) {
						return (
							<Box sx={{ gap: 0.5, display: 'flex', flexWrap: 'wrap', margin: -0.125 }}>
								{selectedItems.map((item) => (
									<Chip
										size="small"
										variant="filled"
										label={item.label}
										{...slotProps?.chip}
										key={String(item.value)}
									/>
								))}
							</Box>
						);
					}

					return selectedItems.map((item) => item.label).join(', ');
				}}
				multiple
				labelId={selectLabelId}
				value={_value}
				onChange={onChangeHandler}
				label={label}
				slotProps={{
					input: slotProps?.input,
					root: slotProps?.root
				}}
				displayEmpty={!!placeholder || displayEmpty}
				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}

				{placeholder && (
					<MenuItem
						disabled
						value=""
					>
						{placeholder}
					</MenuItem>
				)}

				{options.map((option) => (
					<MenuItem
						{...slotProps?.menuItemProps}
						{...option?.menuItemProps}
						key={String(option.value)}
						value={option.value as string | number}
					>
						{checkbox && (
							<Checkbox
								size="small"
								disableRipple
								checked={_value.includes(option.value)}
								{...slotProps?.checkbox}
							/>
						)}

						{option.label}
					</MenuItem>
				))}
			</MuiSelect>

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

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

export default MultiSelect;
