'use no memo'; // composition props usually invalidate the generated wrapper caches

import {
	Children,
	cloneElement,
	type ComponentType,
	Fragment,
	isValidElement,
	type ReactNode,
	type SVGProps,
} from 'react';

import { Collapsible } from '@base-ui/react/collapsible';
import { Switch } from '@base-ui/react/switch';
import { clsx } from 'clsx';

import * as Select from '#/components/Select';
import * as styles from '#/components/SettingsCards.css';
import { Spinner } from '#/components/Spinner';
import { Text } from '#/components/Text';
import { type LinkProps, useInternalLink } from '#/components/web/Link';
import * as Skele from '#/components/web/Skeleton';

import ChevronDownIcon from '#/icons/central/ChevronBottom_round_outlined_radius1_stroke2.svg';
import ChevronRightIcon from '#/icons/central/ChevronRight_round_outlined_radius1_stroke2.svg';
import ChevronUpIcon from '#/icons/central/ChevronTop_round_outlined_radius1_stroke2.svg';

/**
 * card-sectioned settings vocabulary: a list of titled section cards, each holding whole-row button, switch,
 * or select rows. compose a row's leading icon and label; trailing controls are supplied by the row type.
 */
export function List({ children }: { children: ReactNode }) {
	return <div className={styles.list}>{children}</div>;
}

/** titled card where children rows are clipped to corners and separated by automatic hairline dividers */
export function Section({
	bodyText,
	children,
	footnoteText,
	titleText,
}: {
	/** Explanatory body copy rendered above the card, below the title if there is one. */
	bodyText?: ReactNode;
	children: ReactNode;
	/** Muted helper text rendered under the card. */
	footnoteText?: ReactNode;
	titleText?: ReactNode;
}) {
	const rows = Children.toArray(children).filter(isValidElement<{ className?: string }>);
	return (
		<div className={styles.section}>
			{(titleText != null || bodyText != null) && (
				<div className={styles.sectionHeading}>
					{titleText != null && (
						<Text weight="semiBold" color="textContrastHigh">
							{titleText}
						</Text>
					)}
					{bodyText != null && (
						<Text size="md_sub" color="textContrastMedium">
							{bodyText}
						</Text>
					)}
				</div>
			)}

			<div className={styles.card}>
				{rows.map((row, i) => (
					<Fragment key={row.key ?? i}>
						{i > 0 && <div className={styles.divider} />}
						{/* round only the card's end rows so their hover/focus follow the card corner; computed
						    at the element level (not via CSS `:first-child`) since Switch rows emit sibling DOM inputs */}
						{cloneElement(row, {
							className: clsx(
								row.props.className,
								i === 0 && styles.rowFirst,
								i === rows.length - 1 && styles.rowLast,
							),
						})}
					</Fragment>
				))}
			</div>
			{footnoteText != null && (
				<Text className={styles.sectionFootnote} size="sm" color="textContrastMedium">
					{footnoteText}
				</Text>
			)}
		</div>
	);
}

export function Icon({ icon: IconCmp }: { icon: ComponentType<SVGProps<SVGSVGElement>> }) {
	return <IconCmp className={styles.icon} />;
}

/**
 * the row's primary text, optionally over a muted second line.
 *
 * renders the two lines as separate row-grid items so the subtitle can span the full row width beneath the
 * trailing control.
 *
 * @param loading pass true to show a skeleton bar in the subtitle's place while a drill-in row's value is
 *   fetched.
 */
export function Label({
	loading,
	subtitleText,
	titleText,
}: {
	loading?: boolean;
	subtitleText?: ReactNode;
	titleText: ReactNode;
}) {
	return (
		<>
			<Text className={styles.title} size="md" weight="medium" color="text">
				{titleText}
			</Text>
			{loading ? (
				<div className={styles.subtitle}>
					<Skele.Text color="contrast_100" size="md_sub" width={140} />
				</div>
			) : (
				subtitleText != null && (
					<Text className={styles.subtitle} size="md_sub" color="textContrastMedium">
						{subtitleText}
					</Text>
				)
			)}
		</>
	);
}

