/**
 * Push notification manager for rMaps — service worker registration,
 * VAPID subscription, and location request pinging.
 */

const STORAGE_KEY = "rmaps_push_subscribed";

export class MapPushManager {
	private apiBase: string;
	private registration: ServiceWorkerRegistration | null = null;
	private subscription: PushSubscription | null = null;
	private _subscribed = false;
	private _pingCooldowns: Map<string, number> = new Map();

	constructor(apiBase: string) {
		this.apiBase = apiBase;
		this._subscribed = localStorage.getItem(STORAGE_KEY) === "true";
		this.initServiceWorker();
	}

	get isSupported(): boolean {
		return "Notification" in window && "serviceWorker" in navigator && "PushManager" in window;
	}

	get isSubscribed(): boolean {
		return this._subscribed;
	}

	private async initServiceWorker() {
		if (!this.isSupported) return;
		try {
			this.registration = await navigator.serviceWorker.ready;
			// Check existing subscription
			this.subscription = await this.registration.pushManager.getSubscription();
			if (this.subscription) {
				this._subscribed = true;
				localStorage.setItem(STORAGE_KEY, "true");
			}
		} catch {
			// SW not available
		}
	}

	private async getVapidPublicKey(): Promise<string | null> {
		try {
			const res = await fetch(`${this.apiBase}/api/push/vapid-public-key`, {
				signal: AbortSignal.timeout(5000),
			});
			if (res.ok) {
				const data = await res.json();
				return data.publicKey || data.key || null;
			}
		} catch {}
		return null;
	}

	private urlBase64ToUint8Array(base64String: string): Uint8Array {
		const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
		const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
		const rawData = atob(base64);
		const outputArray = new Uint8Array(rawData.length);
		for (let i = 0; i < rawData.length; i++) {
			outputArray[i] = rawData.charCodeAt(i);
		}
		return outputArray;
	}

	async subscribe(): Promise<boolean> {
		if (!this.isSupported || !this.registration) return false;

		try {
			// Request notification permission
			const permission = await Notification.requestPermission();
			if (permission !== "granted") return false;

			// Get VAPID key
			const vapidKey = await this.getVapidPublicKey();
			if (!vapidKey) return false;

			// Subscribe via PushManager
			this.subscription = await this.registration.pushManager.subscribe({
				userVisibleOnly: true,
				applicationServerKey: this.urlBase64ToUint8Array(vapidKey) as any,
			});

			// Sync with server
			const res = await fetch(`${this.apiBase}/api/push/subscribe`, {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({ subscription: this.subscription.toJSON() }),
			});

			if (res.ok) {
				this._subscribed = true;
				localStorage.setItem(STORAGE_KEY, "true");
				return true;
			}
		} catch (err) {
			console.warn("Push subscribe failed:", err);
		}
		return false;
	}

	async unsubscribe(): Promise<boolean> {
		if (!this.subscription) return true;

		try {
			// Notify server
			await fetch(`${this.apiBase}/api/push/unsubscribe`, {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({ subscription: this.subscription.toJSON() }),
			}).catch(() => {});

			// Unsubscribe from PushManager
			await this.subscription.unsubscribe();
			this.subscription = null;
			this._subscribed = false;
			localStorage.removeItem(STORAGE_KEY);
			return true;
		} catch {
			return false;
		}
	}

	async toggle(roomSlug: string, participantId: string): Promise<boolean> {
		if (this._subscribed) {
			await this.unsubscribe();
			return false;
		} else {
			const ok = await this.subscribe();
			return ok;
		}
	}

	async requestLocation(roomSlug: string, participantId: string): Promise<boolean> {
		// 30s cooldown per participant
		const cooldownKey = `${roomSlug}:${participantId}`;
		const lastPing = this._pingCooldowns.get(cooldownKey);
		if (lastPing && Date.now() - lastPing < 30000) return false;
		this._pingCooldowns.set(cooldownKey, Date.now());

		try {
			const res = await fetch(`${this.apiBase}/api/push/request-location`, {
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({ roomSlug, participantId }),
			});
			return res.ok;
		} catch {
			return false;
		}
	}
}
