import { Navigate, Route, Routes } from "react-router-dom";
import { lazy, Suspense, useEffect, useMemo, useState } from "react";
import { fetchSession, fetchShellAuth, updateProfile } from "./api/auth";
import { fetchPlatformModules, fetchPlatformStatus } from "./api/platform";
import { AUTH_REQUIRED_EVENT, isApiError, loadApiSettings, saveApiSettings, type AuthRequiredEventDetail } from "./api/client";
import type { ApiSettings, AuthInfo, AuthSessionInfo, AuthUpdate, AuthUser, LoginResponse, PlatformModuleInfo, PlatformWebModule, UserUiPreferences } from "./types";
import AppShell from "./layout/AppShell";
import PublicLandingPage from "./features/auth/PublicLandingPage";
import LoginModal from "./features/auth/LoginModal";
import { PermissionBoundary } from "./components/AccessBoundary";
import { firstAccessibleRoute, loadRemoteWebModules, moduleInstalled, navItemsForModules, resolveInstalledWebModules, routeContributionsForModules } from "./platform/modules";
import { PlatformModulesProvider } from "./platform/ModuleContext";
import { PLATFORM_MODULES_CHANGED_EVENT } from "./platform/moduleEvents";
import { UnsavedChangesProvider } from "./components/UnsavedChangesGuard";
import { PlatformLanguageProvider, type PlatformLanguage } from "./i18n/LanguageContext";

const DashboardPage = lazy(() => import("./features/dashboard/DashboardPage"));
const SettingsPage = lazy(() => import("./features/settings/SettingsPage"));

const DEFAULT_UI_PREFERENCES: UserUiPreferences = {
  compact_tables: false,
  show_inline_help_hints: true,
  reduce_motion: false,
  sticky_section_sidebars: true,
  theme: "system"
};