/**
 * a row button that fires an action on press and displays a trailing chevron.
 *
 * @param color the row color variant. defaults to `secondary`.
 * @param valueText the row's current value, shown beside the chevron. prefer this over a {@link Label}
 *   subtitle for a short value that reads as an answer to the row's title.
 */
export function ButtonRow({
	children,
	className,
	color = 'secondary',
	label,
	onPress,
	valueText,
}: {
	children: ReactNode;
	className?: string;
	color?: 'primary_subtle' | 'secondary';
	label: string;
	onPress: () => void;
	valueText?: string;
}) {
	return (
		<button
			type="button"
			aria-label={label}
			onClick={onPress}
			className={clsx(
				styles.row,
				styles.rowInteractive,
				color === 'primary_subtle' && styles.rowPrimarySubtle,
				className,
			)}
		>
			{children}
			<RowTrailing chevron={ChevronRightIcon} text={valueText} />
		</button>
	);
}

/**
 * a bare navigating row `<a>` with interactive chrome (hover/focus/reset).
 *
 * the caller provides the row layout (a {@link row}/{@link rowPlain} class via `className`) and every slot.
 * use {@link LinkRow} for the common icon/label grid form with a trailing chevron.
 */
export function LinkRowRaw({
	children,
	className,
	label,
	to,
}: {
	children: ReactNode;
	className?: string;
	label: string;
	to: LinkProps['to'];
}) {
	const { href, onClick } = useInternalLink({ to });
	return (
		<a href={href} aria-label={label} className={clsx(styles.rowInteractive, className)} onClick={onClick}>
			{children}
		</a>
	);
}

/**
 * A row that navigates on press, rendered as an `<a>`; the whole row is the link, with a trailing forward
 * chevron.
 */
export function LinkRow({
	children,
	className,
	label,
	to,
}: {
	children: ReactNode;
	className?: string;
	label: string;
	to: LinkProps['to'];
}) {
	return (
		<LinkRowRaw className={clsx(styles.row, className)} label={label} to={to}>
			{children}
			<RowTrailing chevron={ChevronRightIcon} />
		</LinkRowRaw>
	);
}

/**
 * an expandable row with a height-animated panel and flipping chevron.
 *
 * @param children content shown when expanded
 * @param panel whether to divide children into rows or render them as one block
 * @param trailing content shown beside the chevron while collapsed
 */
export function CollapsibleRow({
	children,
	className,
	icon,
	label,
	onOpenChange,
	open,
	panel = 'rows',
	titleText,
	trailing,
}: {
	children: ReactNode;
	className?: string;
	icon?: ComponentType<SVGProps<SVGSVGElement>>;
	label: string;
	onOpenChange: (open: boolean) => void;
	open: boolean;
	panel?: 'body' | 'rows';
	titleText: ReactNode;
	trailing?: ReactNode;
}) {
	// Section rounds this disclosure's card corners by injecting rowFirst/rowLast into our className. We push
	// that rounding onto the actual corner rows (trigger, last panel row) instead of clipping with
	// `overflow: hidden`, so their focus rings follow the corner rather than being clipped square.
	const roundTop = className?.includes(styles.rowFirst) ?? false;
	const roundBottom = className?.includes(styles.rowLast) ?? false;
	return (
		<Collapsible.Root className={className} onOpenChange={onOpenChange} open={open}>
			<Collapsible.Trigger
				render={
					<button
						aria-label={label}
						className={clsx(
							styles.row,
							styles.rowInteractive,
							roundTop && styles.rowFirst,
							!open && roundBottom && styles.rowLast,
						)}
						type="button"
					/>
				}
			>
				{icon != null && <Icon icon={icon} />}
				<Text className={styles.title} color="text" size="md" weight="medium">
					{titleText}
				</Text>
				<span className={styles.trailing}>
					{!open && trailing}
					{open ? (
						<ChevronUpIcon className={styles.chevron} />
					) : (
						<ChevronDownIcon className={styles.chevron} />
					)}
				</span>
			</Collapsible.Trigger>
			<Collapsible.Panel className={styles.panel}>
				{panel === 'rows' ? (
					<PanelRows roundBottom={roundBottom}>{children}</PanelRows>
				) : (
					<div className={styles.panelBody}>{children}</div>
				)}
			</Collapsible.Panel>
		</Collapsible.Root>
	);
}

