import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";

import * as S from "./style";

const headColors: Record<string, string> = {
	h1: "#FFF06D",
	h2: "#FFB8F8",
	h3: "#73F8C8",
	h4: "#9BEDFF",
	h5: "#C6C1FF",
	h6: "#FFCE95",
};

const isEffectivelyVisible = (el: HTMLElement): boolean => {
	let node: HTMLElement | null = el;
	while (node && node !== document.body) {
		const style = window.getComputedStyle(node);
		if (style.display === "none" || style.visibility === "hidden") return false;
		if (parseFloat(style.opacity) === 0) return false;
		node = node.parentElement;
	}
	return true;
};

const HeadingsOverlay = ({ headingFilter }: IHeadingsOverlayProps) => {
	const [boxes, setBoxes] = useState<HeadingBox[]>([]);
	const [, setTick] = useState(0);
	const rafRef = useRef<number>(0);
	const frameRef = useRef<number>(0);

	useEffect(() => {
		const update = () => {
			cancelAnimationFrame(rafRef.current);
			rafRef.current = requestAnimationFrame(() => {
				const selector = headingFilter || "h1, h2, h3, h4, h5, h6";
				const headings = Array.from(document.querySelectorAll<HTMLElement>(selector));
				const boxes: HeadingBox[] = [];
				for (let i = 0; i < headings.length; i++) {
					const el = headings[i];
					const rect = el.getBoundingClientRect();
					if (rect.width === 0 && rect.height === 0) continue;
					if (!isEffectivelyVisible(el)) continue;
					boxes.push({
						id: el.dataset.griddoid || `heading-${i}`,
						tag: el.tagName.toLowerCase(),
					});
				}
				setBoxes(boxes);
			});
		};

		update();

		document.fonts?.ready?.then(update);

		window.addEventListener("resize", update);
		document.addEventListener("animationend", update, true);
		document.addEventListener("transitionend", update, true);

		const observer = new MutationObserver(update);
		observer.observe(document.body, { childList: true, subtree: true });

		return () => {
			cancelAnimationFrame(rafRef.current);
			window.removeEventListener("resize", update);
			document.removeEventListener("animationend", update, true);
			document.removeEventListener("transitionend", update, true);
			observer.disconnect();
		};
	}, [headingFilter]);

	useEffect(() => {
		const updateFrame = () => {
			frameRef.current = requestAnimationFrame(() => {
				setTick((t) => t + 1);
				updateFrame();
			});
		};

		updateFrame();

		return () => cancelAnimationFrame(frameRef.current);
	}, []);

	return createPortal(
		<div data-testid="headings-overlay">
			{boxes.map(({ id, tag }) => {
					const el = document.querySelector<HTMLElement>(`[data-griddoid="${id}"]`) ||
						document.querySelector(tag);
					if (!el) return null;
					const rect = el.getBoundingClientRect();
				const color = headColors[tag];
				if (!color) return null;
				const labelAbove = rect.left < 30;
				return (
					<S.Box
						key={id}
						$color={color}
						style={{
							top: rect.top,
							left: rect.left,
							width: rect.width,
							height: rect.height,
						}}
					>
						<S.Label $color={color} $above={labelAbove}>
							{tag.toUpperCase()}
						</S.Label>
					</S.Box>
				);
			})}
		</div>,
		document.body,
	);
};

interface HeadingBox {
	id: string;
	tag: string;
}

export interface IHeadingsOverlayProps {
	headingFilter: string | null;
}

export default HeadingsOverlay;
