"use client";

import {
	forwardRef,
	ForwardRefExoticComponent,
	PropsWithoutRef,
	RefAttributes,
} from "react";
import Image, { ImageProps } from "next/image";
import { buildURL, ImgixURLParams } from "imgix-url-builder";
import { ImageFieldImage, isFilled } from "@prismicio/client";

import { devMsg } from "./lib/devMsg";
import { resolveDefaultExport } from "./lib/resolveDefaultExport";

import { imgixLoader } from "./imgixLoader";

const castInt = (input: string | number | undefined): number | undefined => {
	if (typeof input === "number" || typeof input === "undefined") {
		return input;
	} else {
		const parsed = Number.parseInt(input);

		if (Number.isNaN(parsed)) {
			return undefined;
		} else {
			return parsed;
		}
	}
};

export type PrismicNextImageProps = Omit<
	ImageProps,
	"src" | "alt" | "loader"
> & {
	/** The Prismic Image field or thumbnail to render. */
	field: ImageFieldImage | null | undefined;

	/**
	 * An object of Imgix URL API parameters to transform the image.
	 *
	 * @see https://docs.imgix.com/apis/rendering
	 */
	imgixParams?: { [P in keyof ImgixURLParams]: ImgixURLParams[P] | null };

	/**
	 * Declare an image as decorative by providing `alt=""`.
	 *
	 * See:
	 * https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/alt#decorative_images
	 */
	alt?: "";

	/**
	 * Declare an image as decorative only if the Image field does not have
	 * alternative text by providing `fallbackAlt=""`.
	 *
	 * See:
	 * https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/alt#decorative_images
	 */
	fallbackAlt?: "";

	/**
	 * Rendered when the field is empty. If a fallback is not given, `null` will
	 * be rendered.
	 */
	fallback?: React.ReactNode;

	loader?: ImageProps["loader"] | null;
};

/**
 * React component that renders an image from a Prismic Image field or one of
 * its thumbnails using `next/image`. It will automatically set the `alt`
 * attribute using the Image field's `alt` property.
 *
 * It uses an Imgix URL-based loader by default. A custom loader can be provided
 * with the `loader` prop. If you would like to use the Next.js Image
 * Optimization API instead, set `loader={undefined}`.
 *
 * @param props - Props for the component.
 *
 * @returns A responsive image component using `next/image` for the given Image
 *   field.
 *
 * @see To learn more about `next/image`, see: https://nextjs.org/docs/api-reference/next/image
 */
// The type annotation is necessary to avoid a type reference issue.
export const PrismicNextImage: ForwardRefExoticComponent<
	PropsWithoutRef<PrismicNextImageProps> & RefAttributes<HTMLImageElement>
> = forwardRef<HTMLImageElement, PrismicNextImageProps>(
	function PrismicNextImage(
		{
			field,
			imgixParams = {},
			alt,
			fallbackAlt,
			fill,
			width,
			height,
			fallback = null,
			loader = imgixLoader,
			...restProps
		},
		ref,
	) {
		if (process.env.NODE_ENV === "development") {
			if (typeof alt === "string" && alt !== "") {
				console.warn(
					`[PrismicNextImage] The "alt" prop can only be used to declare an image as decorative by passing an empty string (alt="") but was provided a non-empty string. You can resolve this warning by removing the "alt" prop or changing it to alt="". For more details, see ${devMsg(
						"alt-must-be-an-empty-string",
					)}`,
				);
			}

			if (typeof fallbackAlt === "string" && fallbackAlt !== "") {
				console.warn(
					`[PrismicNextImage] The "fallbackAlt" prop can only be used to declare an image as decorative by passing an empty string (fallbackAlt="") but was provided a non-empty string. You can resolve this warning by removing the "fallbackAlt" prop or changing it to fallbackAlt="". For more details, see ${devMsg(
						"alt-must-be-an-empty-string",
					)}`,
				);
			}
		}

		if (!isFilled.imageThumbnail(field)) {
			return <>{fallback}</>;
		}

		const resolvedImgixParams = imgixParams;
		for (const x in imgixParams) {
			if (resolvedImgixParams[x as keyof typeof resolvedImgixParams] === null) {
				resolvedImgixParams[x as keyof typeof resolvedImgixParams] = undefined;
			}
		}

		const src = buildURL(field.url, imgixParams as ImgixURLParams);

		const ar = field.dimensions.width / field.dimensions.height;

		const castedWidth = castInt(width);
		const castedHeight = castInt(height);

		let resolvedWidth = castedWidth ?? field.dimensions.width;
		let resolvedHeight = castedHeight ?? field.dimensions.height;

		if (castedWidth != null && castedHeight == null) {
			resolvedHeight = castedWidth / ar;
		} else if (castedWidth == null && castedHeight != null) {
			resolvedWidth = castedHeight * ar;
		}

		// A non-null assertion is required since we can't statically
		// know if an alt attribute is available.
		const resolvedAlt = (alt ?? (field.alt || fallbackAlt))!;

		if (
			process.env.NODE_ENV === "development" &&
			typeof resolvedAlt !== "string"
		) {
			console.error(
				`[PrismicNextImage] The following image is missing an "alt" property. Please add Alternative Text to the image in Prismic. To mark the image as decorative instead, add one of \`alt=""\` or \`fallbackAlt=""\`.`,
				src,
			);
		}

		const ResolvedImage = resolveDefaultExport(Image);

		return (
			<ResolvedImage
				ref={ref}
				src={src}
				width={fill ? undefined : resolvedWidth}
				height={fill ? undefined : resolvedHeight}
				alt={resolvedAlt}
				fill={fill}
				loader={loader === null ? undefined : loader}
				{...restProps}
			/>
		);
	},
);
