import { ThemeProvider } from "styled-components";
import { render, cleanup, screen, act } from "@testing-library/react";
import { parseTheme } from "@ax/helpers";
import "@testing-library/jest-dom";

import HeadingsOverlay from "@ax/modules/FramePreview/HeadingsOverlay";
import globalTheme from "@ax/themes/theme.json";

const renderWithTheme = (ui: React.ReactElement) =>
	render(<ThemeProvider theme={parseTheme(globalTheme)}>{ui}</ThemeProvider>);

beforeEach(() => {
	let rafId = 0;
	let processingRaf = false;

	jest.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
		// Avoid infinite recursion by checking if we're already processing
		if (!processingRaf) {
			processingRaf = true;
			try {
				cb(performance.now());
			} finally {
				processingRaf = false;
			}
		}
		return ++rafId;
	});

	jest.spyOn(window, "cancelAnimationFrame").mockImplementation(() => {});
});

afterEach(() => {
	cleanup();
	for (const el of document.querySelectorAll("h1, h2, h3, h4, h5, h6")) el.remove();
	jest.restoreAllMocks();
});

describe("HeadingsOverlay rendering", () => {
	it("should render without boxes when no headings exist in the document", () => {
		renderWithTheme(<HeadingsOverlay headingFilter={null} />);

		expect(screen.queryByText("H1")).toBeNull();
		expect(screen.queryByText("H2")).toBeNull();
	});

	it("should render a labeled box for an h1 element with a non-zero bounding rect", async () => {
		const h1 = document.createElement("h1");
		h1.dataset.griddoid = "heading-1";
		document.body.appendChild(h1);
		jest.spyOn(h1, "getBoundingClientRect").mockReturnValue({
			top: 10,
			left: 10,
			width: 200,
			height: 40,
			bottom: 50,
			right: 210,
			x: 10,
			y: 10,
			toJSON: () => {},
		} as DOMRect);

		await act(async () => {
			renderWithTheme(<HeadingsOverlay headingFilter={null} />);
		});

		expect(screen.getByText("H1")).toBeTruthy();
	});

	it("should render an uppercase label for the heading tag", async () => {
		const h2 = document.createElement("h2");
		h2.dataset.griddoid = "heading-1";
		document.body.appendChild(h2);
		jest.spyOn(h2, "getBoundingClientRect").mockReturnValue({
			top: 10,
			left: 10,
			width: 200,
			height: 40,
			bottom: 50,
			right: 210,
			x: 10,
			y: 10,
			toJSON: () => {},
		} as DOMRect);

		await act(async () => {
			renderWithTheme(<HeadingsOverlay headingFilter={null} />);
		});

		expect(screen.getByText("H2")).toBeTruthy();
	});

	it("should not render boxes for headings with zero bounding rect", () => {
		const h1 = document.createElement("h1");
		document.body.appendChild(h1);
		// JSDOM returns zero rect by default — no mock needed

		renderWithTheme(<HeadingsOverlay headingFilter={null} />);

		expect(screen.queryByText("H1")).toBeNull();
	});

	it("should not render a box for a heading with display:none", async () => {
		const h1 = document.createElement("h1");
		h1.dataset.griddoid = "heading-1";
		document.body.appendChild(h1);
		jest.spyOn(h1, "getBoundingClientRect").mockReturnValue({
			top: 10,
			left: 10,
			width: 200,
			height: 40,
			bottom: 50,
			right: 210,
			x: 10,
			y: 10,
			toJSON: () => {},
		} as DOMRect);

		const originalGetComputedStyle = window.getComputedStyle.bind(window);
		jest.spyOn(window, "getComputedStyle").mockImplementation((el) => {
			if (el === h1) return { display: "none", visibility: "", opacity: "1" } as CSSStyleDeclaration;
			return originalGetComputedStyle(el);
		});

		await act(async () => {
			renderWithTheme(<HeadingsOverlay headingFilter={null} />);
		});

		expect(screen.queryByText("H1")).toBeNull();
	});

	it("should not render a box for a heading with opacity 0", async () => {
		const h1 = document.createElement("h1");
		h1.dataset.griddoid = "heading-1";
		document.body.appendChild(h1);
		jest.spyOn(h1, "getBoundingClientRect").mockReturnValue({
			top: 10,
			left: 10,
			width: 200,
			height: 40,
			bottom: 50,
			right: 210,
			x: 10,
			y: 10,
			toJSON: () => {},
		} as DOMRect);

		const originalGetComputedStyle = window.getComputedStyle.bind(window);
		jest.spyOn(window, "getComputedStyle").mockImplementation((el) => {
			if (el === h1) return { display: "block", visibility: "visible", opacity: "0" } as CSSStyleDeclaration;
			return originalGetComputedStyle(el);
		});

		await act(async () => {
			renderWithTheme(<HeadingsOverlay headingFilter={null} />);
		});

		expect(screen.queryByText("H1")).toBeNull();
	});

	it("should only render boxes for headings matching the headingFilter", async () => {
		const h1 = document.createElement("h1");
		h1.dataset.griddoid = "heading-1";
		document.body.appendChild(h1);
		jest.spyOn(h1, "getBoundingClientRect").mockReturnValue({
			top: 10,
			left: 10,
			width: 200,
			height: 40,
			bottom: 50,
			right: 210,
			x: 10,
			y: 10,
			toJSON: () => {},
		} as DOMRect);

		const h2 = document.createElement("h2");
		h2.dataset.griddoid = "heading-2";
		document.body.appendChild(h2);
		jest.spyOn(h2, "getBoundingClientRect").mockReturnValue({
			top: 60,
			left: 10,
			width: 200,
			height: 40,
			bottom: 100,
			right: 210,
			x: 10,
			y: 60,
			toJSON: () => {},
		} as DOMRect);

		await act(async () => {
			renderWithTheme(<HeadingsOverlay headingFilter="h1" />);
		});

		expect(screen.getByText("H1")).toBeTruthy();
		expect(screen.queryByText("H2")).toBeNull();
	});
});
