<script lang="ts">
	import '../app.css';
	import { onMount } from 'svelte';
	import { goto } from '$app/navigation';
	import { page } from '$app/stores';
	import { getSupabase } from '$lib/supabase';
	import { currentUser, authLoading } from '$lib/stores/auth';
	import { userCollectives, currentCollective } from '$lib/stores/collective';
	import {
		loadCurrentUserFeatures,
		subscribeCollectiveFeatures,
		teardownFeatureSubscriptions
	} from '$lib/stores/features';
	import type { FeatureFlags } from '@colectivo/types';
	import { ParaglideJS } from '@inlang/paraglide-sveltekit';
	import { i18n } from '$lib/i18n';
	import { initTheme } from '$lib/theme';
	import { setLanguageTag } from '$lib/paraglide/runtime';
	import { detectLanguage } from '$lib/utils/accept-language';

	onMount(() => {
		// Fase 9.1: hydrate the theme stores from localStorage and start
		// listening for OS prefers-color-scheme changes. The inline script
		// in app.html has already set <html data-theme> synchronously to
		// avoid FOUC — initTheme just builds the JS-side state on top.
		initTheme();

		// Dev-only: expose the supabase client on window so Playwright tests
		// can drive PostgREST through it without re-creating a parallel
		// client. Production never sees this (import.meta.env.DEV is
		// dead-code-eliminated by Vite at build time).
		if (import.meta.env.DEV) {
			(window as unknown as { __sb?: unknown }).__sb = getSupabase();
		}

		// Fase 6.1: register the PWA service worker. `@vite-pwa/sveltekit` does
		// not auto-register — the virtual module is a no-op unless called.
		void import('virtual:pwa-register').then(({ registerSW }) => {
			registerSW({ immediate: true });
		});

		const supabase = getSupabase();

		const {
			data: { subscription }
		} = supabase.auth.onAuthStateChange((event, session) => {
			currentUser.set(session?.user ?? null);
			authLoading.set(false);

			if (session) {
				// Defer out of the onAuthStateChange callback so the internal auth
				// lock is fully released before we make any PostgREST query.
				// Without this, queries hang on `getAccessToken() -> initializePromise`
				// in some browser environments (e.g. Playwright headless Chromium).
				setTimeout(async () => {
					await loadUserCollectives(session.user.id);

					// Fase 12.2: load the user's feature_flags + subscribe to
					// realtime updates (own row). Best-effort: a failure here
					// only degrades to default-ON for every section.
					try {
						await loadCurrentUserFeatures(session.user.id);
					} catch {
						/* non-fatal */
					}

					// Fase 10.8: auto-detect language for genuinely new users.
					// Best-effort, runs after every auth event so newly-seeded
					// (post-Keycloak-self-registration) rows pick up the user's
					// browser preference instead of the seed default 'en'.
					await maybeBootstrapLanguage(session.user.id);

					if (event === 'SIGNED_IN') {
						// Post-login: send the user where they belong.
						// Only redirect when coming from the callback or the root — not on
						// token refreshes or when the user is already on a real route.
						const path = $page.url.pathname;
						if (path === '/auth/callback' || path === '/') {
							// If the user came in via /invitation/[token] and had to
							// log in first, resume that flow instead of going to
							// /lists or /onboarding.
							const pendingToken = sessionStorage.getItem('pendingInvitationToken');
							if (pendingToken) {
								sessionStorage.removeItem('pendingInvitationToken');
								goto(`/invitation/${pendingToken}`);
							} else if ($userCollectives.length === 0) {
								goto('/onboarding');
							} else {
								goto('/lists');
							}
						}
					}
				}, 0);
			}
		});

		// Fase 12.2: keep the active-collective realtime subscription in sync.
		// Subscribing here (after onAuthStateChange) rather than at the auth
		// callback level means it follows the user across collective switches
		// without needing a sign-in event. Tear down on layout destroy too.
		const collectiveUnsub = currentCollective.subscribe((c) => {
			if (c) void subscribeCollectiveFeatures(c.id);
		});

		return () => {
			subscription.unsubscribe();
			collectiveUnsub();
			void teardownFeatureSubscriptions();
		};
	});

	/**
	 * Fase 10.8 — Accept-Language detection for first-time users.
	 *
	 * The Paraglide stack is compile-time and the OIDC dance lives outside
	 * SvelteKit, so we can't sniff the real Accept-Language header server-
	 * side here without standing up a hooks.server.ts that the auth flow
	 * never traverses. Instead we use `navigator.languages` (the browser's
	 * Accept-Language proxy) on the client right after the first SIGNED_IN.
	 *
	 * Idempotency: we only UPDATE when the row was just created (within
	 * 60s of now) AND `language` still equals the seed default ('en'). This
	 * means a Spanish-browser user lands in Spanish on first run; an
	 * established user who later switched themselves to English stays
	 * English; existing users are not retroactively re-detected (plan
	 * §10.8.5 — respect their previous explicit choice).
	 */
	async function maybeBootstrapLanguage(userId: string): Promise<void> {
		const supabase = getSupabase();
		const { data } = await supabase
			.from('users')
			.select('language, created_at')
			.eq('id', userId)
			.single();
		if (!data) return;

		const ageMs = Date.now() - new Date(data.created_at).getTime();
		const isFreshAccount = ageMs < 60_000;
		if (!isFreshAccount) return;
		if (data.language !== 'en') return;

		const langs =
			(typeof navigator !== 'undefined' &&
				(navigator.languages?.length ? Array.from(navigator.languages) : [navigator.language])) ||
			[];
		const detected = detectLanguage(langs);
		if (detected === 'en') return;

		await supabase.from('users').update({ language: detected }).eq('id', userId);
		setLanguageTag(detected);
	}

	async function loadUserCollectives(userId: string): Promise<void> {
		const supabase = getSupabase();

		const { data } = await supabase
			.from('collective_members')
			.select(
				'collective_id, role, collectives(id, name, emoji, created_at, feature_flags)'
			)
			.eq('user_id', userId);

		if (!data) return;

		type CollectiveRow = {
			id: string;
			name: string;
			emoji: string;
			created_at: string;
			feature_flags: FeatureFlags;
		};
		const collectives = data
			.map((row) => {
				const c = row.collectives as
					| { id: string; name: string; emoji: string; created_at: string; feature_flags: FeatureFlags | null }
					| null;
				return c
					? {
							id: c.id,
							name: c.name,
							emoji: c.emoji,
							created_at: c.created_at,
							feature_flags: (c.feature_flags ?? {}) as FeatureFlags
						}
					: null;
			})
			.filter((c): c is CollectiveRow => c !== null);

		userCollectives.set(collectives);

		// Restore last active collective from localStorage, or default to first
		const savedId = localStorage.getItem('activeCollectiveId');
		const active = collectives.find((c) => c.id === savedId) ?? collectives[0] ?? null;
		currentCollective.set(active);
		if (active) localStorage.setItem('activeCollectiveId', active.id);
	}
</script>

<ParaglideJS {i18n}>
	<slot />
</ParaglideJS>