function PanelRows({ children, roundBottom }: { children: ReactNode; roundBottom: boolean }) {
	const rows = Children.toArray(children).filter(isValidElement<{ className?: string }>);
	return rows.map((row, i) => {
		const roundedRow =
			i === rows.length - 1 && roundBottom
				? cloneElement(row, { className: clsx(row.props.className, styles.rowLast) })
				: row;
		return (
			<Fragment key={row.key ?? i}>
				<div className={styles.divider} />
				{roundedRow}
			</Fragment>
		);
	});
}

/**
 * row that toggles a boolean on press.
 *
 * @param loading show a spinner and freeze the row while the change is being persisted
 */
export function SwitchRow({
	children,
	className,
	disabled,
	label,
	loading,
	onChange,
	value,
}: {
	children: ReactNode;
	className?: string;
	disabled?: boolean;
	label: string;
	loading?: boolean;
	onChange: (value: boolean) => void;
	value: boolean;
}) {
	return (
		<Switch.Root
			aria-label={label}
			checked={value}
			disabled={disabled || loading}
			onCheckedChange={onChange}
			className={clsx(styles.row, styles.rowInteractive, className)}
		>
			{children}
			<span className={styles.trailing}>
				{loading && <Spinner color="default" label={label} size="sm" />}
				<span className={styles.switchTrack}>
					<span className={styles.switchThumb} />
				</span>
			</span>
		</Switch.Root>
	);
}

/** row that opens a dropdown on press, where the whole row acts as the trigger showing the current value. */
export function SelectRow<T = string>({
	children,
	className,
	items,
	label,
	onValueChange,
	value,
}: {
	children: ReactNode;
	className?: string;
	items: Select.SelectItem<T>[];
	label: string;
	onValueChange: (value: T) => void;
	value: T;
}) {
	const selected = items.find((item) => item.value === value);
	return (
		<Select.Root items={items} value={value} onValueChange={onValueChange}>
			<Select.Trigger
				render={
					<button
						type="button"
						aria-label={label}
						className={clsx(styles.row, styles.rowInteractive, className)}
					/>
				}
			>
				{children}
				<RowTrailing chevron={ChevronDownIcon} text={selected?.label} />
			</Select.Trigger>
			<Select.Content
				align="end"
				matchTriggerWidth={false}
				items={items}
				renderItem={({ label: itemLabel, value: itemValue }) => (
					<Select.Item value={itemValue} label={itemLabel}>
						<Select.ItemIndicator />
						<Select.ItemText>{itemLabel}</Select.ItemText>
					</Select.Item>
				)}
			/>
		</Select.Root>
	);
}

/**
 * value shown beside a settings row's chevron.
 *
 * @param text value to display
 */
export function Value({ text }: { text: string }) {
	return (
		<Text align="right" className={styles.value} color="textContrastMedium" numberOfLines={1} size="md_sub">
			{text}
		</Text>
	);
}

/** a row's trailing slot: an optional current value, right-aligned against the row's own chevron. */
function RowTrailing({
	chevron: ChevronCmp,
	text,
}: {
	chevron: ComponentType<SVGProps<SVGSVGElement>>;
	text?: string;
}) {
	return (
		<span className={styles.trailing}>
			{text != null && <Value text={text} />}
			<ChevronCmp className={styles.chevron} />
		</span>
	);
}
