import {
	addIdsToHeadings,
	highlightKeywords,
	removeKeywordHighlights,
	observeAndAddIdsToHeadings,
	observeAndHighlightKeywords,
	KEYWORD_HIGHLIGHT_CLASS,
} from "@ax/modules/FramePreview/utils";

describe("addIdsToHeadings", () => {
	it("should add data-griddoid to all heading elements when no filter is given", () => {
		const container = document.createElement("div");
		container.innerHTML = "<h1>Title</h1><h2>Subtitle</h2><h3>Section</h3>";

		addIdsToHeadings(container, null);

		const h1 = container.querySelector("h1") as HTMLElement;
		const h2 = container.querySelector("h2") as HTMLElement;
		const h3 = container.querySelector("h3") as HTMLElement;

		expect(h1.dataset.griddoid).toBe("heading-1");
		expect(h2.dataset.griddoid).toBe("heading-2");
		expect(h3.dataset.griddoid).toBe("heading-3");
	});

	it("should add data-griddoid only to filtered heading type", () => {
		const container = document.createElement("div");
		container.innerHTML = "<h1>Title</h1><h2>Subtitle</h2>";

		addIdsToHeadings(container, "h1");

		const h1 = container.querySelector("h1") as HTMLElement;
		const h2 = container.querySelector("h2") as HTMLElement;

		expect(h1.dataset.griddoid).toBe("heading-1");
		expect(h2.dataset.griddoid).toBeUndefined();
	});

	it("should do nothing if no elements match the selector", () => {
		const container = document.createElement("div");
		container.innerHTML = "<p>No headings here</p>";

		addIdsToHeadings(container, null);

		const p = container.querySelector("p") as HTMLElement;
		expect(p.dataset.griddoid).toBeUndefined();
	});
});

describe("removeKeywordHighlights", () => {
	it("should replace mark.gdd-keyword elements with their text content", () => {
		const container = document.createElement("div");
		container.innerHTML = `<p>Hello <mark class="${KEYWORD_HIGHLIGHT_CLASS}">world</mark>!</p>`;

		removeKeywordHighlights(container);

		expect(container.querySelector(`mark.${KEYWORD_HIGHLIGHT_CLASS}`)).toBeNull();
		expect(container.textContent).toBe("Hello world!");
	});

	it("should do nothing when there are no highlight marks", () => {
		const container = document.createElement("div");
		container.innerHTML = "<p>No highlights here</p>";

		removeKeywordHighlights(container);

		expect(container.textContent).toBe("No highlights here");
	});

	it("should remove multiple marks", () => {
		const container = document.createElement("div");
		container.innerHTML = `<p><mark class="${KEYWORD_HIGHLIGHT_CLASS}">seo</mark> and <mark class="${KEYWORD_HIGHLIGHT_CLASS}">react</mark></p>`;

		removeKeywordHighlights(container);

		expect(container.querySelectorAll(`mark.${KEYWORD_HIGHLIGHT_CLASS}`).length).toBe(0);
		expect(container.textContent).toBe("seo and react");
	});
});