export default function App() {
  const [settings, setSettings] = useState<ApiSettings>(() => loadApiSettings());
  const [auth, setAuth] = useState<AuthInfo | null>(null);
  const [checkingSession, setCheckingSession] = useState(true);
  const [platformModules, setPlatformModules] = useState<PlatformModuleInfo[] | null>(null);
  const [remoteWebModules, setRemoteWebModules] = useState<PlatformWebModule[]>([]);
  const [maintenanceMode, setMaintenanceMode] = useState<{enabled: boolean;message?: string | null;}>({ enabled: false, message: null });
  const [backendReachable, setBackendReachable] = useState(true);
  const [systemLanguages, setSystemLanguages] = useState<{available: PlatformLanguage[];enabled: string[];defaultLanguage: string;} | null>(null);
  const [reloginMessage, setReloginMessage] = useState("");

  const localWebModules = useMemo(() => resolveInstalledWebModules(platformModules), [platformModules]);
  const webModules = useMemo(() => mergeWebModules(localWebModules, remoteWebModules), [localWebModules, remoteWebModules]);
  const navItems = useMemo(() => navItemsForModules(webModules), [webModules]);
  const moduleRoutes = useMemo(() => routeContributionsForModules(webModules), [webModules]);
  const moduleTranslations = useMemo(() => webModules.map((module) => module.translations).filter(Boolean), [webModules]);
  const dashboardModuleInstalled = useMemo(() => moduleInstalled("dashboard", webModules), [webModules]);

  function updateSettings(next: ApiSettings) {
    setSettings(next);
    saveApiSettings(next);
  }

  function updateAuth(next: AuthUpdate | null, accessToken?: string) {
    const nextSettings = accessToken !== undefined ? { ...settings, accessToken } : settings;
    setAuth((current) => next ? normalizeAuthInfo(mergeAuthPayload(current, next)) : null);
    if (accessToken !== undefined) {
      setSettings(nextSettings);
      saveApiSettings(nextSettings);
    }
  }

  function authFromLoginResponse(response: LoginResponse): AuthInfo {
    return normalizeAuthInfo(response);
  }

  function handlePublicLogin(response: LoginResponse) {
    updateAuth(authFromLoginResponse(response), "");
  }

  function handleRelogin(response: LoginResponse) {
    updateAuth(authFromLoginResponse(response), "");
    setReloginMessage("");
  }

  useEffect(() => {
    function handleAuthRequired(event: Event) {
      if (!auth) return;
      const detail = (event as CustomEvent<AuthRequiredEventDetail>).detail;
      setReloginMessage(detail?.message || "i18n:govoplan-core.your_session_has_expired_sign_in_again_to_contin.4cc6a1b3");
    }

    window.addEventListener(AUTH_REQUIRED_EVENT, handleAuthRequired);
    return () => window.removeEventListener(AUTH_REQUIRED_EVENT, handleAuthRequired);
  }, [auth]);

  useEffect(() => {
    let cancelled = false;

    async function refreshPlatformStatus() {
      try {
        const response = await fetchPlatformStatus(settings);
        if (cancelled) return;
        setBackendReachable(true);
        setMaintenanceMode(response.maintenance_mode);
        if (response.i18n) {
          setSystemLanguages({
            available: response.i18n.available_languages.map((item) => ({
              code: item.code,
              label: item.label,
              nativeLabel: item.native_label ?? undefined
            })),
            enabled: response.i18n.enabled_languages,
            defaultLanguage: response.i18n.default_language
          });
        }
      } catch {
        if (!cancelled) {
          setBackendReachable(false);
          setMaintenanceMode({ enabled: false, message: null });
        }
      }
    }

    void refreshPlatformStatus();
    const interval = window.setInterval(() => {void refreshPlatformStatus();}, 30_000);
    window.addEventListener("focus", refreshPlatformStatus);
    return () => {
      cancelled = true;
      window.clearInterval(interval);
      window.removeEventListener("focus", refreshPlatformStatus);
    };
  }, [settings.apiBaseUrl]);

  useEffect(() => {
    let cancelled = false;
    setCheckingSession(true);

    async function bootstrapAuth() {
      try {
        const shellAuth = await fetchShellAuth(settings);
        if (!cancelled) {
          setBackendReachable(true);
          setAuth(normalizeAuthInfo(shellAuth));
        }
      } catch (error) {
        if (!cancelled) {
          setBackendReachable(isApiError(error));
          const cleared = { ...settings, accessToken: "" };
          setSettings(cleared);
          saveApiSettings(cleared);
          setAuth(null);
          setPlatformModules(null);
          setRemoteWebModules([]);
        }
      } finally {
        if (!cancelled) setCheckingSession(false);
      }
    }

    void bootstrapAuth();
    return () => {cancelled = true;};
  }, [settings.apiBaseUrl, settings.apiKey]);

  useEffect(() => {
    if (!auth) return;

    let cancelled = false;
    let inFlight = false;
    let lastRefreshAt = 0;
    function loadModules() {
      inFlight = true;
      lastRefreshAt = Date.now();
      return fetchPlatformModules(settings).
      then((response) => {if (!cancelled) setPlatformModules(response.modules);}).
      catch(() => {if (!cancelled) setPlatformModules(null);}).
      finally(() => {inFlight = false;});
    }

    void loadModules();

    function handleModulesChanged() {
      void loadModules();
    }

    function refreshVisibleModules() {
      if (document.visibilityState === "hidden" || inFlight) return;
      if (Date.now() - lastRefreshAt < 5_000) return;
      void loadModules();
    }

    window.addEventListener(PLATFORM_MODULES_CHANGED_EVENT, handleModulesChanged);
    window.addEventListener("focus", refreshVisibleModules);
    document.addEventListener("visibilitychange", refreshVisibleModules);
    return () => {
      cancelled = true;
      window.removeEventListener(PLATFORM_MODULES_CHANGED_EVENT, handleModulesChanged);
      window.removeEventListener("focus", refreshVisibleModules);
      document.removeEventListener("visibilitychange", refreshVisibleModules);
    };
  }, [auth?.user?.id, auth?.active_tenant?.id, auth?.tenant.id, settings.apiBaseUrl, settings.apiKey]);

  useEffect(() => {
    const preferences = auth?.user.ui_preferences ?? DEFAULT_UI_PREFERENCES;
    const root = document.documentElement;
    root.classList.toggle("ui-compact-tables", preferences.compact_tables);
    root.classList.toggle("ui-hide-help-hints", !preferences.show_inline_help_hints);
    root.classList.toggle("ui-reduce-motion", preferences.reduce_motion);
    root.classList.toggle("ui-no-sticky-section-sidebars", !preferences.sticky_section_sidebars);

    const systemDarkQuery = window.matchMedia?.("(prefers-color-scheme: dark)") ?? null;
    const applyTheme = () => {
      const resolvedTheme = preferences.theme === "system" ?
        systemDarkQuery?.matches ? "dark" : "light" :
        preferences.theme;
      root.dataset.theme = resolvedTheme;
      root.dataset.themePreference = preferences.theme;
    };

    applyTheme();
    if (preferences.theme !== "system" || !systemDarkQuery) {
      return undefined;
    }

    systemDarkQuery.addEventListener("change", applyTheme);
    return () => {
      systemDarkQuery.removeEventListener("change", applyTheme);
    };
  }, [
    auth?.user.ui_preferences?.compact_tables,
    auth?.user.ui_preferences?.show_inline_help_hints,
    auth?.user.ui_preferences?.reduce_motion,
    auth?.user.ui_preferences?.sticky_section_sidebars,
    auth?.user.ui_preferences?.theme
  ]);

  useEffect(() => {
    let cancelled = false;
    if (!auth || !platformModules?.length) {
      setRemoteWebModules([]);
      return () => {cancelled = true;};
    }
    loadRemoteWebModules(platformModules, localWebModules).
    then((modules) => {if (!cancelled) setRemoteWebModules(modules);}).
    catch(() => {if (!cancelled) setRemoteWebModules([]);});
    return () => {cancelled = true;};
  }, [auth?.user?.id, auth?.active_tenant?.id, auth?.tenant.id, platformModules, localWebModules]);

  useEffect(() => {
    if (!auth) return;

    const currentAuth = auth;
    let cancelled = false;
    let inFlight = false;
    let lastRefreshAt = 0;
    let lastShellRefreshAt = Date.now();

    async function refreshVisibleSession() {
      if (document.visibilityState === "hidden" || inFlight) return;
      const now = Date.now();
      if (now - lastRefreshAt < 5_000) return;

      inFlight = true;
      lastRefreshAt = now;
      try {
        const sessionInfo = await fetchSession(settings);
        if (cancelled) return;
        setBackendReachable(true);
        const shellRefreshDue = now - lastShellRefreshAt >= 60_000;
        if (!sessionMatchesAuth(sessionInfo, currentAuth) || shellRefreshDue) {
          const shellAuth = await fetchShellAuth(settings);
          if (cancelled) return;
          lastShellRefreshAt = Date.now();
          setAuth((current) => current && sessionMatchesAuth(sessionInfo, current)
            ? normalizeAuthInfo(mergeAuthPayload(current, shellAuth))
            : normalizeAuthInfo(shellAuth));
        }
      } catch (error) {
        if (!cancelled && !isApiError(error)) {
          setBackendReachable(false);
        }


        // A background refresh must not log the user out on a transient network error.
      } finally {inFlight = false;}
    }

    window.addEventListener("focus", refreshVisibleSession);
    document.addEventListener("visibilitychange", refreshVisibleSession);
    return () => {
      cancelled = true;
      window.removeEventListener("focus", refreshVisibleSession);
      document.removeEventListener("visibilitychange", refreshVisibleSession);
    };
  }, [auth?.user?.id, auth?.active_tenant?.id, auth?.tenant.id, settings.apiBaseUrl, settings.apiKey]);

  if (checkingSession) {
    return (
      <PlatformLanguageProvider systemAvailableLanguages={systemLanguages?.available} systemEnabledLanguageCodes={systemLanguages?.enabled} defaultLanguage={systemLanguages?.defaultLanguage} moduleTranslations={moduleTranslations}>
      <PlatformModulesProvider modules={webModules}>
        <UnsavedChangesProvider>
        <AppShell settings={settings} auth={null} onSettingsChange={updateSettings} onAuthChange={updateAuth} publicMode navItems={navItems} maintenanceMode={maintenanceMode} backendReachable={backendReachable}>
        <div className="public-landing">
          <section className="public-card">
            <div className="public-kicker">i18n:govoplan-core.govoplan.a84c0a85</div>
            <h1>i18n:govoplan-core.checking_session.e7d81968</h1>
            <p>i18n:govoplan-core.please_wait_while_the_local_session_is_verified.1dbd8fd3</p>
          </section>
        </div>
        </AppShell>
      </UnsavedChangesProvider>
      </PlatformModulesProvider>
      </PlatformLanguageProvider>);

  }

  if (!auth) {
    return (
      <PlatformLanguageProvider systemAvailableLanguages={systemLanguages?.available} systemEnabledLanguageCodes={systemLanguages?.enabled} defaultLanguage={systemLanguages?.defaultLanguage} moduleTranslations={moduleTranslations}>
      <PlatformModulesProvider modules={webModules}>
        <UnsavedChangesProvider>
        <AppShell settings={settings} auth={null} onSettingsChange={updateSettings} onAuthChange={updateAuth} publicMode navItems={navItems} maintenanceMode={maintenanceMode} backendReachable={backendReachable}>
          <PublicLandingPage settings={settings} maintenanceMode={maintenanceMode} onLogin={handlePublicLogin} />
        </AppShell>
      </UnsavedChangesProvider>
      </PlatformModulesProvider>
      </PlatformLanguageProvider>);

  }

  const defaultRoute = firstAccessibleRoute(auth, webModules);
  const authAvailableLanguages = auth.available_languages?.map((item) => ({
    code: item.code,
    label: item.label,
    nativeLabel: item.native_label ?? undefined
  }));

  function persistLanguagePreference(code: string) {
    void updateProfile(settings, { preferred_language: code }).then((next) => updateAuth(next)).catch(() => undefined);
  }

  return (
    <PlatformLanguageProvider
      systemAvailableLanguages={authAvailableLanguages ?? systemLanguages?.available}
      systemEnabledLanguageCodes={auth.enabled_language_codes ?? systemLanguages?.enabled}
      userEnabledLanguageCodes={auth.user?.enabled_language_codes}
      defaultLanguage={auth.default_language ?? systemLanguages?.defaultLanguage ?? (auth.active_tenant ?? auth.tenant).default_locale}
      preferredLanguageCode={auth.user?.preferred_language ?? auth.default_language}
      onLanguageChange={persistLanguagePreference}
      moduleTranslations={moduleTranslations}>
    <PlatformModulesProvider modules={webModules}>
      <UnsavedChangesProvider>
      <AppShell settings={settings} auth={auth} onSettingsChange={updateSettings} onAuthChange={updateAuth} navItems={navItems} maintenanceMode={maintenanceMode} backendReachable={backendReachable}>
      <Suspense fallback={<div className="content-pad"><p className="muted">i18n:govoplan-core.loading_module.50161f3c</p></div>}>
        <Routes key={(auth.active_tenant ?? auth.tenant).id}>
          <Route path="/" element={<Navigate to={defaultRoute} replace />} />
          {!dashboardModuleInstalled && <Route path="/dashboard" element={<DashboardPage />} />}
          {moduleRoutes.map((route) =>
                <Route
                  key={route.path}
                  path={route.path}
                  element={
                  <PermissionBoundary auth={auth} anyOf={route.anyOf} allOf={route.allOf} fallback={defaultRoute}>
                  {route.render({ settings, auth, onAuthChange: updateAuth })}
                </PermissionBoundary>
                  } />

                )}
          <Route path="/settings" element={<SettingsPage settings={settings} auth={auth} onSettingsChange={updateSettings} onAuthChange={updateAuth} />} />
          <Route path="*" element={<Navigate to={defaultRoute} replace />} />
        </Routes>
      </Suspense>
      {reloginMessage &&
            <LoginModal
              settings={settings}
              title="i18n:govoplan-core.session_expired.b828190e"
              message={reloginMessage}
              onClose={() => setReloginMessage("")}
              onLogin={handleRelogin} />

            }
      </AppShell>
      </UnsavedChangesProvider>
    </PlatformModulesProvider>
    </PlatformLanguageProvider>);

}

