import { Navigate, Route, Routes } from "react-router-dom";
import { lazy, Suspense, useEffect, useMemo, useState } from "react";
import { fetchMe } from "./api/auth";
import { fetchPlatformModules } from "./api/platform";
import { AUTH_REQUIRED_EVENT, loadApiSettings, saveApiSettings, type AuthRequiredEventDetail } from "./api/client";
import type { ApiSettings, AuthInfo, LoginResponse, PlatformModuleInfo } 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 { adminReadScopes } from "./utils/permissions";
import { firstAccessibleRoute, navItemsForModules, resolveInstalledWebModules, routeContributionsForModules } from "./platform/modules";
import { PlatformModulesProvider } from "./platform/ModuleContext";
import { UnsavedChangesProvider } from "./components/UnsavedChangesGuard";

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

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 [reloginMessage, setReloginMessage] = useState("");

  const webModules = useMemo(() => resolveInstalledWebModules(platformModules), [platformModules]);
  const navItems = useMemo(() => navItemsForModules(webModules), [webModules]);
  const moduleRoutes = useMemo(() => routeContributionsForModules(webModules), [webModules]);

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

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

  function authFromLoginResponse(response: LoginResponse): AuthInfo {
    const active = response.active_tenant ?? response.tenant;
    return {
      user: response.user,
      tenant: active,
      active_tenant: active,
      tenants: response.tenants ?? [active],
      scopes: response.scopes,
      roles: response.roles,
      groups: response.groups
    };
  }

  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 || "Your session has expired. Sign in again to continue.");
    }

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

  useEffect(() => {
    let cancelled = false;
    setCheckingSession(true);
    fetchMe(settings)
      .then((me) => { if (!cancelled) setAuth(me); })
      .catch(() => {
        if (!cancelled) {
          const cleared = { ...settings, accessToken: "" };
          setSettings(cleared);
          saveApiSettings(cleared);
          setAuth(null);
          setPlatformModules(null);
        }
      })
      .finally(() => {
        if (!cancelled) setCheckingSession(false);
      });
    return () => { cancelled = true; };
  }, [settings.apiBaseUrl, settings.apiKey]);

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

    let cancelled = false;
    fetchPlatformModules(settings)
      .then((response) => { if (!cancelled) setPlatformModules(response.modules); })
      .catch(() => { if (!cancelled) setPlatformModules(null); });

    return () => { cancelled = true; };
  }, [auth?.user.id, auth?.active_tenant?.id, auth?.tenant.id, settings.apiBaseUrl, settings.apiKey]);

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

    let inFlight = false;
    let lastRefreshAt = 0;

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

      inFlight = true;
      lastRefreshAt = now;
      try {
        setAuth(await fetchMe(settings));
      } catch {
        // 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 () => {
      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 (
      <PlatformModulesProvider modules={webModules}>
        <UnsavedChangesProvider>
        <AppShell settings={settings} auth={null} onSettingsChange={updateSettings} onAuthChange={updateAuth} publicMode navItems={navItems}>
        <div className="public-landing">
          <section className="public-card">
            <div className="public-kicker">GovOPlaN</div>
            <h1>Checking session...</h1>
            <p>Please wait while the local session is verified.</p>
          </section>
        </div>
        </AppShell>
      </UnsavedChangesProvider>
      </PlatformModulesProvider>
    );
  }

  if (!auth) {
    return (
      <PlatformModulesProvider modules={webModules}>
        <UnsavedChangesProvider>
        <AppShell settings={settings} auth={null} onSettingsChange={updateSettings} onAuthChange={updateAuth} publicMode navItems={navItems}>
          <PublicLandingPage settings={settings} onLogin={handlePublicLogin} />
        </AppShell>
      </UnsavedChangesProvider>
      </PlatformModulesProvider>
    );
  }

  const defaultRoute = firstAccessibleRoute(auth, webModules);

  return (
    <PlatformModulesProvider modules={webModules}>
      <UnsavedChangesProvider>
      <AppShell settings={settings} auth={auth} onSettingsChange={updateSettings} onAuthChange={updateAuth} navItems={navItems}>
      <Suspense fallback={<div className="content-pad"><p className="muted">Loading module...</p></div>}>
        <Routes key={(auth.active_tenant ?? auth.tenant).id}>
          <Route path="/" element={<Navigate to={defaultRoute} replace />} />
          <Route path="/dashboard" element={<DashboardPage settings={settings} />} />
          {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 })}
                </PermissionBoundary>
              }
            />
          ))}
          <Route path="/settings" element={<SettingsPage settings={settings} auth={auth} onSettingsChange={updateSettings} onAuthChange={updateAuth} />} />
          <Route path="/admin" element={
            <PermissionBoundary auth={auth} anyOf={adminReadScopes} fallback={defaultRoute}>
              <AdminPage settings={settings} auth={auth} onAuthChange={updateAuth} />
            </PermissionBoundary>
          } />
          <Route path="*" element={<Navigate to={defaultRoute} replace />} />
        </Routes>
      </Suspense>
      {reloginMessage && (
        <LoginModal
          settings={settings}
          title="Session expired"
          message={reloginMessage}
          onClose={() => setReloginMessage("")}
          onLogin={handleRelogin}
        />
      )}
      </AppShell>
      </UnsavedChangesProvider>
    </PlatformModulesProvider>
  );
}
