import { lazy, Suspense, useState, useEffect, useCallback, useMemo, useRef } from 'react';
import type { ExportState } from './components/map/MapPage';
import {
  getSeoContentPage,
  getSeoLandingPage,
  isSeoContentKey,
  isSeoLandingKey,
  SEO_CONTENT_PATHS,
  SEO_LANDING_PATHS,
  type SeoContentKey,
  type SeoLandingKey,
} from './lib/seoRoutes';
import Header, { type Page } from './components/ui/Header';
import type { FeatureMeta, FeatureGroup, POICategoriesResponse, POICategoryGroup } from './types';
import { fetchWithRetry, apiUrl, logNonAbortError, readDemoChoice, setDemoCenter } from './lib/api';
import { trackEvent } from './lib/analytics';
import { parseUrlState } from './lib/url-state';
import pb from './lib/pocketbase';
import { INITIAL_VIEW_STATE } from './lib/consts';
import { useTheme } from './hooks/useTheme';
import { useIsMobile } from './hooks/useIsMobile';
import { useAuth } from './hooks/useAuth';
import { useLicense } from './hooks/useLicense';
import { useTelemetry } from './hooks/useTelemetry';
import { useSavedSearches } from './hooks/useSavedSearches';

declare global {
  interface Window {
    __screenshot_ready?: boolean;
    __map_idle?: boolean;
  }
}

const HomePage = lazy(() => import('./components/home/HomePage'));
const PricingPage = lazy(() => import('./components/pricing/PricingPage'));
const LearnPage = lazy(() => import('./components/learn/LearnPage'));
const SeoLandingPage = lazy(() => import('./components/landing/SeoLandingPage'));
const SeoContentPage = lazy(() => import('./components/landing/SeoContentPage'));
const AccountPage = lazy(() => import('./components/account/AccountPage'));
const SavedPage = lazy(() =>
  import('./components/account/AccountPage').then((module) => ({ default: module.SavedPage }))
);
const InvitePage = lazy(() => import('./components/invite/InvitePage'));
const LegalPage = lazy(() => import('./components/legal/LegalPage'));
const MapPage = lazy(() => import('./components/map/MapPage'));
const AuthModal = lazy(() => import('./components/ui/AuthModal'));
const SaveSearchModal = lazy(() => import('./components/ui/SaveSearchModal'));
const LicenseSuccessModal = lazy(() => import('./components/ui/LicenseSuccessModal'));

function PageFallback() {
  return <div className="flex-1 bg-warm-50 dark:bg-navy-950" />;
}

interface RouteMatch {
  page: Page;
  inviteCode?: string;
  hash?: string;
}

type PostAuthIntent = 'checkout';
type LicenseSuccessStatus = 'hidden' | 'verifying' | 'success' | 'delayed';

const LICENSE_VERIFICATION_ATTEMPTS = 8;
const LICENSE_VERIFICATION_DELAY_MS = 1500;

function hasFullAccess(user?: { subscription?: string; isAdmin?: boolean } | null): boolean {
  return user?.subscription === 'licensed' || user?.isAdmin === true;
}

function delay(ms: number): Promise<void> {
  return new Promise((resolve) => window.setTimeout(resolve, ms));
}

