import type { Step } from "@circuy/contracts";
import {
	arrow,
	autoUpdate,
	computePosition,
	flip,
	offset,
	shift,
} from "@floating-ui/dom";
import { stylesheet } from "./styles.js";

export type RenderHandlers = {
	onNext: () => void;
	onDismiss: () => void;
	onChoice: (id: string) => void;
};

let host: HTMLDivElement | null = null;
let root: ShadowRoot | null = null;
let stopAuto: (() => void) | null = null;
let previousFocus: Element | null = null;
let onKey: ((event: KeyboardEvent) => void) | null = null;

export function mountHost(): ShadowRoot {
	if (root) return root;
	host = document.createElement("div");
	host.dataset.circuy = "";
	root = host.attachShadow({ mode: "open" });
	const sheet = new CSSStyleSheet();
	sheet.replaceSync(stylesheet);
	root.adoptedStyleSheets = [sheet];
	document.body.append(host);
	return root;
}

function stopPositioning(): void {
	stopAuto?.();
	stopAuto = null;
}

function detachKeys(): void {
	if (onKey) document.removeEventListener("keydown", onKey);
	onKey = null;
}

export function hideStep(): void {
	stopPositioning();
	detachKeys();
	root?.replaceChildren();
}

export function destroyHost(): void {
	hideStep();
	host?.remove();
	host = null;
	root = null;
}

export function restoreFocus(): void {
	if (previousFocus instanceof HTMLElement) previousFocus.focus();
	previousFocus = null;
}

function focusables(container: HTMLElement): HTMLElement[] {
	return [
		...container.querySelectorAll<HTMLElement>(
			'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
		),
	];
}

export function showStep(
	step: Step,
	target: Element | null,
	handlers: RenderHandlers,
): void {
	const shadow = mountHost();
	hideStep();
	if (!previousFocus) previousFocus = document.activeElement;

	const bubble = document.createElement("div");
	bubble.className = "bubble";
	bubble.dataset.kind = step.type;
	const titleId = `circuy-title-${step.id}`;
	if (step.type === "modal" || step.type === "question") {
		bubble.setAttribute("role", "dialog");
		bubble.setAttribute("aria-modal", "true");
	} else {
		bubble.setAttribute("role", "tooltip");
	}
	bubble.setAttribute("aria-labelledby", titleId);

	const title = document.createElement("p");
	title.className = "title";
	title.id = titleId;
	title.textContent = step.title;

	const body = document.createElement("div");
	body.className = "body";
	if ("body" in step) body.innerHTML = step.body ?? "";

	const actions = document.createElement("div");
	actions.className = "actions";

	const close = document.createElement("button");
	close.className = "secondary";
	close.type = "button";
	close.textContent = "Fermer";
	close.addEventListener("click", handlers.onDismiss);
	actions.append(close);

	if (step.type === "question") {
		for (const option of step.options) {
			const button = document.createElement("button");
			button.type = "button";
			button.textContent = option.label;
			button.addEventListener("click", () => handlers.onChoice(option.id));
			actions.append(button);
		}
	} else if (
		step.type === "modal" ||
		(step.type === "tooltip" && (step.advance ?? "button") === "button")
	) {
		const next = document.createElement("button");
		next.type = "button";
		next.textContent = "Suivant";
		next.addEventListener("click", handlers.onNext);
		actions.append(next);
	}

	const arrowEl = document.createElement("div");
	arrowEl.className = "arrow";
	bubble.append(title, body, actions, arrowEl);

	let highlight: HTMLDivElement | null = null;
	if (step.type === "tooltip" && step.highlight && target) {
		highlight = document.createElement("div");
		highlight.className = "highlight";
	}

	const nodes: HTMLElement[] = highlight ? [highlight, bubble] : [bubble];
	shadow.replaceChildren(...nodes);

	const trap = step.type === "modal" || step.type === "question";
	onKey = (event: KeyboardEvent) => {
		if (event.key === "Escape") {
			event.preventDefault();
			handlers.onDismiss();
			return;
		}
		if (!trap || event.key !== "Tab") return;
		const list = focusables(bubble);
		const first = list[0];
		const last = list[list.length - 1];
		if (!first || !last) return;
		const active = shadow.activeElement;
		if (event.shiftKey && active === first) {
			event.preventDefault();
			last.focus();
		} else if (!event.shiftKey && active === last) {
			event.preventDefault();
			first.focus();
		}
	};
	document.addEventListener("keydown", onKey);

	queueMicrotask(() => bubble.querySelector("button")?.focus());

	if (step.type !== "tooltip" || !(target instanceof HTMLElement)) return;

	bubble.style.transform = "none";
	const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
	target.scrollIntoView({
		block: "center",
		behavior: reduced ? "auto" : "smooth",
	});

	const placement = step.placement ?? "bottom";
	const update = () => {
		void computePosition(target, bubble, {
			strategy: "fixed",
			placement,
			middleware: [
				offset(12),
				flip(),
				shift({ padding: 8 }),
				arrow({ element: arrowEl }),
			],
		}).then(({ x, y, placement: used, middlewareData }) => {
			Object.assign(bubble.style, { left: `${x}px`, top: `${y}px` });
			const arrowData = middlewareData.arrow;
			if (arrowData) {
				const side = used.split("-")[0] ?? "bottom";
				const staticSide =
					{ top: "bottom", right: "left", bottom: "top", left: "right" }[
						side
					] ?? "top";
				Object.assign(arrowEl.style, {
					left: arrowData.x != null ? `${arrowData.x}px` : "",
					top: arrowData.y != null ? `${arrowData.y}px` : "",
					right: "",
					bottom: "",
					[staticSide]: "-5px",
				});
			}
			if (highlight) {
				const rect = target.getBoundingClientRect();
				Object.assign(highlight.style, {
					top: `${rect.top}px`,
					left: `${rect.left}px`,
					width: `${rect.width}px`,
					height: `${rect.height}px`,
				});
			}
		});
	};
	stopAuto = autoUpdate(target, bubble, update);
}
