<script lang="ts">
	import { setContext, onMount, onDestroy } from 'svelte';
	import { invalidateAll, goto } from '$app/navigation';
	import type { LayoutData } from './$types';
	import AppSidebar from '$lib/components/sidebar/app-sidebar.svelte';
	import AppLauncher from '$lib/components/sidebar/app-launcher.svelte';
	import * as Breadcrumb from '$lib/components/ui/breadcrumb/index.js';
	import { Separator } from '$lib/components/ui/separator/index.js';
	import * as Sidebar from '$lib/components/ui/sidebar/index.js';
	import { companyStore } from '$lib/stores/company.svelte';
	import SessionTimeoutWarning from '$lib/components/session-timeout-warning.svelte';
	import { page } from '$app/state';
	import { browser } from '$app/environment';
	import {
		createSessionManager,
		destroySessionManager,
		SESSION_EXPIRED_EVENT
	} from '$lib/session-manager';
	import type { SessionExpiredDetail } from '$lib/session-manager';
	import { authStore, loadSidebarPermsCache, markPermissionsHydrated, logout, getKeycloakInstance } from '$lib/auth';
	import { get } from 'svelte/store';
	import LicenseErrorScreen from '$lib/components/license-error-screen.svelte';
	import { systemStore, type SystemType } from '$lib/stores/system.svelte';
	import { workspaceAppsStore } from '$lib/stores/workspace-apps.svelte';

	type LicenseError = { type: string; message: string; status: number };
	let { data, children }: { data: LayoutData & { licenseError?: LicenseError }; children: any } = $props();


	// Hacer disponible el usuario en el contexto para los componentes hijos.
	// El sidebar ya lee de page.data directamente; este contexto lo usan otros componentes.
	setContext('user', data.user);
	setContext('userTenants', data.userTenants ?? []);
	// Actualizar el contexto reactive al cambiar data.user (post invalidateAll)
	$effect(() => { setContext('user', data.user); });

	// ── Sembrar el authStore desde data.user ───────────────────────────────
	// Se ejecuta SÍNCRONAMENTE en el script (SSR + hidratación), no en onMount,
	// para que `$currentUser` ya esté poblado en el primer render de las páginas
	// hijas. Evita el flash de "Acceso restringido" al refrescar cuando las páginas
	// derivan `canView` a partir de `$currentUser`.
	function syncAuthStoreFromData() {
		if (!data.user) return;
		if (data.user.token) {
			authStore.setToken(data.user.token);
			authStore.setAuthenticated(true);
		}
		const resolvedAvatarUrl =
			data.user.workspaceAvatarUrl ||
			data.user.workspace_avatar_url ||
			data.user.avatarUrl ||
			data.user.avatar_url ||
			data.user.legacyAvatarUrl ||
			data.user.legacy_avatar_url ||
			null;
		authStore.setUser({
			id: data.user.sub ?? data.user.id ?? '',
			username: data.user.preferred_username ?? data.user.username ?? '',
			email: data.user.email,
			name: data.user.name,
			avatarUrl: resolvedAvatarUrl,
			workspaceAvatarUrl: data.user.workspaceAvatarUrl ?? data.user.workspace_avatar_url ?? null,
			legacyAvatarUrl: data.user.legacyAvatarUrl ?? data.user.legacy_avatar_url ?? data.user.avatar_url ?? null,
			tenantId: data.user.tenant_id,
			roles: data.user.roles ?? [],
			permissions: data.user.permissions ?? [],
			allowedSystems: data.allowedSystems ?? data.user.allowedSystems ?? []
		});
	}

	syncAuthStoreFromData();
	systemStore.initialize(
		(data.allowedSystems ?? []) as SystemType[],
		(data.activeSystem ?? null) as string | null
	);
	workspaceAppsStore.initialize(data.workspaceApps ?? [], data.appRouting ?? null);

	// Pre-popular permisos desde localStorage en el frame síncrono, antes del
	// primer render, para que el sidebar muestre los ítems completos sin parpadeo.
	// Esto cubre la segunda visita y tabs nuevas; la primera visita no tiene cache.
	if (browser) {
		const _preUserId = data.user?.sub ?? (data.user as any)?.id ?? null;
		const _preCidRaw = (() => { try { return localStorage.getItem('activeCompanyId'); } catch { return null; } })();
		const _preCid = _preCidRaw !== null ? parseInt(_preCidRaw) : NaN;
		if (_preUserId && Number.isFinite(_preCid)) {
			const _cached = loadSidebarPermsCache(_preUserId, _preCid);
			if (_cached) {
				const _u = get(authStore).user;
				if (_u) {
					authStore.setUser({
						..._u,
						permissions: _cached.permissions,
						roles: Array.from(new Set([...(_u.roles ?? []), ..._cached.roles])),
						allowedSystems: _cached.allowedSystems.length > 0
							? (_cached.allowedSystems as SystemType[])
							: _u.allowedSystems,
						tenantId: _cached.tenantId ?? _u.tenantId
					});
					markPermissionsHydrated();
				}
			}
		}
	}

	// NOTA: el $effect que re-corría syncAuthStoreFromData() en cada cambio de
	// data.user fue eliminado porque destruía los permisos del store en cada
	// navegación. El SSR no es fuente de verdad para `permissions`/`roles`
	// (los carga el cliente vía syncCompanyPermissions en companyStore), así
	// que reaplicar `data.user.permissions ?? []` los reseteaba a `[]`.
	//
	// Ahora: la sincronización síncrona de arriba cubre la hidratación inicial
	// (SSR + refresh). El refresco ante un cambio de compañía se hace de forma
	// explícita en `handleCompanyChange` más abajo, donde sí cambian sistemas
	// y apps. La identidad del usuario no cambia entre rutas ni entre
	// compañías, así que no requiere refresco reactivo.

	// ── Manejar expiración de sesión ────────────────────────────────────────
	function handleSessionExpired(e: Event) {
		const { reason } = (e as CustomEvent<SessionExpiredDetail>).detail;
		console.info(`[Dashboard] Sesión expirada (motivo: ${reason}) — cerrando sesión`);
		void logout();
	}

	onMount(() => {
		// ── Inicializar el SessionManager ─────────────────────────────────────
		if (data.user?.token) {
			const mgr = createSessionManager({
				refreshBeforeExpirySeconds: 60,      // Refrescar 60s antes de que expire
				idleTimeoutMs: 30 * 60 * 1000,       // Idle timeout: 30 minutos
				warningBeforeIdleMs: 5 * 60 * 1000,  // Advertencia: 5 min antes del idle
				ssoCheckIntervalMs: 5 * 60 * 1000,   // Verificar sesión SSO cada 5 min
				getKeycloakInstance,
				onTokenRefreshed: (newToken) => {
					authStore.setToken(newToken);
				},
				onSessionExpired: (reason) => {
					void logout();
				}
			});

			mgr.start(data.user.token);
		}

		// ── Escuchar evento global de expiración de sesión ────────────────────
		window.addEventListener(SESSION_EXPIRED_EVENT, handleSessionExpired);

		// ── Inicializar compañías ─────────────────────────────────────────────
		if (data.companies) {
			const activeCompanyId = (data as { activeCompanyId?: number }).activeCompanyId;
			void (async () => {
				await companyStore.initialize(data.companies, activeCompanyId);
				// Releer en cliente: el SSR puede haber quedado desfasado respecto al provisionamiento.
				await companyStore.loadCompanies(undefined, activeCompanyId);
				// Garantiza que el flag se levante incluso si no hay compañía activa
				// (en ese caso `syncCompanyPermissions` nunca corre).
				markPermissionsHydrated();
			})();
		} else {
			markPermissionsHydrated();
		}

		// ── Escuchar cambios de compañía y recargar datos ─────────────────────
		const handleCompanyChange = async () => {
			await invalidateAll();
			// Re-poblar sistemas y apps con la nueva data tras invalidateAll.
			// Antes lo hacía el $effect que reaccionaba a data.user, ahora se
			// hace de forma explícita solo en el cambio de compañía (única vez
			// que esos datos realmente cambian). No tocamos authStore aquí:
			// identidad no cambia entre compañías, y los permisos los maneja
			// syncCompanyPermissions que se dispara dentro de setActiveCompany.
			systemStore.initialize(
				(data.allowedSystems ?? []) as SystemType[],
				(data.activeSystem ?? null) as string | null
			);
			workspaceAppsStore.initialize(data.workspaceApps ?? [], data.appRouting ?? null);
			await goto(`${page.url.pathname}${page.url.search}${page.url.hash}`, { invalidateAll: true });
		};
		window.addEventListener('companyChanged', handleCompanyChange);

		return () => {
			window.removeEventListener('companyChanged', handleCompanyChange);
			window.removeEventListener(SESSION_EXPIRED_EVENT, handleSessionExpired);
		};
	});

	onDestroy(() => {
		destroySessionManager();
	});
</script>

{#if data.licenseError}
	<LicenseErrorScreen error={data.licenseError} />
{:else}
<Sidebar.Provider>
	<AppSidebar />
	<Sidebar.Inset class="overflow-x-hidden">
		<header
			class="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12"
		>
			<div class="flex items-center gap-2 px-4">
				<Sidebar.Trigger class="-ml-1" />
				<Separator orientation="vertical" class="mr-2 data-[orientation=vertical]:h-4" />
			</div>
			{#if workspaceAppsStore.hasApps}
				<div class="ml-auto px-4">
					<AppLauncher />
				</div>
			{/if}
		</header>
		<div
			id="dashboard-main-content"
			class="flex flex-1 flex-col min-h-0 gap-4 overflow-y-auto overflow-x-hidden p-4 pt-0"
		>
			{@render children?.()}
		</div>
	</Sidebar.Inset>
</Sidebar.Provider>

<SessionTimeoutWarning />
{/if}