function normalizeHash(hash?: string | null): string {
  return hash?.replace(/^#/, '') ?? '';
}

function currentRelativePath(): string {
  return `${window.location.pathname}${window.location.search}`;
}

const LAST_DASHBOARD_PARAMS_KEY = 'pp_last_dashboard_params';

function persistLastDashboardParams(params: string) {
  try {
    if (params) window.localStorage.setItem(LAST_DASHBOARD_PARAMS_KEY, params);
  } catch {
    // Storage unavailable (private mode/quota) — session restore is best-effort.
  }
}

function readLastDashboardSearch(): string {
  try {
    const saved = window.localStorage.getItem(LAST_DASHBOARD_PARAMS_KEY);
    return saved ? `?${saved.replace(/^\?/, '')}` : '';
  } catch {
    return '';
  }
}

/**
 * Filters and map view live only in the URL. When the dashboard is opened bare
 * (no query), restore the last session's params so users pick up where they
 * left off. Explicit params and shared links always win.
 */
function restoreLastDashboardSession() {
  const pathname = window.location.pathname.replace(/\/+$/, '');
  if (pathname !== '/dashboard' || window.location.search) return;
  const saved = readLastDashboardSearch();
  if (!saved) return;
  window.history.replaceState(window.history.state, '', `/dashboard${saved}`);
}

function isProtectedPage(page: Page): boolean {
  return page === 'account' || page === 'saved';
}

function buildPageUrl(page: Page, inviteCode?: string, search = '', hash = ''): string {
  const normalizedHash = normalizeHash(hash);
  return `${pageToPath(page, inviteCode)}${search}${normalizedHash ? `#${normalizedHash}` : ''}`;
}

function scrollToHash(hash: string) {
  window.requestAnimationFrame(() => {
    document.getElementById(hash)?.scrollIntoView({ block: 'start', behavior: 'smooth' });
  });
}

function unavailableAuthAction(): never {
  throw new Error('Authentication actions are not available in this render mode');
}

function pageToPath(page: Page, inviteCode?: string): string {
  switch (page) {
    case 'dashboard':
      return '/dashboard';
    case 'learn':
      return '/learn';
    case 'pricing':
      return '/pricing';
    case 'property-price-map':
    case 'postcode-property-search':
    case 'commute-property-search':
    case 'school-property-search':
    case 'postcode-checker':
      return SEO_LANDING_PATHS[page];
    case 'birmingham-property-search':
    case 'manchester-property-search':
    case 'bristol-property-search':
    case 'data-sources':
    case 'methodology':
    case 'privacy-security':
      return SEO_CONTENT_PATHS[page];
    case 'terms':
      return '/terms';
    case 'privacy':
      return '/privacy';
    case 'saved':
      return '/saved';
    case 'account':
      return '/account';
    case 'invite':
      if (!inviteCode) {
        throw new Error('Cannot build invite path without an invite code');
      }
      return `/invite/${inviteCode}`;
    default:
      return '/';
  }
}

function pathToPage(rawPathname: string): RouteMatch | null {
  // Proxies 307-redirect /learn -> /learn/; treat trailing slashes as equivalent.
  const pathname = rawPathname.length > 1 ? rawPathname.replace(/\/+$/, '') || '/' : rawPathname;
  if (pathname === '/dashboard') return { page: 'dashboard' };
  if (pathname === '/saved') return { page: 'saved' };
  if (pathname === '/invites') return { page: 'account', hash: 'invites' };
  if (pathname === '/learn') return { page: 'learn' };
  if (pathname === '/pricing') return { page: 'pricing' };
  const seoLandingPage = getSeoLandingPage(pathname);
  if (seoLandingPage) return { page: seoLandingPage };
  const seoContentPage = getSeoContentPage(pathname);
  if (seoContentPage) return { page: seoContentPage };
  if (pathname === '/account') return { page: 'account' };
  if (pathname === '/support') return { page: 'learn' };
  if (pathname === '/terms') return { page: 'terms' };
  if (pathname === '/privacy') return { page: 'privacy' };
  if (pathname.startsWith('/invite/')) {
    const code = pathname.slice('/invite/'.length);
    return { page: 'invite', inviteCode: code };
  }
  if (pathname === '/') return { page: 'home' };
  return null;
}

function isSeoLandingPage(page: Page): page is SeoLandingKey {
  return isSeoLandingKey(page);
}

function isSeoContentPage(page: Page): page is SeoContentKey {
  return isSeoContentKey(page);
}

export default function App() {
  const urlState = useMemo(() => {
    // Must run before any reads of window.location.search below.
    restoreLastDashboardSession();
    return parseUrlState();
  }, []);
  const initialRoute = useMemo(() => pathToPage(window.location.pathname), []);
  const [mapUrlState, setMapUrlState] = useState(urlState);
  // Demo location: a fresh visitor (no explicit URL view) is offered the "check
  // your area" prompt (in MapPage). A choice made earlier this session is reused —
  // GPS coords re-centre the map and are sent to the server (via lib/api), while
  // 'declined' keeps Canary Wharf. Resolve it once, before children fetch, so the
  // very first data request already carries the centre.
  // Read license synchronously from the persisted session so we never apply the
  // demo location for a returning licensed user (they bypass the free zone, and we
  // don't want their location in request URLs).
  const licensedAtMount = useMemo(
    () =>
      pb.authStore.isValid &&
      (pb.authStore.record?.subscription === 'licensed' || pb.authStore.record?.is_admin === true),
    []
  );
  const initialDemoChoice = useMemo(() => {
    const choice = readDemoChoice();
    if (choice && choice !== 'declined' && !licensedAtMount) setDemoCenter(choice);
    return choice;
  }, [licensedAtMount]);
  const [demoView] = useState<typeof INITIAL_VIEW_STATE | null>(() =>
    initialDemoChoice && initialDemoChoice !== 'declined' && !licensedAtMount
      ? {
          ...INITIAL_VIEW_STATE,
          latitude: initialDemoChoice.lat,
          longitude: initialDemoChoice.lon,
        }
      : null
  );
  const [dashboardRouteKey, setDashboardRouteKey] = useState(() =>
    window.location.pathname === '/dashboard' ? window.location.search : ''
  );
  const [dashboardParams, setDashboardParams] = useState(() =>
    window.location.pathname === '/dashboard' ? window.location.search.replace(/^\?/, '') : ''
  );
  const dashboardSearchRef = useRef(
    window.location.pathname === '/dashboard' ? window.location.search : ''
  );
  const activePageRef = useRef<Page>('home');
  const initialViewState = useMemo(
    () => (mapUrlState.hasExplicitView ? mapUrlState.viewState : (demoView ?? INITIAL_VIEW_STATE)),
    [mapUrlState.hasExplicitView, mapUrlState.viewState, demoView]
  );

  const isScreenshotMode = useMemo(() => {
    const params = new URLSearchParams(window.location.search);
    return params.get('screenshot') === '1';
  }, []);
  const isOgMode = useMemo(() => {
    const params = new URLSearchParams(window.location.search);
    return params.get('og') === '1';
  }, []);

  const [features, setFeatures] = useState<FeatureMeta[]>([]);
  const [poiCategoryGroups, setPOICategoryGroups] = useState<POICategoryGroup[]>([]);
  const [initialLoading, setInitialLoading] = useState(true);
  const [pendingInfoFeature, setPendingInfoFeature] = useState<string | null>(null);
  const [inviteCode, setInviteCode] = useState<string | null>(() => {
    return initialRoute?.inviteCode ?? null;
  });
  const [routeHash, setRouteHash] = useState(
    () => initialRoute?.hash ?? normalizeHash(window.location.hash)
  );
  const [activePage, setActivePage] = useState<Page>(() => {
    if (isScreenshotMode) return 'dashboard';

    // Derive page from URL pathname
    if (initialRoute) return initialRoute.page;

    // Restore from history state (e.g. popstate)
    if (window.history.state?.page) return window.history.state.page;

    // Unknown path — track as 404
    if (window.location.pathname !== '/') {
      trackEvent('404', { path: window.location.pathname });
    }
    return 'home';
  });

  const { theme, toggleTheme } = useTheme();
  const isMobile = useIsMobile();
  useTelemetry();
  const {
    user,
    loading: authLoading,
    error: authError,
    login,
    register,
    loginWithOAuth,
    logout,
    requestPasswordReset,
    refreshAuth,
    clearError,
  } = useAuth();
  // Only offer the "check your area" demo prompt to non-licensed visitors on a
  // fresh visit. `user` is hydrated synchronously from the persisted PocketBase
  // session, so a returning premium user is recognised at mount (no flash).
  const offerDemoLocation =
    !urlState.hasExplicitView && initialDemoChoice == null && !hasFullAccess(user);
  // Stop transmitting the demo location once the user is licensed (in-session
  // upgrade, cross-tab sign-in, or a stale session that refreshed to licensed).
  useEffect(() => {
    if (hasFullAccess(user)) setDemoCenter(null);
  }, [user]);
  const { startCheckout: startPostAuthCheckout } = useLicense();
  const [showAuthModal, setShowAuthModal] = useState(false);
  const [authModalTab, setAuthModalTab] = useState<'login' | 'register'>('login');
  const [postAuthIntent, setPostAuthIntent] = useState<PostAuthIntent | null>(null);
  const postAuthCheckoutReturnPathRef = useRef<string | null>(null);
  const authCompletedRef = useRef(false);
  const [licenseSuccessStatus, setLicenseSuccessStatus] = useState<LicenseSuccessStatus>('hidden');
  const [dashboardReady, setDashboardReady] = useState(false);

  // Keep a ref to the latest refreshAuth so the mount-only startup effect always
  // calls the current implementation without re-running when the callback identity changes.
  const refreshAuthRef = useRef(refreshAuth);
  useEffect(() => {
    refreshAuthRef.current = refreshAuth;
  }, [refreshAuth]);

  const openAuthModal = useCallback(
    (
      tab: 'login' | 'register',
      intent: PostAuthIntent | null = null,
      checkoutReturnPath?: string
    ) => {
      authCompletedRef.current = false;
      postAuthCheckoutReturnPathRef.current =
        intent === 'checkout' ? (checkoutReturnPath ?? currentRelativePath()) : null;
      setPostAuthIntent(intent);
      setAuthModalTab(tab);
      setShowAuthModal(true);
      clearError();
    },
    [clearError]
  );

  const closeAuthModal = useCallback(() => {
    setShowAuthModal(false);
    const completed = authCompletedRef.current;
    if (!completed) {
      setPostAuthIntent(null);
      postAuthCheckoutReturnPathRef.current = null;
      // Only protected pages bounce home; the dashboard stays open in demo
      // mode (server-enforced free zone) when the modal is dismissed.
      if (isProtectedPage(activePageRef.current)) {
        window.history.replaceState({ page: 'home', hash: '' }, '', '/');
        setRouteHash('');
        setActivePage('home');
      }
    }
    authCompletedRef.current = false;
  }, []);

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const returnedFromCheckout = params.get('license_success') === '1';
    let cancelled = false;

    if (returnedFromCheckout) {
      params.delete('license_success');
      const newUrl = params.toString()
        ? `${window.location.pathname}?${params.toString()}`
        : window.location.pathname;
      window.history.replaceState({}, '', newUrl);
    }

    async function refreshOnStartup() {
      if (!returnedFromCheckout) {
        // Refresh auth on startup to pick up server-side subscription changes,
        // but only when a token exists — logged-out visitors would just 401.
        if (pb.authStore.token) {
          refreshAuthRef.current().catch(() => {});
        }
        return;
      }

      setLicenseSuccessStatus('verifying');
      for (let attempt = 0; attempt < LICENSE_VERIFICATION_ATTEMPTS; attempt += 1) {
        try {
          const refreshedUser = await refreshAuthRef.current();
          if (cancelled) return;
          if (hasFullAccess(refreshedUser)) {
            trackEvent('Purchase');
            setLicenseSuccessStatus('success');
            return;
          }
        } catch (error) {
          logNonAbortError('Failed to verify license activation', error);
          break;
        }

        await delay(LICENSE_VERIFICATION_DELAY_MS);
        if (cancelled) return;
      }

      if (!cancelled) setLicenseSuccessStatus('delayed');
    }

    refreshOnStartup();
    return () => {
      cancelled = true;
    };
    // Mount-only: this is a startup auth refresh / license verification handshake
    // that must fire exactly once on initial load. refreshAuth is read via ref.
  }, []);

  const savedSearches = useSavedSearches(user?.id ?? null);
  const [showSaveModal, setShowSaveModal] = useState(false);
  const [editingSearch, setEditingSearch] = useState<{ id: string; name: string } | null>(null);

  useEffect(() => {
    const controller = new AbortController();
    let featuresLoaded = false;
    let poisLoaded = false;

    const checkDone = () => {
      if (featuresLoaded && poisLoaded) setInitialLoading(false);
    };

    fetchWithRetry<{ groups: FeatureGroup[] }>(
      apiUrl('features'),
      (json) => {
        const flat: FeatureMeta[] = json.groups.flatMap((g) =>
          g.features.map((f) => ({ ...f, group: g.name }))
        );
        setFeatures(flat);
        featuresLoaded = true;
        checkDone();
      },
      controller.signal
    );

    fetchWithRetry<POICategoriesResponse>(
      apiUrl('poi-categories'),
      (json) => {
        setPOICategoryGroups(json.groups);
        poisLoaded = true;
        checkDone();
      },
      controller.signal
    );

    return () => controller.abort();
  }, []);

  const navigateTo = useCallback(
    (page: Page, hash?: string, infoFeature?: string) => {
      const targetHash = normalizeHash(hash);
      // Save dashboard search params before navigating away
      if (activePageRef.current === 'dashboard') {
        dashboardSearchRef.current = window.location.search;
      }
      if (infoFeature) {
        window.history.replaceState({ ...window.history.state, infoFeature }, '');
      }
      // Restore dashboard search params when navigating back, falling back to
      // the last persisted session for first visits in this tab.
      const search =
        page === 'dashboard' ? dashboardSearchRef.current || readLastDashboardSearch() : '';
      const url = buildPageUrl(page, inviteCode ?? undefined, search, targetHash);
      window.history.pushState({ page, hash: targetHash }, '', url);
      if (page === 'dashboard') {
        setMapUrlState(parseUrlState());
        setDashboardRouteKey(window.location.search);
      }
      setRouteHash(targetHash);
      setActivePage(page);
      setEditingSearch(null);
      if (targetHash) scrollToHash(targetHash);
    },
    [inviteCode]
  );

  const handleEditSearch = useCallback((id: string, name: string, params: string) => {
    const search = params.startsWith('?') ? params : `?${params}`;
    dashboardSearchRef.current = search;
    const url = `/dashboard${search}`;
    window.history.pushState({ page: 'dashboard', hash: '' }, '', url);
    setMapUrlState(parseUrlState());
    setDashboardRouteKey(search);
    setRouteHash('');
    setActivePage('dashboard');
    setEditingSearch({ id, name });
  }, []);

  const handleCancelEdit = useCallback(() => {
    setEditingSearch(null);
  }, []);

  const updateEditingSearch = useCallback(
    async (params: string) => {
      if (!editingSearch) return;
      await savedSearches.updateSearchParams(editingSearch.id, params);
      setEditingSearch(null);
    },
    [editingSearch, savedSearches]
  );

  const handleUpdateEdit = useCallback(
    async (params: string) => {
      try {
        await updateEditingSearch(params);
        navigateTo('saved');
      } catch {
        // Error stored on savedSearches.error
      }
    },
    [updateEditingSearch, navigateTo]
  );

  useEffect(() => {
    if (authLoading || !user || postAuthIntent !== 'checkout') return;

    setPostAuthIntent(null);
    setShowAuthModal(false);
    const checkoutReturnPath = postAuthCheckoutReturnPathRef.current ?? undefined;
    postAuthCheckoutReturnPathRef.current = null;
    if (hasFullAccess(user)) {
      if (checkoutReturnPath?.startsWith('/dashboard')) {
        window.history.pushState({ page: 'dashboard', hash: '' }, '', checkoutReturnPath);
        setMapUrlState(parseUrlState());
        setDashboardRouteKey(window.location.search);
        setRouteHash('');
        setActivePage('dashboard');
      } else {
        navigateTo('dashboard');
      }
      return;
    }

    startPostAuthCheckout(checkoutReturnPath).catch((error) => {
      logNonAbortError('Failed to resume checkout after auth', error);
      navigateTo('pricing');
    });
  }, [authLoading, navigateTo, postAuthIntent, startPostAuthCheckout, user]);

  useEffect(() => {
    activePageRef.current = activePage;
  }, [activePage]);

  // Refs for the initial history.replaceState seed below — the popstate effect runs
  // mount-only, but it needs to read the *initial* page/hash/inviteCode values once.
  const initialPageRef = useRef(activePage);
  const initialRouteHashRef = useRef(routeHash);
  const initialInviteCodeRef = useRef(inviteCode);

  useEffect(() => {
    if (!window.history.state?.page) {
      const initialActivePage = initialPageRef.current;
      const hash = initialRouteHashRef.current || normalizeHash(window.location.hash);
      window.history.replaceState(
        { page: initialActivePage, hash },
        '',
        buildPageUrl(
          initialActivePage,
          initialInviteCodeRef.current ?? undefined,
          window.location.search,
          hash
        )
      );
    }
    const handlePopState = (e: PopStateEvent) => {
      let page: Page;
      const hash = normalizeHash(window.location.hash);
      if (e.state?.page) {
        page = e.state.page;
        setActivePage(page);
        setRouteHash(hash || e.state.hash || '');
        if (e.state.infoFeature) {
          setPendingInfoFeature(e.state.infoFeature);
        }
      } else {
        const parsed = pathToPage(window.location.pathname);
        page = parsed?.page || 'home';
        setActivePage(page);
        setRouteHash(parsed?.hash ?? hash);
        if (parsed?.inviteCode) setInviteCode(parsed.inviteCode);
      }
      // Re-parse URL state when returning to dashboard via back/forward
      if (page === 'dashboard') {
        setMapUrlState(parseUrlState());
        setDashboardRouteKey(window.location.search);
      } else {
        setEditingSearch(null);
      }
    };
    window.addEventListener('popstate', handlePopState);
    return () => window.removeEventListener('popstate', handlePopState);
    // Mount-only: registers a single popstate listener for the app lifetime and
    // seeds initial history state. The handler uses only stable setters and module
    // functions; initial-render values are read via refs above.
  }, []);

  const { fetchSearches } = savedSearches;
  useEffect(() => {
    if (activePage === 'saved') {
      fetchSearches();
    }
  }, [activePage, fetchSearches]);

  const isProtectedPageActive = isProtectedPage(activePage);
  // Only protected pages (account/saved) prompt for login on entry. The
  // dashboard opens straight into demo mode (server-enforced free zone) so
  // visitors can try it without logging in; the upgrade modal still appears
  // when they pan outside the free zone.
  useEffect(() => {
    if (authLoading) return;
    if (isProtectedPageActive && !user) {
      openAuthModal('login');
    }
    if (activePage === 'pricing' && hasFullAccess(user)) {
      navigateTo('dashboard');
    }
  }, [activePage, authLoading, isProtectedPageActive, navigateTo, openAuthModal, user]);

  const [exportState, setExportState] = useState<ExportState | null>(null);

  useEffect(() => {
    if (activePage !== 'dashboard' || !user) {
      setDashboardReady(false);
      setExportState(null);
    }
  }, [activePage, user]);

  if ((isScreenshotMode || isOgMode) && inviteCode) {
    return (
      <Suspense fallback={<PageFallback />}>
        <InvitePage
          code={inviteCode}
          user={null}
          theme={theme}
          screenshotMode
          onLoginClick={unavailableAuthAction}
          onRegisterClick={unavailableAuthAction}
          onLicenseGranted={unavailableAuthAction}
        />
      </Suspense>
    );
  }

  if (isScreenshotMode) {
    return (
      <Suspense fallback={<PageFallback />}>
        <MapPage
          features={features}
          poiCategoryGroups={poiCategoryGroups}
          initialFilters={urlState.filters}
          initialViewState={initialViewState}
          initialPOICategories={urlState.poiCategories}
          initialOverlays={urlState.overlays}
          initialCrimeTypes={urlState.crimeTypes}
          initialBasemap={urlState.basemap}
          initialColorOpacity={urlState.colorOpacity}
          initialTab={urlState.tab}
          initialLoading={initialLoading}
          theme={theme}
          pendingInfoFeature={null}
          onClearPendingInfoFeature={() => {}}
          onNavigateTo={() => {}}
          screenshotMode
          ogMode={isOgMode}
          initialTravelTime={urlState.travelTime}
          shareCode={urlState.share}
          onLoginClick={unavailableAuthAction}
          onRegisterClick={unavailableAuthAction}
        />
      </Suspense>
    );
  }

  return (
    <div className="h-full flex flex-col">
      <Header
        activePage={activePage}
        activeHash={routeHash}
        onPageChange={navigateTo}
        theme={theme}
        onToggleTheme={toggleTheme}
        exportState={activePage === 'dashboard' && user ? exportState : null}
        dashboardParams={activePage === 'dashboard' ? dashboardParams : ''}
        dashboardActionsDisabled={activePage === 'dashboard' && !dashboardReady}
        onSaveSearch={
          activePage === 'dashboard' && user
            ? editingSearch
              ? () => handleUpdateEdit(dashboardParams)
              : () => setShowSaveModal(true)
            : null
        }
        savingSearch={savedSearches.saving}
        editingSearch={activePage === 'dashboard' ? editingSearch : null}
        onCancelEdit={handleCancelEdit}
        onUpdateEdit={() => handleUpdateEdit(dashboardParams)}
        user={user}
        onLoginClick={() => openAuthModal('login')}
        onRegisterClick={() => openAuthModal('register')}
        onLogout={logout}
        isMobile={isMobile}
      />
      <Suspense fallback={<PageFallback />}>
        {activePage === 'home' ? (
          <HomePage
            onOpenDashboard={() => navigateTo('dashboard')}
            onOpenPricing={() => navigateTo('pricing')}
            theme={theme}
            hidePricing={user?.subscription === 'licensed' || user?.isAdmin}
          />
        ) : activePage === 'pricing' && !(user?.subscription === 'licensed' || user?.isAdmin) ? (
          <PricingPage
            onOpenDashboard={() => navigateTo('dashboard')}
            user={user}
            onLoginClick={() => openAuthModal('login', 'checkout', '/pricing')}
            onRegisterClick={() => openAuthModal('register', 'checkout', '/pricing')}
          />
        ) : activePage === 'learn' ? (
          <LearnPage />
        ) : activePage === 'terms' || activePage === 'privacy' ? (
          <LegalPage kind={activePage} />
        ) : isSeoLandingPage(activePage) ? (
          <SeoLandingPage pageKey={activePage} onOpenDashboard={() => navigateTo('dashboard')} />
        ) : isSeoContentPage(activePage) ? (
          <SeoContentPage pageKey={activePage} onOpenDashboard={() => navigateTo('dashboard')} />
        ) : activePage === 'saved' && user ? (
          <SavedPage
            searches={savedSearches.searches}
            searchesLoading={savedSearches.loading}
            onDeleteSearch={savedSearches.deleteSearch}
            onUpdateSearchNotes={savedSearches.updateSearchNotes}
            onUpdateSearchName={savedSearches.updateSearchName}
            onOpenSearch={handleEditSearch}
          />
        ) : activePage === 'account' && user ? (
          <AccountPage
            user={user}
            onRefreshAuth={async () => {
              await refreshAuth();
            }}
            scrollTarget={routeHash}
          />
        ) : isProtectedPageActive && !user ? (
          <PageFallback />
        ) : activePage === 'invite' && inviteCode ? (
          <InvitePage
            code={inviteCode}
            user={user}
            theme={theme}
            onLoginClick={() => openAuthModal('login')}
            onRegisterClick={() => openAuthModal('register')}
            onLicenseGranted={() => {
              setLicenseSuccessStatus('success');
              refreshAuth();
            }}
          />
        ) : (
          <MapPage
            key={dashboardRouteKey}
            features={features}
            poiCategoryGroups={poiCategoryGroups}
            initialFilters={mapUrlState.filters}
            initialViewState={initialViewState}
            offerDemoLocation={offerDemoLocation}
            initialPOICategories={mapUrlState.poiCategories}
            initialOverlays={mapUrlState.overlays}
            initialCrimeTypes={mapUrlState.crimeTypes}
            initialBasemap={mapUrlState.basemap}
            initialColorOpacity={mapUrlState.colorOpacity}
            initialTab={mapUrlState.tab}
            initialLoading={initialLoading}
            theme={theme}
            pendingInfoFeature={pendingInfoFeature}
            onClearPendingInfoFeature={() => setPendingInfoFeature(null)}
            onNavigateTo={navigateTo}
            onExportStateChange={setExportState}
            onDashboardParamsChange={(params) => {
              setDashboardParams(params);
              if (!mapUrlState.share) persistLastDashboardParams(params);
            }}
            onDashboardReadyChange={setDashboardReady}
            isMobile={isMobile}
            initialTravelTime={mapUrlState.travelTime}
            initialPostcode={mapUrlState.postcode}
            shareCode={mapUrlState.share}
            user={user}
            onLoginClick={() => openAuthModal('login')}
            onRegisterClick={() => openAuthModal('register')}
            onCheckoutLoginClick={(returnPath) => openAuthModal('login', 'checkout', returnPath)}
            onCheckoutRegisterClick={(returnPath) =>
              openAuthModal('register', 'checkout', returnPath)
            }
            deferTutorial={licenseSuccessStatus !== 'hidden'}
            onSaveSearch={user ? savedSearches.saveSearch : undefined}
            savingSearch={savedSearches.saving}
            editingSearch={editingSearch}
            onCancelEdit={handleCancelEdit}
            onUpdateEdit={handleUpdateEdit}
            onUpdateEditInPlace={updateEditingSearch}
          />
        )}
      </Suspense>
      <Suspense fallback={null}>
        {showAuthModal && (
          <AuthModal
            onClose={closeAuthModal}
            onAuthenticated={() => {
              authCompletedRef.current = true;
            }}
            onLogin={login}
            onRegister={register}
            onOAuthLogin={loginWithOAuth}
            onForgotPassword={requestPasswordReset}
            loading={authLoading}
            error={authError}
            onClearError={clearError}
            initialTab={authModalTab}
          />
        )}
        {showSaveModal && (
          <SaveSearchModal
            onClose={() => setShowSaveModal(false)}
            onSave={(name) => savedSearches.saveSearch(name, dashboardParams)}
            onViewSearches={() => {
              setShowSaveModal(false);
              navigateTo('saved');
            }}
            saving={savedSearches.saving}
            error={savedSearches.error}
          />
        )}
        {licenseSuccessStatus !== 'hidden' && (
          <LicenseSuccessModal
            status={licenseSuccessStatus}
            onClose={() => {
              const shouldOpenDashboard = licenseSuccessStatus === 'success';
              setLicenseSuccessStatus('hidden');
              if (shouldOpenDashboard) navigateTo('dashboard');
            }}
          />
        )}
      </Suspense>
    </div>
  );
}