type AuthPayload = AuthUpdate;

function mergeAuthPayload(current: AuthInfo | null, next: AuthPayload): AuthPayload {
  if (!current) return next;
  const nextActiveTenant = next.active_tenant ?? next.tenant ?? null;
  const currentActiveTenant = current.active_tenant ?? current.tenant;
  const tenantChanged = Boolean(nextActiveTenant && nextActiveTenant.id !== currentActiveTenant.id);
  return {
    ...current,
    ...next,
    user: next.user ? { ...current.user, ...next.user } : current.user,
    tenant: nextActiveTenant ?? current.tenant,
    active_tenant: nextActiveTenant ?? currentActiveTenant,
    tenants: next.tenants ?? current.tenants,
    scopes: next.scopes ?? current.scopes,
    roles: next.roles ?? (next.roles_loaded === false || tenantChanged ? [] : current.roles),
    groups: next.groups ?? (next.groups_loaded === false || tenantChanged ? [] : current.groups),
    principal: next.principal === undefined ? current.principal : next.principal,
    available_languages: next.available_languages ?? (tenantChanged ? undefined : current.available_languages),
    enabled_language_codes: next.enabled_language_codes ?? (tenantChanged ? undefined : current.enabled_language_codes),
    default_language: next.default_language ?? (tenantChanged ? undefined : current.default_language),
    profile_loaded: next.profile_loaded ?? (tenantChanged ? false : current.profile_loaded),
    roles_loaded: next.roles_loaded ?? (tenantChanged ? false : current.roles_loaded),
    groups_loaded: next.groups_loaded ?? (tenantChanged ? false : current.groups_loaded)
  };
}

