import * as stylex from "@stylexjs/stylex";
import { type PropsWithChildren, memo } from "react";

import { a11y, interaction, reset } from "./mixins";
import { controlColor } from "./theme.stylex";
import { font, fontSize, size } from "./tokens.stylex";

export type SelectProps =
	| UncontrolledMultipleSelectProps
	| UncontrolledSingleSelectProps
	| ControlledSingleSelectProps
	| ControlledMultipleSelectProps;

/**
 * @remarks Intentionally uses a native <select> element with options/optgroups under the hood.
 *          On mobile devices, it offers a better native select experience. And it's hard to get this right with a custom select component. We just style the top <select>.
 */
export interface BaseSelectProps extends PropsWithChildren {
	name?: string;

	size?: "small" | "medium" | "large";

	required?: boolean;
	disabled?: boolean;
	autoComplete?: string;
	autoFocus?: boolean;
}

export interface UncontrolledSingleSelectProps extends BaseSelectProps {
	multiple?: false | undefined;
	defaultValue?: string;
}

export interface UncontrolledMultipleSelectProps extends BaseSelectProps {
	multiple: true;
	defaultValue?: string[];
}

export interface ControlledSingleSelectProps
	extends UncontrolledSingleSelectProps {
	value: string;
	onChange: (value: string) => void;
}

export interface ControlledMultipleSelectProps
	extends UncontrolledMultipleSelectProps {
	value: string[];
	onChange: (value: readonly string[]) => void;
}

const styles = stylex.create({
	select: {
		// ask the browser to render it in its native dark mode. We hope that this sets the dropdown style to dark mode.
		colorScheme: "dark",

		background: {
			default: controlColor.inputBackground,
			":focus": controlColor.inputActiveBackground,
			":disabled": controlColor.inputDisabledBackground,
		},

		borderWidth: "1px",
		borderStyle: "solid",
		borderColor: {
			default: controlColor.inputBorder,
			":focus": controlColor.inputActiveBorder,
			// https://web.dev/articles/user-valid-and-user-invalid-pseudo-classes
			":user-invalid": controlColor.inputInvalidBorder,
			":disabled": controlColor.inputDisabledBorder,
		},

		borderRadius: 0,
		color: {
			default: controlColor.inputColor,
			":focus": controlColor.inputActiveColor,
			":disabled": controlColor.inputDisabledColor,
		},

		width: "100%",
		fontFamily: font.main,

		"::placeholder": {
			color: controlColor.inputPlaceholderColor,
		},

		outline: {
			":focus": "none",
		},
		cursor: {
			":disabled": "not-allowed",
		},
	},
});

const sizeVariants = stylex.create({
	small: {
		padding: `${size.rem1} ${size.rem2}`,
		fontSize: fontSize.body,
	},
	medium: {
		padding: `${size.px2} ${size.px3}`,
		fontSize: size.rem4,
		fontWeight: 400,
	},
	large: {
		padding: `${size.rem1} ${size.rem2}`,
		fontSize: size.rem6, // TODO: ASK-UX:So we have small/large text fields?
	},
});

export const Select = memo(function Select(props: SelectProps) {
	return (
		<select
			{...stylex.props(
				reset.buttonStyle,
				interaction.disableTapHighlight,
				a11y.defaultOutline,
				styles.select,
				sizeVariants[props.size ?? "small"],
			)}
			name={props.name}
			defaultValue={props.defaultValue}
			required={props.required}
			disabled={props.disabled}
			autoComplete={props.autoComplete}
			// biome-ignore lint/a11y/noAutofocus: We're just passing it down
			autoFocus={props.autoFocus}
			multiple={"multiple" in props && props.multiple}
			value={"value" in props ? props.value : undefined}
			onChange={
				"onChange" in props
					? // biome-ignore lint/suspicious/noExplicitAny: passing "multiple" fixes this
						(e) => props.onChange(e.currentTarget.value as any)
					: undefined
			}
		>
			{props.children}
		</select>
	);
});

export interface SelectOptionGroupProps extends PropsWithChildren {
	label: string;
	disabled?: boolean;
}
export const SelectOptionGroup = memo(function SelectOptionGroup(
	props: SelectOptionGroupProps,
) {
	return <optgroup {...props} />;
});

export interface SelectOptionProps extends PropsWithChildren {
	value: string;
	disabled?: boolean;
}
export const SelectOption = memo(function SelectOption(
	props: SelectOptionProps,
) {
	return <option {...props} />;
});
