import * as chai from "chai";
import { initJSDOM } from "../../../util/jsdom.mjs";
import { ResizeObserverMock } from "../../../util/resize-observer.mjs";

const expect = chai.expect;

describe("Slider", function () {
	let Slider;

	before(function (done) {
		initJSDOM()
			.then(() => {
				import("../../../../source/components/layout/slider.mjs")
					.then((m) => {
						Slider = m.Slider;
						done();
					})
					.catch((e) => done(e));
			})
			.catch((e) => done(e));
	});

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

	it("should register monster-slider", function () {
		expect(document.createElement("monster-slider")).to.be.instanceof(Slider);
	});

	it("should coalesce resize updates and skip unchanged sizes", function () {
		const OriginalResizeObserver = window.ResizeObserver;
		const originalGlobalResizeObserver = globalThis.ResizeObserver;
		const originalRequestAnimationFrame = window.requestAnimationFrame;
		const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;

		class TrackingResizeObserver extends ResizeObserverMock {
			static instances = [];

			constructor(callback) {
				super(callback);
				TrackingResizeObserver.instances.push(this);
			}
		}

		const scheduledCallbacks = [];

		try {
			window.ResizeObserver = TrackingResizeObserver;
			globalThis.ResizeObserver = TrackingResizeObserver;
			window.requestAnimationFrame = (callback) => {
				scheduledCallbacks.push(callback);
				return scheduledCallbacks.length;
			};
			window.cancelAnimationFrame = () => {};
			globalThis.requestAnimationFrame = window.requestAnimationFrame;

			const mocks = document.getElementById("mocks");
			const slider = document.createElement("monster-slider");
			slider.setOption("features.autoPlay", false);
			slider.setOption("features.thumbnails", false);
			mocks.appendChild(slider);

			const sliderElement = slider.shadowRoot.querySelector(
				"[data-monster-role='slider']",
			);
			const resizeObserver = TrackingResizeObserver.instances.find((observer) =>
				observer.elements.includes(sliderElement),
			);
			expect(resizeObserver).to.exist;

			let resizeEvents = 0;
			slider.addEventListener("monster-slider-resized", () => {
				resizeEvents += 1;
			});

			resizeObserver.triggerResize([
				{ contentRect: { width: 320, height: 120 } },
			]);
			resizeObserver.triggerResize([
				{ contentRect: { width: 640, height: 120 } },
			]);

			expect(scheduledCallbacks).to.have.length(1);
			scheduledCallbacks.shift()();
			expect(resizeEvents).to.equal(1);

			resizeObserver.triggerResize([
				{ contentRect: { width: 640, height: 120 } },
			]);
			expect(scheduledCallbacks).to.have.length(0);
			expect(resizeEvents).to.equal(1);
		} finally {
			window.ResizeObserver = OriginalResizeObserver;
			globalThis.ResizeObserver = originalGlobalResizeObserver;
			window.requestAnimationFrame = originalRequestAnimationFrame;
			globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
		}
	});

	it("should coalesce drag updates to the latest move per frame", function () {
		const originalRequestAnimationFrame = window.requestAnimationFrame;
		const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;

		const scheduledCallbacks = [];

		try {
			window.requestAnimationFrame = (callback) => {
				scheduledCallbacks.push(callback);
				return scheduledCallbacks.length;
			};
			window.cancelAnimationFrame = () => {};
			globalThis.requestAnimationFrame = window.requestAnimationFrame;

			const mocks = document.getElementById("mocks");
			const slider = document.createElement("monster-slider");
			slider.setOption("features.autoPlay", false);
			mocks.appendChild(slider);

			for (let i = 0; i < 3; i += 1) {
				const slide = document.createElement("div");
				Object.defineProperty(slide, "offsetWidth", {
					configurable: true,
					value: 100,
				});
				slide.textContent = `Slide ${i + 1}`;
				slider.appendChild(slide);
			}

			const sliderElement = slider.shadowRoot.querySelector(
				"[data-monster-role='slider']",
			);
			Object.defineProperty(sliderElement, "offsetWidth", {
				configurable: true,
				value: 300,
			});
			slider.moveTo(0);

			const createMouseEvent = (type, pageX) => {
				const event = new window.MouseEvent(type, {
					bubbles: true,
					clientX: pageX,
				});
				Object.defineProperty(event, "pageX", {
					configurable: true,
					value: pageX,
				});
				return event;
			};

			sliderElement.dispatchEvent(createMouseEvent("mousedown", 10));
			document.body.dispatchEvent(createMouseEvent("mousemove", 20));
			document.body.dispatchEvent(createMouseEvent("mousemove", 55));

			expect(scheduledCallbacks).to.have.length(1);
			scheduledCallbacks.shift()();
			expect(sliderElement.style.transform).to.contain("45px");

			document.body.dispatchEvent(createMouseEvent("mouseup", 55));
		} finally {
			window.requestAnimationFrame = originalRequestAnimationFrame;
			globalThis.requestAnimationFrame = originalGlobalRequestAnimationFrame;
		}
	});
});