function normalizeAuthInfo(response: AuthPayload): AuthInfo {
  const principal = response.principal ?? null;
  const activeTenant = response.active_tenant ?? response.tenant ?? response.tenants?.[0] ?? null;
  const user = normalizeAuthUser(response.user, principal);
  const profileLoaded = response.profile_loaded ?? hasFullProfilePayload(response);
  const rolesLoaded = response.roles_loaded ?? response.roles !== undefined;
  const groupsLoaded = response.groups_loaded ?? response.groups !== undefined;

  if (!activeTenant) {
    throw new Error("Authentication response did not include an active tenant.");
  }
  if (!user) {
    throw new Error("Authentication response did not include a user.");
  }

  return {
    user,
    tenant: activeTenant,
    active_tenant: activeTenant,
    tenants: response.tenants ?? [activeTenant],
    scopes: response.scopes ?? principal?.scopes ?? [],
    roles: response.roles ?? [],
    groups: response.groups ?? [],
    principal,
    available_languages: response.available_languages,
    enabled_language_codes: profileLoaded ? response.enabled_language_codes ?? activeTenant.enabled_language_codes ?? [] : undefined,
    default_language: profileLoaded ? response.default_language ?? user.preferred_language ?? activeTenant.default_locale ?? "en" : undefined,
    profile_loaded: profileLoaded,
    roles_loaded: rolesLoaded,
    groups_loaded: groupsLoaded
  };
}

