import type { Template } from "@reactive-resume/schema/templates";
import type { CSSProperties } from "react";
import type { ResolvedResumePreviewProps } from "./preview.shared";
import type { PreviewPageSize } from "./preview.shared.utils";
import { t } from "@lingui/core/macro";
import { AnimatePresence, m } from "motion/react";
import { useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { isRTL } from "@reactive-resume/utils/locale";
import { cn } from "@reactive-resume/utils/style";
import { createResumePdfBlob } from "@/features/resume/export/pdf-document";
import { useStylesheetStore } from "@/features/resume/stylesheet/store";
import { usePreviewPausedStore, useResumeData, useResumeStore } from "../builder/draft";
import { PdfCanvasDocument, PdfCanvasPage } from "./pdf-canvas";
import { ResumePreviewLoader } from "./preview.shared";
import { getResumePreviewGapValue, getResumePreviewPageCount } from "./preview.shared.utils";
import { ResumeAccessibleText } from "./resume-accessible-text";

type PreviewPdf = {
	file: Blob;
	id: number;
	numPages: number;
	pageSizes: Record<number, PreviewPageSize>;
	phase: "active" | "exiting" | "staged";
	renderedPages: number[];
	template: Template;
};

const UPDATE_DEBOUNCE_MS = 100;
const CROSSFADE_DURATION_MS = 180;

const createPreviewPdf = (file: Blob, id: number, hasExistingPreview: boolean, template: Template): PreviewPdf => ({
	file,
	id,
	numPages: 0,
	pageSizes: {},
	phase: hasExistingPreview ? "staged" : "active",
	renderedPages: [],
	template,
});

const addPreviewLayer = (layers: PreviewPdf[], nextPdf: PreviewPdf) => {
	const activeLayers = layers.filter((layer) => layer.phase === "active");
	return activeLayers.length === 0 ? [nextPdf] : [...activeLayers, nextPdf];
};

const getActivePreviewLayer = (layers: PreviewPdf[]) => layers.find((layer) => layer.phase === "active") ?? null;

const setPreviewPageCount = (layers: PreviewPdf[], layerId: number, numPages: number) =>
	layers.map((layer) => (layer.id === layerId ? { ...layer, numPages } : layer));

const setPreviewPageSize = (layers: PreviewPdf[], layerId: number, pageNumber: number, pageSize: PreviewPageSize) =>
	layers.map((layer) =>
		layer.id === layerId
			? {
					...layer,
					pageSizes: {
						...layer.pageSizes,
						[pageNumber]: pageSize,
					},
				}
			: layer,
	);

const markPreviewPageRendered = (layers: PreviewPdf[], layerId: number, pageNumber: number) => {
	let shouldPromoteLayer = false;

	const nextLayers = layers.map((layer) => {
		if (layer.id !== layerId || layer.renderedPages.includes(pageNumber)) return layer;

		const renderedPages = [...layer.renderedPages, pageNumber];
		const nextLayer = { ...layer, renderedPages };

		if (layer.phase === "staged" && renderedPages.length >= layer.numPages) {
			shouldPromoteLayer = true;
			return { ...nextLayer, phase: "active" as const };
		}

		return nextLayer;
	});

	if (!shouldPromoteLayer) return nextLayers;

	return nextLayers.map((layer) => {
		if (layer.id === layerId) return layer;
		if (layer.phase === "active") return { ...layer, phase: "exiting" as const };

		return layer;
	});
};

const removePreviewLayer = (layers: PreviewPdf[], layerId: number) => layers.filter((layer) => layer.id !== layerId);

export function ResumePreviewClient({
	className,
	data,
	pageGap = 16,
	pageLayout,
	pageScale,
	pageClassName,
	showPageNumbers,
}: ResolvedResumePreviewProps) {
	const builderResumeData = useResumeData();
	const resumeData = data ?? builderResumeData;
	const builderResumeId = useResumeStore((state) => state.resumeId);
	const stylesheetResumeId = useStylesheetStore((state) => state.resumeId);
	const mode = useStylesheetStore((state) => state.mode);
	const applied = useStylesheetStore((state) => state.applied);
	const presentation = useMemo(
		() =>
			data === undefined && builderResumeId !== undefined && stylesheetResumeId === builderResumeId
				? { stylesheet: { mode, applied } }
				: undefined,
		[applied, builderResumeId, data, mode, stylesheetResumeId],
	);
	const paused = usePreviewPausedStore((state) => state.paused);

	const [previewLayers, setPreviewLayers] = useState<PreviewPdf[]>([]);

	const pdfIdRef = useRef(0);
	const requestIdRef = useRef(0);
	const hasPreviewRef = useRef(false);

	useEffect(() => {
		if (!resumeData) return;
		// Mobile hides the preview behind the Edit/Design overlay; skip re-rendering and keep the last PDF shown.
		if (paused) return;

		let cancelled = false;
		const requestId = ++requestIdRef.current;
		const delay = hasPreviewRef.current ? UPDATE_DEBOUNCE_MS : 0;

		const generatePdfPreview = async () => {
			try {
				if (cancelled || requestId !== requestIdRef.current) return;
				const blob = await createResumePdfBlob(resumeData, undefined, undefined, presentation);

				if (!cancelled && requestId === requestIdRef.current) {
					const nextPdf = createPreviewPdf(
						blob,
						pdfIdRef.current++,
						hasPreviewRef.current,
						resumeData.metadata.template,
					);

					hasPreviewRef.current = true;
					setPreviewLayers((current) => addPreviewLayer(current, nextPdf));
				}
			} catch {
				if (cancelled || requestId !== requestIdRef.current) return;
				toast.error(t`The resume preview could not be updated. The last valid preview is still shown.`, {
					id: "resume-preview-render-error",
				});
			}
		};

		const timeoutId = window.setTimeout(() => {
			void generatePdfPreview();
		}, delay);

		return () => {
			cancelled = true;
			window.clearTimeout(timeoutId);
		};
	}, [paused, presentation, resumeData]);

	if (!resumeData) return null;

	const visiblePdf = getActivePreviewLayer(previewLayers);
	const resolvedPageGap = getResumePreviewGapValue(pageGap);

	if (!visiblePdf) {
		return (
			<>
				<ResumeAccessibleText data={resumeData} />
				<ResumePreviewLoader
					pageCount={getResumePreviewPageCount(resumeData)}
					pageClassName={pageClassName}
					pageGap={pageGap}
					pageLayout={pageLayout}
					pageScale={pageScale}
					showPageNumbers={showPageNumbers}
				/>
			</>
		);
	}

	return (
		<div className={cn("grid", className)}>
			<ResumeAccessibleText data={resumeData} />
			<AnimatePresence initial={false}>
				{previewLayers.map((visiblePdf) => (
					<m.div
						key={visiblePdf.id}
						aria-hidden={visiblePdf.phase !== "active"}
						data-resume-preview-template={visiblePdf.template}
						style={{ "--resume-preview-page-gap": resolvedPageGap } as CSSProperties}
						className={cn("col-start-1 row-start-1", visiblePdf.phase !== "active" && "pointer-events-none")}
						initial={{ opacity: visiblePdf.phase === "active" ? 1 : 0 }}
						animate={{ opacity: visiblePdf.phase === "active" ? 1 : 0 }}
						exit={{ opacity: 0 }}
						transition={{ duration: CROSSFADE_DURATION_MS / 1000, ease: "easeOut" }}
						onAnimationComplete={() => {
							if (visiblePdf.phase !== "exiting") return;
							setPreviewLayers((current) => removePreviewLayer(current, visiblePdf.id));
						}}
					>
						<PdfCanvasDocument
							file={visiblePdf.file}
							onLoadSuccess={(document) => {
								setPreviewLayers((current) => setPreviewPageCount(current, visiblePdf.id, document.numPages));
							}}
						>
							{(document) => (
								<div
									dir={isRTL(resumeData.metadata.page.locale) ? "rtl" : "ltr"}
									className={cn(
										"flex justify-start gap-(--resume-preview-page-gap)",
										pageLayout === "horizontal" ? "flex-row items-start" : "flex-col items-center",
									)}
								>
									{Array.from({ length: visiblePdf.numPages }, (_, index) => {
										const pageNumber = index + 1;
										const totalPages = visiblePdf.numPages;
										const pageSize = visiblePdf.pageSizes[pageNumber];

										return (
											<PdfCanvasPage
												key={`${visiblePdf.id}-${pageNumber}`}
												document={document}
												pageSize={pageSize}
												pageNumber={pageNumber}
												pageScale={pageScale}
												totalPages={totalPages}
												className={pageClassName}
												showPageNumbers={showPageNumbers}
												onLoadSuccess={(_, pageSize) => {
													setPreviewLayers((current) =>
														setPreviewPageSize(current, visiblePdf.id, pageNumber, pageSize),
													);
												}}
												onRenderSuccess={() => {
													if (visiblePdf.phase !== "staged") return;

													setPreviewLayers((current) => markPreviewPageRendered(current, visiblePdf.id, pageNumber));
												}}
											/>
										);
									})}
								</div>
							)}
						</PdfCanvasDocument>
					</m.div>
				))}
			</AnimatePresence>
		</div>
	);
}
