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

import type { HTMLAttributes, ReactNode, Ref } from 'react';

import { Dialog as BaseDialog } from '@base-ui/react/dialog';
import { clsx } from 'clsx';

import * as styles from '#/components/Dialog/Popup.css';
import { NavigationEnabled } from '#/components/NavigationDisabled';
import { Text } from '#/components/Text';

import TimesIcon from '#/icons/central/CrossLarge_round_outlined_radius1_stroke2.svg';
import { m } from '#/paraglide/messages';

const stopPropagation = (e: { stopPropagation: () => void }) => e.stopPropagation();

type CardProps = {
	children: ReactNode;
	size?: 'default' | 'medium' | 'narrow' | 'wide' | 'xwide';
	/** `none` drops the card's own padding, for full-bleed content that reapplies padding per-section. */
	padding?: 'default' | 'none';
	/**
	 * body strategy. `viewport` (default) is a padded card that grows to its content while the viewport
	 * scrolls. `body` is a height-bounded flex column whose `Body`/`List` child scrolls internally, with pinned
	 * `Header`/`Footer` slots.
	 */
	scroll?: 'body' | 'viewport';
	/** `body`-scroll only: lock to max height so it doesn't shrink to fit transient loading/empty states. */
	fullHeight?: boolean;
	className?: string;
	/** Accessible name for the dialog. Redundant when the popup renders a `Title`. */
	label?: string;
};

/**
 * Portalled backdrop + scrollable viewport. Wraps a {@link Card}; use this split form (rather than the bundled
 * {@link Popup}) when you need a viewport-level sibling of the card — e.g. an outer-anchored {@link Close},
 * which must sit outside the card so the card's scale-in transform doesn't capture its `fixed` positioning.
 */
export function Viewport({ children }: { children: ReactNode }) {
	return (
		<BaseDialog.Portal className={styles.portal}>
			<BaseDialog.Backdrop className={styles.backdrop} forceRender onClick={stopPropagation} />
			<BaseDialog.Viewport className={styles.viewport} onClick={stopPropagation}>
				<NavigationEnabled>{children}</NavigationEnabled>
			</BaseDialog.Viewport>
		</BaseDialog.Portal>
	);
}

/**
 * Themed popup card. Put dialog content inside. Wrap in a {@link Viewport}, or use {@link Popup} which bundles
 * both.
 */
export function Card({
	children,
	size = 'default',
	padding = 'default',
	scroll = 'viewport',
	fullHeight,
	className,
	label,
}: CardProps) {
	return (
		<BaseDialog.Popup
			aria-label={label}
			className={clsx(styles.popup({ fullHeight, padding, scroll, size }), className)}
		>
			{children}
		</BaseDialog.Popup>
	);
}

/**
 * Portalled backdrop + scrollable viewport + themed popup card. Put dialog content inside. The common case;
 * reach for {@link Viewport} + {@link Card} directly when you need a viewport-level sibling like an outer
 * {@link Close}.
 */
export function Popup(props: CardProps) {
	return (
		<Viewport>
			<Card {...props} />
		</Viewport>
	);
}

/** Scrollable content region of a `body`-scroll Popup (below a pinned `Header`, above a pinned `Footer`). */
export function Body({
	children,
	className,
	ref,
	...props
}: {
	children: ReactNode;
	className?: string;
	ref?: Ref<HTMLDivElement>;
} & HTMLAttributes<HTMLDivElement>) {
	return (
		<div ref={ref} className={clsx(styles.body, className)} {...props}>
			{children}
		</div>
	);
}

/** Pinned action bar at the bottom of a `body`-scroll Popup. */
export function Footer({ children }: { children: ReactNode }) {
	return <div className={styles.footer}>{children}</div>;
}

/**
 * In-flow header row for a `viewport`-scroll Popup: a {@link Title} beside a trailing {@link Close}. Because
 * the close lives in the row rather than floating, a long title can never slide under it.
 */
export function TitleRow({ children }: { children: ReactNode }) {
	return <div className={styles.titleRow}>{children}</div>;
}

/** Dialog heading. */
export function Title({ children, className }: { children: ReactNode; className?: string }) {
	return (
		<BaseDialog.Title className={clsx(styles.title, className)} render={<Text size="xl" weight="semiBold" />}>
			{children}
		</BaseDialog.Title>
	);
}

/**
 * Action/button row. Author children in reading order (secondary → primary); the layout knobs are orthogonal.
 * `direction` sets the axis — `row` (default), `column`, or `responsive` (a column on narrow screens, a row
 * past 800px). `align` distributes the row — `end` (default) clusters right, `center`, or `between` spreads
 * to the edges — and is inert in a column. `reverse` flips the flow so the last (primary) child leads; on
 * `responsive` it flips only the narrow column phase, hoisting the primary action to the top on mobile.
 */
export function Actions({
	children,
	align = 'end',
	direction = 'row',
	reverse = false,
}: {
	children: ReactNode;
	align?: 'between' | 'center' | 'end';
	direction?: 'column' | 'responsive' | 'row';
	reverse?: boolean;
}) {
	return <div className={styles.actions({ align, direction, reverse })}>{children}</div>;
}

/** Hairline rule between sections. */
export function Divider() {
	return <div className={styles.divider} aria-hidden />;
}

/**
 * Close (×) button. `default` is static/in-flow — place it in a {@link TitleRow}. `floating` pins it to the
 * popup's top-right corner (media/full-bleed dialogs with no header row); `outer` pins it to the screen
 * corner outside the card (full-height dialogs like the GIF picker).
 */
export function Close({ variant }: { variant?: 'default' | 'floating' | 'outer' } = {}) {
	return (
		<BaseDialog.Close aria-label={m['common.a11y.closeDialog']()} className={styles.close({ variant })}>
			<TimesIcon className={styles.timesIcon} />
		</BaseDialog.Close>
	);
}