function hasFullProfilePayload(response: AuthPayload): boolean {
  return Boolean(
    response.default_language ||
    response.available_languages ||
    response.enabled_language_codes ||
    response.roles ||
    response.groups
  );
}

function normalizeAuthUser(user: Partial<AuthUser> | null | undefined, principal: AuthInfo["principal"]): AuthUser | null {
  if (user?.id && user.account_id) {
    return {
      id: user.id,
      account_id: user.account_id,
      email: user.email ?? principal?.email ?? "",
      display_name: user.display_name ?? principal?.display_name ?? principal?.email ?? null,
      tenant_display_name: user.tenant_display_name ?? null,
      is_tenant_admin: user.is_tenant_admin ?? false,
      password_reset_required: user.password_reset_required ?? false,
      preferred_language: user.preferred_language ?? null,
      enabled_language_codes: user.enabled_language_codes ?? [],
      ui_preferences: normalizeUiPreferences(user.ui_preferences)
    };
  }

  if (!principal?.membership_id || !principal.account_id) {
    return null;
  }

  return {
    id: principal.membership_id,
    account_id: principal.account_id,
    email: principal.email ?? "",
    display_name: principal.display_name ?? principal.email ?? null,
    tenant_display_name: principal.display_name ?? null,
    is_tenant_admin: false,
    password_reset_required: false,
    preferred_language: null,
    enabled_language_codes: [],
    ui_preferences: DEFAULT_UI_PREFERENCES
  };
}

function sessionMatchesAuth(sessionInfo: AuthSessionInfo, auth: AuthInfo): boolean {
  const activeTenant = auth.active_tenant ?? auth.tenant;
  if (sessionInfo.user.id !== auth.user.id) return false;
  if (sessionInfo.user.account_id !== auth.user.account_id) return false;
  if ((sessionInfo.active_tenant ?? sessionInfo.tenant).id !== activeTenant.id) return false;
  if (auth.principal?.auth_method && sessionInfo.auth_method !== auth.principal.auth_method) return false;
  if (auth.principal?.session_id && sessionInfo.session_id && auth.principal.session_id !== sessionInfo.session_id) return false;
  if (auth.principal?.api_key_id && sessionInfo.api_key_id && auth.principal.api_key_id !== sessionInfo.api_key_id) return false;
  return true;
}

function normalizeUiPreferences(value: Partial<UserUiPreferences> | null | undefined): UserUiPreferences {
  const theme = value?.theme === "light" || value?.theme === "dark" || value?.theme === "system" ? value.theme : "system";
  return {
    compact_tables: Boolean(value?.compact_tables ?? DEFAULT_UI_PREFERENCES.compact_tables),
    show_inline_help_hints: Boolean(value?.show_inline_help_hints ?? DEFAULT_UI_PREFERENCES.show_inline_help_hints),
    reduce_motion: Boolean(value?.reduce_motion ?? DEFAULT_UI_PREFERENCES.reduce_motion),
    sticky_section_sidebars: Boolean(value?.sticky_section_sidebars ?? DEFAULT_UI_PREFERENCES.sticky_section_sidebars),
    theme
  };
}

function mergeWebModules(localModules: PlatformWebModule[], remoteModules: PlatformWebModule[]): PlatformWebModule[] {
  if (remoteModules.length === 0) return localModules;
  const seen = new Set(localModules.map((module) => module.id));
  return [
  ...localModules,
  ...remoteModules.filter((module) => {
    if (seen.has(module.id)) return false;
    seen.add(module.id);
    return true;
  })];

}
