import type { ReactNode } from "react";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { useClickOutside } from "../hooks/use-click-outside.js";
import { useSwipeToDismissDown } from "../hooks/use-swipe-to-dismiss.js";
import { cn } from "../lib/utils.js";

interface DetailPopoverProps {
	readonly anchorRect: DOMRect;
	readonly onClose: () => void;
	readonly ariaLabel: string;
	readonly children: ReactNode;
}

function DesktopPanel({
	anchorRect,
	onClose,
	ariaLabel,
	children,
}: Readonly<DetailPopoverProps>) {
	const ref = useRef<HTMLDivElement>(null);
	const [pos, setPos] = useState<{ top: number; left: number } | null>(null);

	useLayoutEffect(() => {
		const el = ref.current;
		if (!el) return;
		const popover = el.getBoundingClientRect();
		const vw = document.documentElement.clientWidth;
		const vh = document.documentElement.clientHeight;
		// Prefer placement to the LEFT of the anchor (panel is on the right edge)
		let left = anchorRect.left - popover.width - 8;
		if (left < 8) {
			left = anchorRect.right + 8;
		}
		if (left + popover.width > vw - 8) {
			left = vw - popover.width - 8;
		}
		let top = anchorRect.top;
		if (top + popover.height > vh - 8) {
			top = vh - popover.height - 8;
		}
		if (top < 8) top = 8;
		setPos({ top, left });
	}, [anchorRect]);

	useClickOutside(ref, onClose);

	return (
		<div
			ref={ref}
			className="card-glow fixed z-50 max-h-[calc(100vh-16px)] w-80 max-w-[calc(100vw-16px)] overflow-y-auto rounded-lg border border-border bg-card p-4 shadow-lg"
			style={pos ? { top: pos.top, left: pos.left } : { visibility: "hidden" }}
			role="dialog"
			aria-label={ariaLabel}
		>
			{children}
		</div>
	);
}

function MobileSheet({
	onClose,
	ariaLabel,
	children,
}: Readonly<Omit<DetailPopoverProps, "anchorRect">>) {
	const { offsetY, isSwiping, handlers } = useSwipeToDismissDown(onClose);

	useEffect(() => {
		const handler = (e: KeyboardEvent) => {
			if (e.key === "Escape") onClose();
		};
		document.addEventListener("keydown", handler);
		return () => document.removeEventListener("keydown", handler);
	}, [onClose]);

	return (
		<div className="fixed inset-0 z-50">
			<button
				type="button"
				className="fade-in absolute inset-0 animate-in bg-black/50"
				onClick={onClose}
				aria-label="Close details"
			/>
			<div
				className={cn(
					"panel-glow absolute right-0 bottom-0 left-0 max-h-[80vh] rounded-t-2xl border-border border-t bg-card",
					!isSwiping && "animate-slide-in-bottom",
				)}
				style={
					isSwiping ? { transform: `translateY(${offsetY}px)` } : undefined
				}
				{...handlers}
				role="dialog"
				aria-label={ariaLabel}
			>
				<div className="flex justify-center pt-2 pb-1">
					<div className="h-1 w-10 rounded-full bg-muted-foreground/40" />
				</div>
				<div className="max-h-[calc(80vh-24px)] overflow-y-auto p-4">
					{children}
				</div>
			</div>
		</div>
	);
}

export function DetailPopover({
	anchorRect,
	onClose,
	ariaLabel,
	children,
}: Readonly<DetailPopoverProps>) {
	const [isDesktop, setIsDesktop] = useState(
		() => globalThis.matchMedia("(min-width: 1024px)").matches,
	);

	useEffect(() => {
		const mq = globalThis.matchMedia("(min-width: 1024px)");
		const handler = (e: MediaQueryListEvent) => setIsDesktop(e.matches);
		mq.addEventListener("change", handler);
		return () => mq.removeEventListener("change", handler);
	}, []);

	// Portal to document.body to escape any CSS transforms on ancestors
	// (the side panel uses translate-x for collapse animation, which would
	// otherwise become the containing block for fixed-positioned children).
	const content = isDesktop ? (
		<DesktopPanel
			anchorRect={anchorRect}
			onClose={onClose}
			ariaLabel={ariaLabel}
		>
			{children}
		</DesktopPanel>
	) : (
		<MobileSheet onClose={onClose} ariaLabel={ariaLabel}>
			{children}
		</MobileSheet>
	);
	return createPortal(content, document.body);
}
