// Thin wrapper around the Screen Wake Lock API. Held by the diashow page so a phone
// driving a projector doesn't sleep. No-op on unsupported browsers (Firefox, older
// Safari).
//
// Wake locks die when the document goes hidden; the page re-acquires on visible to
// keep the screen on across short interruptions.

interface SentinelLike {
	release: () => Promise<void>;
	// WakeLockSentinel is an EventTarget; it fires `release` when the lock ends — including
	// when the OS drops it because the tab was hidden.
	addEventListener?: (type: 'release', listener: () => void) => void;
}

type WakeLock = { request: (t: string) => Promise<SentinelLike> };

let sentinel: SentinelLike | null = null;
let visibilityHandler: (() => void) | null = null;
let retryTimer: ReturnType<typeof setInterval> | null = null;

/**
 * How often to retry while visible and lock-less.
 *
 * `visibilitychange` was the ONLY retry trigger, and a kiosk never changes visibility: the
 * projector tab is opened once and left alone for eight hours. So a single refusal at startup
 * was permanent. Refusal is not exotic either — iOS declines Screen Wake Lock outright in Low
 * Power Mode, which is exactly the state a tablet that has been sitting on a table all
 * afternoon is in. The symptom is the screen sleeping mid-party and someone having to walk
 * over and tap it, repeatedly.
 */
const RETRY_INTERVAL_MS = 30_000;

async function request(wakeLock: WakeLock): Promise<void> {
	try {
		sentinel = await wakeLock.request('screen');
		// The OS auto-releases the lock when the tab is hidden. Without clearing our handle
		// on that event, the visibility handler's `sentinel === null` guard would never be
		// true and the lock would never be re-acquired — the screen could then sleep after
		// the first time the tab lost focus. Null it on release so re-acquire can fire.
		sentinel.addEventListener?.('release', () => {
			sentinel = null;
		});
	} catch {
		// User denied, or already released — nothing useful to do.
		sentinel = null;
	}
}

/**
 * Acquire the screen wake lock, and keep trying.
 *
 * Returns whether the API exists at all, so the caller can tell "the browser cannot do this,
 * warn the operator" (Firefox, Safari < 16.4, most TV browsers) apart from "asked for, may
 * still arrive". It deliberately does NOT report whether the first request succeeded — that
 * answer goes stale immediately, and the retry loop below is what actually matters.
 */
export async function acquireWakeLock(): Promise<boolean> {
	const wakeLock = (navigator as Navigator & { wakeLock?: WakeLock }).wakeLock;
	if (!wakeLock) return false;
	await request(wakeLock);

	// Re-acquire when the page becomes visible again (the OS releases the lock
	// while the tab is hidden).
	if (!visibilityHandler) {
		visibilityHandler = () => {
			if (document.visibilityState === 'visible' && sentinel === null) {
				void request(wakeLock);
			}
		};
		document.addEventListener('visibilitychange', visibilityHandler);
	}

	// The kiosk case: visible, no lock, and no visibility change ever coming. Cheap enough to
	// run all evening — it does nothing at all once a lock is held.
	if (!retryTimer) {
		retryTimer = setInterval(() => {
			if (document.visibilityState === 'visible' && sentinel === null) {
				void request(wakeLock);
			}
		}, RETRY_INTERVAL_MS);
	}
	return true;
}

export async function releaseWakeLock(): Promise<void> {
	if (sentinel) {
		try {
			await sentinel.release();
		} catch {
			// ignore — release after release is fine
		}
		sentinel = null;
	}
	if (visibilityHandler) {
		document.removeEventListener('visibilitychange', visibilityHandler);
		visibilityHandler = null;
	}
	if (retryTimer) {
		clearInterval(retryTimer);
		retryTimer = null;
	}
}
