import { useState } from 'react';

import type { AppBskyEmbedGallery } from '@atcute/bluesky';

import { assignInlineVars } from '@vanilla-extract/dynamic';
import { clsx } from 'clsx';

import { useLargeAltBadgeEnabled } from '#/state/preferences/large-alt-badge';

import * as Dialog from '#/components/Dialog';
import { lightboxHandle, type LightboxPayload } from '#/components/dialogs/handles';
import * as styles from '#/components/ImageEmbed/AutoSizedImage.css';
import { getAspectRatio } from '#/components/ImageEmbed/carousel/utils';
import { MediaBadges } from '#/components/ImageEmbed/MediaBadges';

import ImageIcon from '#/icons/central/Images1_round_outlined_radius1_stroke2.svg';

export type AutoSizedImageProps = {
	image: AppBskyEmbedGallery.ViewImage;
	crop?: 'constrained' | 'none';
	/** What the lightbox opens with; the image renders as a detached `Dialog.Trigger`. */
	payload: LightboxPayload;
	onPressIn?: () => void;
};

/**
 * renders a single post image that preserves its aspect ratio and opens a lightbox on click.
 *
 * @param ratio determines the layout constraint: 'constrained' caps the height, 'none' does not cap the
 *   height.
 */
export function AutoSizedImage({ image, crop = 'constrained', payload, onPressIn }: AutoSizedImageProps) {
	const [status, setStatus] = useState<'error' | 'loaded' | 'loading'>(image.thumbnail ? 'loading' : 'error');
	const largeAlt = useLargeAltBadgeEnabled();

	const measure = (node: HTMLImageElement | null) => {
		if (node?.complete) {
			setStatus(node.naturalWidth > 0 ? 'loaded' : 'error');
		}
	};
	// Old images, or images from other clients, can lack an aspect ratio; those fall back to a square box.
	const aspectRatio = getAspectRatio(image.aspectRatio);

	// A ratio-less image is letterboxed inside its square box rather than cover-cropped to an unknown shape.
	const isContain = aspectRatio === undefined;
	const className = crop === 'none' ? styles.uncapped : styles.constrained;

	const onPointerDown = onPressIn ? () => onPressIn() : undefined;

	return (
		<Dialog.Trigger
			handle={lightboxHandle}
			payload={payload}
			type="button"
			className={className}
			style={assignInlineVars({ [styles.ratioVar]: String(aspectRatio ?? 1) })}
			aria-label={image.alt || undefined}
			onPointerDown={onPointerDown}
		>
			{status === 'error' ? (
				<span className={styles.fallback}>
					<ImageIcon className={styles.imageIcon} />
				</span>
			) : (
				<img
					className={clsx(
						styles.image,
						isContain && styles.imageContain,
						status === 'loading' && styles.loading,
					)}
					src={image.thumbnail}
					alt={image.alt}
					loading="lazy"
					onError={() => setStatus('error')}
					onLoad={() => setStatus('loaded')}
					ref={measure}
				/>
			)}
			{/* A single image keeps its aspect ratio, so it's never cropped — only the alt badge applies. */}
			<MediaBadges variant="single" hasAlt={!!image.alt} cropped={false} large={largeAlt} />
		</Dialog.Trigger>
	);
}
