import { browser } from "$app/environment";
import type { Track } from "./types";

export type MediaSessionHandlers = {
	play: () => void;
	pause: () => void;
	next: () => void;
	prev: () => void;
	seekTo?: (timeSeconds: number) => void;
	seekBy?: (deltaSeconds: number) => void;
};

export type MediaSessionBindings = {
	/**
	 * Call whenever the current track changes.
	 */
	setTrack: (track: Track | null) => void;

	/**
	 * Call on play/pause changes if you want to keep Media Session "active" state
	 * aligned with your app (handlers still work regardless).
	 */
	setPlaybackState: (state: "none" | "paused" | "playing") => void;

	/**
	 * Call reasonably often (e.g. on `timeupdate`, `loadedmetadata`, `ratechange`)
	 * to keep lockscreen / OS UI in sync.
	 */
	updatePositionState: (args: {
		duration: number;
		position: number;
		playbackRate?: number;
	}) => void;

	/**
	 * Unregisters handlers. Optional — layout-scoped players typically never unmount.
	 */
	destroy: () => void;
};

function canUseMediaSession() {
	return (
		browser &&
		typeof navigator !== "undefined" &&
		"mediaSession" in navigator &&
		typeof (navigator as Navigator).mediaSession !== "undefined"
	);
}

function canUseMetadata() {
	return typeof MediaMetadata !== "undefined";
}

export function createMediaSessionBindings(
	handlers: MediaSessionHandlers,
): MediaSessionBindings {
	const mediaSession = canUseMediaSession()
		? (navigator as Navigator).mediaSession
		: null;

	const setActionHandler = (
		action: MediaSessionAction,
		handler: MediaSessionActionHandler | null,
	) => {
		if (!mediaSession) return;
		try {
			mediaSession.setActionHandler(action, handler);
		} catch {
			// Some browsers throw for unsupported actions; ignore.
		}
	};

	const safeNumber = (n: number) => (Number.isFinite(n) ? n : 0);

	const setTrack = (track: Track | null) => {
		if (!mediaSession) return;
		if (!canUseMetadata()) return;

		if (!track) {
			// Keep it simple: clear metadata.
			mediaSession.metadata = null;
			return;
		}

		function typeNumberLabel(t: Track) {
			const type = t.type;
			const n = Number(t.number ?? 0);

			let typeLabel: string | null = null;
			if (typeof type === "number") {
				if (type === 1) typeLabel = "OP";
				else if (type === 2) typeLabel = "ED";
				else if (type === 3) typeLabel = "INS";
				else typeLabel = `T${type}`;
			}

			if (!typeLabel) return null;
			return `${typeLabel}${n ? String(n) : ""}`;
		}
		mediaSession.metadata = new MediaMetadata({
			title: `${track.animeName} (${typeNumberLabel(track)}) — ${track.title}`,
			artist: track.artist,
			album: track.album,
			// You can add artwork later if/when you have it:
			// artwork: [{ src: "/some.png", sizes: "512x512", type: "image/png" }]
		});
	};

	const setPlaybackState = (state: "none" | "paused" | "playing") => {
		if (!mediaSession) return;
		try {
			mediaSession.playbackState = state;
		} catch {
			// Some browsers may not implement playbackState; ignore.
		}
	};

	const updatePositionState = (args: {
		duration: number;
		position: number;
		playbackRate?: number;
	}) => {
		if (!mediaSession) return;

		const anySession = mediaSession as unknown as {
			setPositionState?: (state: {
				duration: number;
				playbackRate?: number;
				position: number;
			}) => void;
		};

		if (typeof anySession.setPositionState !== "function") return;

		const duration = Math.max(0, safeNumber(args.duration));
		const position = Math.max(0, safeNumber(args.position));
		const playbackRate = args.playbackRate ?? 1;

		try {
			anySession.setPositionState({
				duration,
				position: Math.min(position, duration || position),
				playbackRate,
			});
		} catch {
			// iOS Safari and some Chromium variants can throw on invalid values.
		}
	};

	const installHandlers = () => {
		if (!mediaSession) return;

		setActionHandler("play", () => handlers.play());
		setActionHandler("pause", () => handlers.pause());
		setActionHandler("previoustrack", () => handlers.prev());
		setActionHandler("nexttrack", () => handlers.next());

		// Seeking (optional)
		setActionHandler("seekto", (details) => {
			if (!handlers.seekTo) return;
			const d = details as MediaSessionActionDetails & { seekTime?: number };
			if (typeof d.seekTime !== "number") return;
			handlers.seekTo(d.seekTime);
		});

		setActionHandler("seekbackward", (details) => {
			const d = details as MediaSessionActionDetails & { seekOffset?: number };
			const offset = typeof d.seekOffset === "number" ? d.seekOffset : 10;
			if (handlers.seekBy) handlers.seekBy(-offset);
			else if (handlers.seekTo) handlers.seekTo(0); // fallback-ish
		});

		setActionHandler("seekforward", (details) => {
			const d = details as MediaSessionActionDetails & { seekOffset?: number };
			const offset = typeof d.seekOffset === "number" ? d.seekOffset : 10;
			if (handlers.seekBy) handlers.seekBy(offset);
		});

		// Stop isn't as universally supported; map to pause.
		setActionHandler("stop", () => handlers.pause());
	};

	const destroy = () => {
		if (!mediaSession) return;
		// Clear handlers we set.
		setActionHandler("play", null);
		setActionHandler("pause", null);
		setActionHandler("previoustrack", null);
		setActionHandler("nexttrack", null);
		setActionHandler("seekto", null);
		setActionHandler("seekbackward", null);
		setActionHandler("seekforward", null);
		setActionHandler("stop", null);
	};

	installHandlers();

	return {
		setTrack,
		setPlaybackState,
		updatePositionState,
		destroy,
	};
}