describe("highlightKeywords", () => {
	it("should wrap matching keyword in a mark element", () => {
		const container = document.createElement("div");
		container.innerHTML = "<p>Hello world</p>";

		highlightKeywords(container, ["world"]);

		const mark = container.querySelector(`mark.${KEYWORD_HIGHLIGHT_CLASS}`);
		expect(mark).not.toBeNull();
		expect(mark?.textContent).toBe("world");
	});

	it("should do nothing when keywords array is empty", () => {
		const container = document.createElement("div");
		container.innerHTML = "<p>Hello world</p>";

		highlightKeywords(container, []);

		expect(container.querySelector(`mark.${KEYWORD_HIGHLIGHT_CLASS}`)).toBeNull();
	});

	it("should do nothing when keywords contain only whitespace", () => {
		const container = document.createElement("div");
		container.innerHTML = "<p>Hello world</p>";

		highlightKeywords(container, ["   "]);

		expect(container.querySelector(`mark.${KEYWORD_HIGHLIGHT_CLASS}`)).toBeNull();
	});

	it("should be case-insensitive", () => {
		const container = document.createElement("div");
		container.innerHTML = "<p>Hello WORLD</p>";

		highlightKeywords(container, ["world"]);

		const mark = container.querySelector(`mark.${KEYWORD_HIGHLIGHT_CLASS}`);
		expect(mark).not.toBeNull();
		expect(mark?.textContent).toBe("WORLD");
	});

	it("should highlight multiple keywords", () => {
		const container = document.createElement("div");
		container.innerHTML = "<p>Hello world and react</p>";

		highlightKeywords(container, ["world", "react"]);

		const marks = container.querySelectorAll(`mark.${KEYWORD_HIGHLIGHT_CLASS}`);
		expect(marks.length).toBe(2);
	});

	it("should remove previous highlights before adding new ones", () => {
		const container = document.createElement("div");
		container.innerHTML = "<p>Hello world react</p>";

		highlightKeywords(container, ["world"]);
		expect(container.querySelectorAll(`mark.${KEYWORD_HIGHLIGHT_CLASS}`).length).toBe(1);

		highlightKeywords(container, ["react"]);

		const marks = container.querySelectorAll(`mark.${KEYWORD_HIGHLIGHT_CLASS}`);
		expect(marks.length).toBe(1);
		expect(marks[0].textContent).toBe("react");
	});

	it("should not highlight text inside SCRIPT elements", () => {
		const container = document.createElement("div");
		container.innerHTML = "<script>var world = 'test';</script><p>Hello world</p>";

		highlightKeywords(container, ["world"]);

		const marks = container.querySelectorAll(`mark.${KEYWORD_HIGHLIGHT_CLASS}`);
		expect(marks.length).toBe(1);
		expect(marks[0].textContent).toBe("world");
	});
});

describe("observeAndAddIdsToHeadings", () => {
	it("should add IDs to headings immediately on call", () => {
		const container = document.createElement("div");
		container.innerHTML = "<h1>Title</h1><h2>Subtitle</h2>";

		observeAndAddIdsToHeadings(container, null);

		const h1 = container.querySelector("h1") as HTMLElement;
		const h2 = container.querySelector("h2") as HTMLElement;
		expect(h1.dataset.griddoid).toBe("heading-1");
		expect(h2.dataset.griddoid).toBe("heading-2");
	});

	it("should return a cleanup function that disconnects the observer", () => {
		const container = document.createElement("div");
		container.innerHTML = "<h1>Title</h1>";

		const cleanup = observeAndAddIdsToHeadings(container, null);

		expect(typeof cleanup).toBe("function");
		expect(() => cleanup()).not.toThrow();
	});

	it("should re-apply IDs when new headings are added to the observed container", async () => {
		const container = document.createElement("div");
		container.innerHTML = "<h1>Title</h1>";

		const cleanup = observeAndAddIdsToHeadings(container, null);

		const h2 = document.createElement("h2");
		h2.textContent = "Subtitle";
		container.appendChild(h2);

		await new Promise((resolve) => setTimeout(resolve, 0));

		expect(h2.dataset.griddoid).toBe("heading-2");

		cleanup();
	});
});

describe("observeAndHighlightKeywords", () => {
	it("should highlight keywords immediately on call", () => {
		const container = document.createElement("div");
		container.innerHTML = "<p>Hello world</p>";

		const cleanup = observeAndHighlightKeywords(container, ["world"]);

		expect(container.querySelector(`mark.${KEYWORD_HIGHLIGHT_CLASS}`)).not.toBeNull();
		expect(typeof cleanup).toBe("function");

		cleanup();
	});

	it("should remove highlights when the cleanup function is called", () => {
		const container = document.createElement("div");
		container.innerHTML = "<p>Hello world</p>";

		const cleanup = observeAndHighlightKeywords(container, ["world"]);
		expect(container.querySelector(`mark.${KEYWORD_HIGHLIGHT_CLASS}`)).not.toBeNull();

		cleanup();

		expect(container.querySelector(`mark.${KEYWORD_HIGHLIGHT_CLASS}`)).toBeNull();
		expect(container.textContent).toBe("Hello world");
	});

	it("should re-highlight keywords after DOM mutations in the observed container", async () => {
		const container = document.createElement("div");
		container.innerHTML = "<p>Hello world</p>";

		const cleanup = observeAndHighlightKeywords(container, ["world"]);

		const newP = document.createElement("p");
		newP.textContent = "Another world mention";
		container.appendChild(newP);

		await new Promise((resolve) => setTimeout(resolve, 0));

		const marks = container.querySelectorAll(`mark.${KEYWORD_HIGHLIGHT_CLASS}`);
		expect(marks.length).toBeGreaterThanOrEqual(1);

		cleanup();
	});
});
