---
/**
 * EmDash Image component
 *
 * Renders images with responsive srcset for CDN providers that support it.
 * Uses the provider's getSrc function for URL-based image transformations.
 *
 * Usage:
 * ```astro
 * ---
 * import { Image } from "emdash/ui";
 * ---
 * <Image image={post.data.featured_image} />
 *
 * <!-- With overrides -->
 * <Image image={post.data.featured_image} class="hero" />
 * ```
 */
import type { MediaValue } from "../fields/types.js";
import type { HTMLAttributes } from "astro/types";
import { getImage } from "astro:assets";
import type { ImageEmbed } from "../media/types.js";
import { getMediaProvider } from "../media/provider-loader.js";
import { buildRenderMediaUrl } from "../media/url.js";
import {
	buildResponsiveImage,
	toAbsoluteMediaUrl,
	RESPONSIVE_BREAKPOINTS,
} from "../media/responsive.js";
import { getPublicOrigin } from "../api/public-url.js";

interface Props extends Omit<
	HTMLAttributes<"img">,
	"src" | "width" | "height"
> {
	/** Image value from content field or media library */
	image: MediaValue | string | undefined | null;
	/** Override alt text (uses image.alt by default) */
	alt?: string;
	/** Override width (uses image.width by default) */
	width?: number;
	/** Override height (uses image.height by default) */
	height?: number;
	/** Priority loading (disables lazy loading) */
	priority?: boolean;
}

const { image, alt, width, height, priority, ...attrs } = Astro.props;

// Normalize string URLs to object form
function normalizeImage(
	img: MediaValue | string | undefined | null
): MediaValue | null {
	if (!img) return null;
	if (typeof img === "string") {
		return { id: "", src: img };
	}
	return img;
}

/**
 * Build the URL for a local image. Prefers `meta.storageKey`; falls back to
 * the internal proxy with `img.id` when no storage key is available.
 */
function buildLocalImageUrl(img: MediaValue): string {
	return buildRenderMediaUrl(Astro.locals.emdash?.getPublicMediaUrl, {
		storageKey: img.meta?.storageKey as string | undefined,
		id: img.id,
	});
}

/**
 * Generate srcset using provider's getSrc function
 */
function generateSrcset(
	getSrc: NonNullable<ImageEmbed["getSrc"]>,
	maxWidth: number,
	aspectRatio?: number
): string {
	return RESPONSIVE_BREAKPOINTS.filter((w) => w <= maxWidth * 2) // Include up to 2x for retina
		.map((w) => {
			const h = aspectRatio ? Math.round(w / aspectRatio) : undefined;
			return `${getSrc({ width: w, height: h })} ${w}w`;
		})
		.join(", ");
}

const img = normalizeImage(image);

// Determine final dimensions (props override image metadata)
const finalWidth = width ?? img?.width;
const finalHeight = height ?? img?.height;
const finalAlt = alt ?? img?.alt ?? "";
const aspectRatio =
	finalWidth && finalHeight ? finalWidth / finalHeight : undefined;

// Get the image source URL and srcset
let src = "";
let srcset: string | undefined;
let sizes: string | undefined;

if (img) {
	const providerId = img.provider ?? "local";

	if (providerId === "local" || img.src) {
		// Local provider or direct src URL. Route through Astro's image service
		// (`astro:assets`) to generate a responsive srcset; on Cloudflare this is
		// the Images binding, on Node it is sharp. Falls back to a plain <img>
		// when the service is unavailable or the source can't be optimized.
		src = img.src || buildLocalImageUrl(img);
		const optimized = await buildResponsiveImage(getImage, {
			src: toAbsoluteMediaUrl(src, getPublicOrigin(Astro.url, Astro.locals.emdash?.config)),
			width: finalWidth,
			height: finalHeight,
		});
		if (optimized) {
			src = optimized.src;
			srcset = optimized.srcset;
			sizes = optimized.sizes;
		}
	} else {
		// External provider
		try {
			const provider = await getMediaProvider(providerId);
			if (provider) {
				const result = provider.getEmbed(img, {
					width: finalWidth,
					height: finalHeight,
				});
				const embed = result instanceof Promise ? await result : result;
				if (embed.type === "image") {
					src = embed.src;

					// Generate srcset if provider supports dynamic sizing
					if (embed.getSrc) {
						// Use image width, or default to 1200 for responsive images
						const maxWidth = finalWidth || 1200;
						srcset = generateSrcset(embed.getSrc, maxWidth, aspectRatio);
						sizes = finalWidth
							? `(min-width: ${finalWidth}px) ${finalWidth}px, 100vw`
							: "100vw";
					}
				}
			} else {
				console.warn(`[EmDashImage] Provider not found: ${providerId}`);
			}
		} catch (error) {
			console.error(
				`[EmDashImage] Failed to get embed for image ${img.id}:`,
				error
			);
		}

		// Fallback to local URL if provider failed
		if (!src) {
			src = buildLocalImageUrl(img);
		}
	}
}

// Build placeholder background style. Prefer the first-class MediaValue fields;
// fall back to `meta` for snapshots stored before LQIP was promoted off `meta`.
const blurhash =
	img?.blurhash ?? (img?.meta?.blurhash as string | undefined);
const dominantColor =
	img?.dominantColor ?? (img?.meta?.dominantColor as string | undefined);

let placeholderStyle = "";
if (blurhash) {
	const { blurhashToImageCssString } = await import("@unpic/placeholder");
	placeholderStyle = blurhashToImageCssString(blurhash);
} else if (dominantColor) {
	placeholderStyle = `background-color: ${dominantColor};`;
}

const baseStyle = aspectRatio
	? `aspect-ratio: ${aspectRatio}; max-width: 100%; height: auto;`
	: "max-width: 100%; height: auto;";

const imgProps: Record<string, unknown> = {
	src,
	srcset,
	sizes,
	width: finalWidth,
	height: finalHeight,
	alt: finalAlt,
	loading: priority ? "eager" : "lazy",
	fetchpriority: priority ? "high" : undefined,
	decoding: "async",
	style: placeholderStyle ? `${baseStyle} ${placeholderStyle}` : baseStyle,
	...attrs,
};
---

{img && src ? <img {...imgProps} /> : null}
