/**
 * The modal dialog (milestone 23, ruling 4).
 *
 * React Aria owns the focus trap, the Escape handler and the `aria-modal`
 * wiring that the hand-rolled overlay only approximated. State is controlled by
 * the caller because the trigger is a table row button, not a `DialogTrigger`.
 *
 * The title is both the visible heading and the accessible name: `Heading
 * slot="title"` is what React Aria points `aria-labelledby` at, so a caller
 * cannot name the dialog one thing and show another.
 */

import type { ReactNode } from "react";
import * as stylex from "@stylexjs/stylex";
import { Dialog as AriaDialog, Heading, Modal, ModalOverlay } from "react-aria-components";
import { colors } from "./tokens.stylex";
import { styles as shared } from "./styles";

interface Props {
	/** The dialog's heading, and with it the dialog's accessible name. */
	title: string;
	isOpen: boolean;
	onClose: () => void;
	/** `detail` is the wider panel a record needs; `form` fits a column of fields. */
	size?: "form" | "detail";
	children: ReactNode;
}

const styles = stylex.create({
	overlay: {
		position: "fixed",
		inset: 0,
		zIndex: 50,
		display: "flex",
		alignItems: "center",
		justifyContent: "center",
		padding: "1rem",
		backgroundColor: "rgba(0, 0, 0, 0.4)",
	},
	/**
	 * A column so the header stays put and the body scrolls under it. The height
	 * cap is what keeps a long record inside the viewport instead of running off
	 * the bottom of a short one.
	 */
	panel: {
		width: "100%",
		maxHeight: "85vh",
		display: "flex",
		flexDirection: "column",
		borderRadius: "0.5rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: colors.border,
		backgroundColor: colors.surfaceRaised,
		color: colors.text,
		boxShadow: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
	},
	panelForm: {
		maxWidth: "28rem",
	},
	panelDetail: {
		maxWidth: "52rem",
	},
	/**
	 * The panel already draws the boundary; the dialog's own ring would double
	 * it. `minHeight: 0` is what lets the body shrink far enough to scroll.
	 */
	body: {
		outlineStyle: "none",
		display: "flex",
		flexDirection: "column",
		minHeight: 0,
	},
	header: {
		flexShrink: 0,
		display: "flex",
		alignItems: "center",
		justifyContent: "space-between",
		gap: "1rem",
		borderBottomWidth: 1,
		borderBottomStyle: "solid",
		borderBottomColor: colors.border,
		paddingInline: "1.5rem",
		paddingBlock: "0.75rem",
	},
	title: {
		fontSize: "1.125rem",
		lineHeight: "1.75rem",
		fontWeight: 600,
	},
	/** 44px on both axes: the pointer-target floor, which the word alone misses. */
	close: {
		minWidth: 44,
		minHeight: 44,
		display: "inline-flex",
		alignItems: "center",
		justifyContent: "center",
	},
	content: {
		minHeight: 0,
		overflowY: "auto",
		paddingInline: "1.5rem",
		paddingBlock: "1.5rem",
	},
});

export default function Dialog({ title, isOpen, onClose, size = "form", children }: Props) {
	return (
		<ModalOverlay
			isOpen={isOpen}
			onOpenChange={(open) => {
				if (!open) onClose();
			}}
			isDismissable
			className={() => stylex.props(styles.overlay).className ?? ""}
		>
			<Modal
				className={() =>
					stylex.props(styles.panel, size === "detail" ? styles.panelDetail : styles.panelForm).className ??
					""
				}
			>
				<AriaDialog {...stylex.props(styles.body)}>
					<div {...stylex.props(styles.header)}>
						<Heading slot="title" level={2} {...stylex.props(styles.title)}>
							{title}
						</Heading>
						<button
							type="button"
							onClick={onClose}
							{...stylex.props(shared.button, styles.close, shared.focusRing)}
						>
							Close
						</button>
					</div>
					<div {...stylex.props(styles.content)}>{children}</div>
				</AriaDialog>
			</Modal>
		</ModalOverlay>
	);
}
