import { type ReactNode, useEffect, FC } from "react";
import { useRouter } from "next/router";
import Script from "next/script";
import { getToolbarSrc, cookie as prismicCookie } from "@prismicio/client";

/** Props for `<PrismicPreview>`. */
export type PrismicPreviewProps = {
	/**
	 * The name of your Prismic repository. A Prismic Toolbar will be registered
	 * using this repository.
	 */
	repositoryName: string;

	/**
	 * The URL of your app's Prismic preview endpoint (default: `/api/preview`).
	 * This URL will be fetched on preview update events.
	 */
	updatePreviewURL?: string;

	/**
	 * The URL of your app's exit preview endpoint (default: `/api/exit-preview`).
	 * This URL will be fetched on preview exit events.
	 */
	exitPreviewURL?: string;

	/** Children to render adjacent to the Prismic Toolbar. */
	children?: ReactNode;
};

/**
 * React component that sets up Prismic Previews using the Prismic Toolbar. When
 * the Prismic Toolbar send events to the browser, such as on preview updates
 * and exiting, this component will automatically refresh the page with the
 * changes.
 *
 * This component can be wrapped around your app or added anywhere in your app's
 * tree. It must be rendered on every page.
 */
export const PrismicPreview: FC<PrismicPreviewProps> = (props) => {
	const {
		repositoryName,
		updatePreviewURL = "/api/preview",
		exitPreviewURL = "/api/exit-preview",
		children,
	} = props;

	const router = useRouter();

	const toolbarSrc = getToolbarSrc(repositoryName);

	useEffect(() => {
		const controller = new AbortController();

		window.addEventListener("prismicPreviewUpdate", onUpdate, {
			signal: controller.signal,
		});
		window.addEventListener("prismicPreviewEnd", onEnd, {
			signal: controller.signal,
		});

		// Start the preview for preview share links. Previews from
		// share links do not go to the `updatePreviewURL` like a normal
		// preview.
		//
		// We check that the current URL is a descendant of the base
		// path to prevent infinite refrehes.
		if (
			window.location.href.startsWith(
				window.location.origin + router.basePath,
			) &&
			getPreviewCookieRepositoryName() === repositoryName &&
			!router.isPreview
		) {
			start();
		}

		function onEnd(event: Event) {
			event.preventDefault();
			fetch(router.basePath + exitPreviewURL, { signal: controller.signal })
				.then((res) => {
					if (!res.ok) {
						console.error(
							`[<PrismicPreview>] Failed to exit Preview Mode using the "${exitPreviewURL}" API endpoint. Does it exist?`,
						);

						return;
					}

					refresh();
				})
				.catch(() => {});
		}

		function onUpdate(event: Event) {
			event.preventDefault();
			start();
		}

		function start() {
			// We check `opaqueredirect` because we don't care if
			// the redirect was successful or not. As long as it
			// redirects, we know the endpoint exists and at least
			// attempted to set preview data.
			fetch(router.basePath + updatePreviewURL, {
				redirect: "manual",
				signal: controller.signal,
			})
				.then((res) => {
					if (res.type !== "opaqueredirect") {
						console.error(
							`[<PrismicPreview>] Failed to start or update the preview using "${updatePreviewURL}". Does it exist?`,
						);

						return;
					}

					refresh();
				})
				.catch(() => {});
		}

		function refresh() {
			router.replace(router.asPath, undefined, { scroll: false });
		}

		return () => controller.abort();
	}, [exitPreviewURL, updatePreviewURL, repositoryName, router]);

	return (
		<>
			{children}
			<Script src={toolbarSrc} strategy="lazyOnload" />
		</>
	);
};

function getPreviewCookieRepositoryName() {
	const cookie = window.document.cookie
		.split("; ")
		.find((row) => row.startsWith(`${prismicCookie.preview}=`))
		?.split("=")[1];

	return (decodeURIComponent(cookie ?? "").match(/"([^"]+)\.prismic\.io"/) ||
		[])[1];
}
