import * as chai from "chai";
import { chaiDom } from "../../../util/chai-dom.mjs";
import { initJSDOM } from "../../../util/jsdom.mjs";

let expect = chai.expect;
chai.use(chaiDom);

let PopperButton;

describe("PopperButton", function () {
	before(function (done) {
		initJSDOM()
			.then(() => {
				return import("../../../../source/components/form/popper-button.mjs");
			})
			.then((m) => {
				PopperButton = m["PopperButton"];
				done();
			})
			.catch((e) => done(e));
	});

	afterEach(() => {
		let mocks = document.getElementById("mocks");
		mocks.innerHTML = "";
	});

	it("should instance of popper-button", function () {
		expect(document.createElement("monster-popper-button")).is.instanceof(
			PopperButton,
		);
	});

	it("should apply content overflow mode to the rendered content wrapper", function (done) {
		let mocks = document.getElementById("mocks");
		const button = document.createElement("monster-popper-button");
		mocks.appendChild(button);

		setTimeout(() => {
			try {
				const content = button.shadowRoot.querySelector('[part="content"]');
				expect(content).to.exist;
				expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
					"both",
				);

				button.setOption("popper.contentOverflow", "horizontal");

				setTimeout(() => {
					try {
						expect(
							content.getAttribute("data-monster-overflow-mode"),
						).to.equal("horizontal");
						done();
					} catch (e) {
						done(e);
					}
				}, 0);
			} catch (e) {
				done(e);
			}
		}, 0);
	});

	it("should resolve nested overlay content to horizontal overflow by default", function (done) {
		let mocks = document.getElementById("mocks");
		const button = document.createElement("monster-popper-button");
		mocks.appendChild(button);

		setTimeout(() => {
			try {
				const content = button.shadowRoot.querySelector('[part="content"]');
				expect(content).to.exist;

				button.setOption(
					"content",
					"<div><monster-select></monster-select></div>",
				);

				setTimeout(() => {
					try {
						expect(
							content.getAttribute("data-monster-overflow-mode"),
						).to.equal("horizontal");
						done();
					} catch (e) {
						done(e);
					}
				}, 0);
			} catch (e) {
				done(e);
			}
		}, 0);
	});

	it("should apply visible content overflow mode to the rendered content wrapper", function (done) {
		let mocks = document.getElementById("mocks");
		const button = document.createElement("monster-popper-button");
		mocks.appendChild(button);

		setTimeout(() => {
			try {
				const content = button.shadowRoot.querySelector('[part="content"]');
				expect(content).to.exist;

				button.setOption("popper.contentOverflow", "visible");

				setTimeout(() => {
					try {
						expect(
							button.getOption("popper.contentOverflow"),
						).to.equal("visible");
						expect(
							content.getAttribute("data-monster-overflow-mode"),
						).to.equal("visible");
						done();
					} catch (e) {
						done(e);
					}
				}, 0);
			} catch (e) {
				done(e);
			}
		}, 0);
	});

	it("should apply content overflow mode from the HTML attribute", function (done) {
		let mocks = document.getElementById("mocks");
		mocks.innerHTML = `
            <monster-popper-button
                data-monster-option-popper-content-overflow="horizontal"
            ></monster-popper-button>
        `;

		setTimeout(() => {
			try {
				const button = mocks.querySelector("monster-popper-button");
				const content = button.shadowRoot.querySelector('[part="content"]');
				expect(button.getOption("popper.contentOverflow")).to.equal(
					"horizontal",
				);
				expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
					"horizontal",
				);
				done();
			} catch (e) {
				done(e);
			}
		}, 0);
	});

	it("should apply visible content overflow mode from the HTML attribute", function (done) {
		let mocks = document.getElementById("mocks");
		mocks.innerHTML = `
            <monster-popper-button
                data-monster-option-popper-content-overflow="visible"
            ></monster-popper-button>
        `;

		setTimeout(() => {
			try {
				const button = mocks.querySelector("monster-popper-button");
				const content = button.shadowRoot.querySelector('[part="content"]');
				expect(button.getOption("popper.contentOverflow")).to.equal(
					"visible",
				);
				expect(content.getAttribute("data-monster-overflow-mode")).to.equal(
					"visible",
				);
				done();
			} catch (e) {
				done(e);
			}
		}, 0);
	});

	it("should use absolute positioning by default", function (done) {
		let mocks = document.getElementById("mocks");
		const button = document.createElement("monster-popper-button");
		mocks.appendChild(button);

		setTimeout(() => {
			try {
				button.showDialog();

				const popper = button.shadowRoot.querySelector(
					'[data-monster-role="popper"]',
				);
				expect(popper).to.exist;
				expect(popper.style.position).to.equal("absolute");
				done();
			} catch (e) {
				done(e);
			}
		}, 0);
	});

	it("should move the popper through the opening appearance states", async function () {
		let mocks = document.getElementById("mocks");
		const button = document.createElement("monster-popper-button");
		mocks.appendChild(button);

		await waitForTimeout();

		button.showDialog();

		const popper = button.shadowRoot.querySelector(
			'[data-monster-role="popper"]',
		);
		expect(popper).to.exist;
		expect(popper.dataset.monsterAppearance).to.equal("opening");

		await waitForCondition(() => {
			return popper.dataset.monsterAppearance === "open";
		});
		expect(popper.dataset.monsterAppearance).to.equal("open");

		button.hideDialog();

		await waitForCondition(() => {
			return !popper.hasAttribute("data-monster-appearance");
		});
		expect(popper.hasAttribute("data-monster-appearance")).to.equal(false);
	});

	it("should finish the opening appearance state when requestAnimationFrame stalls", async function () {
		let mocks = document.getElementById("mocks");
		const button = document.createElement("monster-popper-button");
		const originalRequestAnimationFrame = globalThis.requestAnimationFrame;
		const originalCancelAnimationFrame = globalThis.cancelAnimationFrame;

		try {
			globalThis.requestAnimationFrame = () => 1;
			globalThis.cancelAnimationFrame = () => {};
			mocks.appendChild(button);

			await waitForTimeout();

			button.showDialog();

			const popper = button.shadowRoot.querySelector(
				'[data-monster-role="popper"]',
			);
			expect(popper).to.exist;
			expect(popper.dataset.monsterAppearance).to.equal("opening");

			await waitForCondition(() => {
				return popper.dataset.monsterAppearance === "open";
			});
			expect(popper.dataset.monsterAppearance).to.equal("open");
			button.hideDialog();
		} finally {
			globalThis.requestAnimationFrame = originalRequestAnimationFrame;
			globalThis.cancelAnimationFrame = originalCancelAnimationFrame;
		}
	});
});

function waitForTimeout(timeout = 0) {
	return new Promise((resolve) => setTimeout(resolve, timeout));
}

function waitForCondition(check, { timeout = 1000, interval = 10 } = {}) {
	const deadline = Date.now() + timeout;

	return new Promise((resolve, reject) => {
		const tick = () => {
			let done = false;
			try {
				done = check();
			} catch (e) {
				reject(e);
				return;
			}

			if (done) {
				resolve();
				return;
			}

			if (Date.now() >= deadline) {
				reject(new Error("Timed out waiting for condition."));
				return;
			}

			setTimeout(tick, interval);
		};

		tick();
	});
}
