import { Switch, Route, useLocation, Router as WouterRouter } from "wouter";
import { Suspense, lazy, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import { queryClient } from "./lib/queryClient";
import { QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
import { AppUpdateToast } from "@/components/AppUpdateToast";
import { TooltipProvider } from "@/components/ui/tooltip";
import { HelmetProvider, Helmet } from "react-helmet-async";
import { AuthProvider, useAuth } from "@/contexts/AuthContext";
import { GuidanceProvider } from "@/contexts/GuidanceContext";
import { BottomOverlayProvider } from "@/contexts/BottomOverlayContext";
import { initReferralTracking } from "@/lib/referralTracking";
import { useAccessibility } from "@/hooks/useAccessibility";
// initGA + initSpotifyPixel are dynamic-imported inside their useEffects
// (see below). They run post-mount so async loading is invisible to the
// user. initReferralTracking stays eager — it's tiny and attribution-
// critical (very-fast conversions can race a dynamic import).
import { useAnalytics } from "@/hooks/use-analytics";
import { GateLogin } from "@/components/GateLogin";
import { NpsSurveyProvider } from "@/components/NpsSurveyProvider";
import { I18nProvider } from "@/lib/i18n/I18nProvider";
import { getLocaleFromPath, stripLocalePrefix, LOCALE_PREFIX } from "@/lib/i18n/localeRouting";
import { subscribeToLocationChanges } from "@/lib/i18n/locationListener";
import DirectoryNotFound from "@/components/DirectoryNotFound";
import { CHOICY_LOCAL_ORG } from "@shared/orgSchema";

import { isChunkError } from "@/lib/isChunkError";
import { clearCachesWithTimeout } from "@/lib/chunkRecovery";

// In-page lazy retry path. Reuses the shared `clearCachesWithTimeout` helper
// so the cache-clear here gets the same 1s hang-safety the boundary's
// auto-reload path uses — without it, a stuck `caches.keys()` on iOS would
// freeze the retry indefinitely, defeating recovery before the error ever
// escapes to ErrorBoundary. We don't call `tryRecoverFromChunkError()` here
// because that one *reloads*; in retryImport we want to retry inside the
// current page first, and only let the error escape to ErrorBoundary (which
// then triggers reload-with-loop-breaker) once retries are exhausted.
function retryImport(importFn: () => Promise<any>, retries = 2, delay = 500): Promise<any> {
  return importFn().catch(async (err: Error) => {
    if (retries > 0 && isChunkError(err.message)) {
      await clearCachesWithTimeout();
      await new Promise(resolve => setTimeout(resolve, delay));
      return retryImport(importFn, retries - 1, delay);
    }
    throw err;
  });
}

function lazyWithRetry(importFn: () => Promise<any>): ReturnType<typeof lazy> {
  return lazy(() => retryImport(importFn));
}

const FeedbackButton = lazyWithRetry(() => import("@/components/FeedbackButton").then(m => ({ default: m.FeedbackButton })));
const PWAInstallPrompt = lazyWithRetry(() => import("@/components/PWAInstallPrompt").then(m => ({ default: m.PWAInstallPrompt })));
const CookieConsent = lazyWithRetry(() => import("@/components/CookieConsent").then(m => ({ default: m.CookieConsent })));
const WhatsNewDialog = lazyWithRetry(() => import("@/components/WhatsNewDialog").then(m => ({ default: m.WhatsNewDialog })));

const BusinessProfile = lazyWithRetry(() => import("@/pages/BusinessProfile"));
// HomePage imported EAGERLY (Task #545): it's the landing route hit on
// every cold visit, so lazy-loading it adds a serial network round-trip
// (shell chunk → HomePage chunk) after parse before the first paint of
// real content. Eager import pulls its module graph into the main bundle
// so first paint waits only for the main bundle, not a follow-up chunk.
// Browser FCP dropped from ~5.5s (POOR) to <2s on warm dev / <1.5s on prod
// builds. All other ~19 routes stay lazy.
import HomePage from "@/pages/HomePage";
import { BootSkeleton, ContentSkeleton } from "@/components/BootSkeleton";

// Conditionally show FeedbackButton - hide on admin, claim, and settings pages
function ConditionalFeedbackButton() {
  const [location] = useLocation();
  if (location.startsWith("/claim") || location.startsWith("/admin") || location.startsWith("/settings")) {
    return null;
  }
  return <FeedbackButton />;
}

// Lazy-load consumer pages
const Deals = lazyWithRetry(() => import("@/pages/Deals"));
const MapView = lazyWithRetry(() => import("@/pages/MapView"));
const SearchPage = lazyWithRetry(() => import("@/pages/Search"));

// Lazy-load business dashboard pages for better initial load time
const BusinessDashboard = lazyWithRetry(() => import("@/pages/BusinessDashboard"));
const BusinessResources = lazyWithRetry(() => import("@/pages/BusinessResources"));
const NewsletterDetail = lazyWithRetry(() => import("@/pages/NewsletterDetail"));
const BusinessAnalyticsDashboard = lazyWithRetry(() => import("@/pages/BusinessAnalyticsDashboard"));
const BusinessTerritoryDashboard = lazyWithRetry(() => import("@/pages/BusinessTerritoryDashboard"));
const QuickClaim = lazyWithRetry(() => import("@/pages/QuickClaim"));
const Register = lazyWithRetry(() => import("@/pages/Register"));
const Login = lazyWithRetry(() => import("@/pages/Login"));

// Route-based code splitting: Admin and Franchisee routes loaded as chunks
const AdminRoutes = lazyWithRetry(() => import("@/routes/AdminRoutes"));
const FranchiseeRoutes = lazyWithRetry(() => import("@/routes/FranchiseeRoutes"));
// Lazy-load directory and content pages
const CityPage = lazyWithRetry(() => import("@/pages/CityPage"));
const StatePage = lazyWithRetry(() => import("@/pages/StatePage"));
const LocalGuidePage = lazyWithRetry(() => import("@/pages/LocalGuidePage"));
const ServiceCategoryPage = lazyWithRetry(() => import("@/pages/ServiceCategoryPage"));
const CategoryPage = lazyWithRetry(() => import("@/pages/CategoryPage"));
const EmergencyServicePage = lazyWithRetry(() => import("@/pages/EmergencyServicePage"));
const DirectoryPage = lazyWithRetry(() => import("@/pages/DirectoryPage"));
const BlogPage = lazyWithRetry(() => import("@/pages/BlogPage"));
const BlogArticlePage = lazyWithRetry(() => import("@/pages/BlogArticlePage"));
const BlogCategoryPage = lazyWithRetry(() => import("@/pages/BlogCategoryPage"));

// Lazy-load business management pages
const BusinessLeads = lazyWithRetry(() => import("@/pages/BusinessLeads"));
const BusinessLeadAutomation = lazyWithRetry(() => import("@/pages/BusinessLeadAutomation"));
const BusinessManagement = lazyWithRetry(() => import("@/pages/BusinessManagement"));

// Lazy-load opt-out pages
const OptOutRequest = lazyWithRetry(() => import("@/pages/OptOutRequest"));
const OptOutConfirm = lazyWithRetry(() => import("@/pages/OptOutConfirm"));
const NurtureOptOut = lazyWithRetry(() => import("@/pages/NurtureOptOut"));
const DataDeletionRequest = lazyWithRetry(() => import("@/pages/DataDeletionRequest"));
const DataDeletionConfirm = lazyWithRetry(() => import("@/pages/DataDeletionConfirm"));
const DataDeletionHistory = lazyWithRetry(() => import("@/pages/DataDeletionHistory"));
const SecuritySettings = lazyWithRetry(() => import("@/pages/SecuritySettings"));
const EditBusiness = lazyWithRetry(() => import("@/pages/EditBusiness"));
const Profile = lazyWithRetry(() => import("@/pages/Profile"));
// Lazy-load marketing and feature pages
const ReferralProgram = lazyWithRetry(() => import("@/pages/ReferralProgram"));
const FranchiseOpportunities = lazyWithRetry(() => import("@/pages/FranchiseOpportunities"));
const MyFavorites = lazyWithRetry(() => import("@/pages/MyFavorites"));
const EmailFavorites = lazyWithRetry(() => import("@/pages/EmailFavorites"));
const EmailVerification = lazyWithRetry(() => import("@/pages/EmailVerification"));
const ClaimWelcome = lazyWithRetry(() => import("@/pages/ClaimWelcome"));
const ListYourBusiness = lazyWithRetry(() => import("@/pages/ListYourBusiness"));
const ClaimConfirmed = lazyWithRetry(() => import("@/pages/ClaimConfirmed"));
const AcceptTeamInvite = lazyWithRetry(() => import("@/pages/AcceptTeamInvite"));
const Advertise = lazyWithRetry(() => import("@/pages/Advertise"));
const ReferralLanding = lazyWithRetry(() => import("@/pages/ReferralLanding"));
const TrustDemo = lazyWithRetry(() => import("@/pages/TrustDemo"));
const CoverageUnitsDemo = lazyWithRetry(() => import("@/pages/CoverageUnitsDemo"));
const SocialMediaDashboard = lazyWithRetry(() => import("@/pages/SocialMediaDashboard"));
const CheckoutSuccessPage = lazyWithRetry(() => import("@/pages/CheckoutSuccessPage"));
const LocationDetails = lazyWithRetry(() => import("@/pages/LocationDetails"));
const BusinessLocationsDashboard = lazyWithRetry(() => import("@/pages/BusinessLocationsDashboard"));

const PublicStatsPage = lazyWithRetry(() => import("@/pages/PublicStatsPage"));
const NotFound = lazyWithRetry(() => import("@/pages/not-found"));
const PricingPage = lazyWithRetry(() => import("@/pages/PricingPage"));
const DirectoryComparison = lazyWithRetry(() => import("@/pages/DirectoryComparison"));
const RecentlyJoined = lazyWithRetry(() => import("@/pages/RecentlyJoined"));
const CostComparison = lazyWithRetry(() => import("@/pages/CostComparison"));
const ValueComparison = lazyWithRetry(() => import("@/pages/ValueComparison"));
const DirectoryAlternative = lazyWithRetry(() => import("@/pages/DirectoryAlternative"));
const ForBusinessPage = lazyWithRetry(() => import("@/pages/ForBusinessPage"));
const FranchisePage = lazyWithRetry(() => import("@/pages/FranchisePage"));
const Browse = lazyWithRetry(() => import("@/pages/Browse"));
const AboutPage = lazyWithRetry(() => import("@/pages/AboutPage"));
const FounderLetter = lazyWithRetry(() => import("@/pages/FounderLetter"));
const WhatWeBelieve = lazyWithRetry(() => import("@/pages/WhatWeBelieve"));
const TriedAndTrue = lazyWithRetry(() => import("@/pages/TriedAndTrue"));
const TriedAndTrueClaim = lazyWithRetry(() => import("@/pages/TriedAndTrueClaim"));
import SkipLinks from "@/components/SkipLinks";
import { useKeyboardNavigation } from "@/hooks/useKeyboardNavigation";
import { useAccessibilityAnnouncements } from "@/components/AccessibilityNotifications";
import { useConsumerShortcuts } from "@/hooks/useConsumerShortcuts";
import PublicLayout from "@/components/PublicLayout";
import { ErrorBoundary } from "@/components/ErrorBoundary";
import { DelayedLoader } from "@/components/ui/branded-loader";
import { ImpersonationGate } from "@/components/ImpersonationGate";
import { useSwipeBack } from "@/hooks/useSwipeBack";
import { useWebVitals } from "@/hooks/useWebVitals";
import { useScrollRestoration } from "@/hooks/useScrollRestoration";
import { devWarn } from "@/lib/devLogger";

// Post-first-paint utilities — KeyboardShortcutHelp only mounts when '?' is
// pressed, OfflineIndicator only when navigator.onLine flips. Lazy keeps
// both out of the eager bundle.
const KeyboardShortcutHelp = lazyWithRetry(() => import("@/components/KeyboardShortcutHelp").then(m => ({ default: m.KeyboardShortcutHelp })));
const OfflineIndicator = lazyWithRetry(() => import("@/components/OfflineIndicator").then(m => ({ default: m.OfflineIndicator })));

// Lazy-load charity and community pages
const CharityRegistration = lazyWithRetry(() => import("@/pages/CharityRegistration"));
const CharityVoting = lazyWithRetry(() => import("@/pages/CharityVoting"));
const CharityResults = lazyWithRetry(() => import("@/pages/CharityResults"));
const NominateCharity = lazyWithRetry(() => import("@/pages/NominateCharity"));
const CharityImpact = lazyWithRetry(() => import("@/pages/CharityImpact"));
const CharityHowItWorks = lazyWithRetry(() => import("@/pages/CharityHowItWorks"));
const CharityQuestionnaire = lazyWithRetry(() => import("@/pages/CharityQuestionnaire"));
const GrandOpeningStoryQuestionnaire = lazyWithRetry(() => import("@/pages/GrandOpeningStoryQuestionnaire"));
const FoundingMemberReferralLanding = lazyWithRetry(() => import("@/pages/FoundingMemberReferralLanding"));
const CharityProfiles = lazyWithRetry(() => import("@/pages/CharityProfiles"));
const CorporateSponsors = lazyWithRetry(() => import("@/pages/CorporateSponsors"));
const HelpCenter = lazyWithRetry(() => import("@/pages/HelpCenter"));
const CommunityResources = lazyWithRetry(() => import("@/pages/CommunityResources"));
const HelpResources = lazyWithRetry(() => import("@/pages/HelpResources"));

// Lazy-load ad builder page
const AdBuilderPage = lazyWithRetry(() => import("@/pages/AdBuilderPage"));

// Lazy-load error pages
const QrError = lazyWithRetry(() => import("@/pages/QrError"));

// Lazy-load self-service ad pages
const AdPurchase = lazyWithRetry(() => import("@/pages/AdPurchase"));
const AdSuccess = lazyWithRetry(() => import("@/pages/AdSuccess"));
const AdPayPalCheckout = lazyWithRetry(() => import("@/pages/AdPayPalCheckout"));

// Lazy-load affiliate and franchise pages
const AffiliateProgram = lazyWithRetry(() => import("@/pages/AffiliateProgram"));
const AffiliateTerms = lazyWithRetry(() => import("@/pages/AffiliateTerms"));
const SalesRepTerms = lazyWithRetry(() => import("@/pages/SalesRepTerms"));
const AffiliateDashboard = lazyWithRetry(() => import("@/pages/AffiliateDashboard"));
const SalesRepDashboard = lazyWithRetry(() => import("@/pages/SalesRepDashboard"));
const SalesRepTools = lazyWithRetry(() => import("@/pages/SalesRepTools"));
const SalesRepCommissions = lazyWithRetry(() => import("@/pages/SalesRepCommissions"));
const SalesRepLeadQueue = lazyWithRetry(() => import("@/pages/SalesRepLeadQueue"));
const JoinSalesTeam = lazyWithRetry(() => import("@/pages/JoinSalesTeam"));
const CareersJoinSalesTeam = lazyWithRetry(() => import("@/pages/CareersJoinSalesTeam"));
const FranchiseReadinessQuiz = lazyWithRetry(() => import("@/pages/FranchiseReadinessQuiz"));

const ChangesConfirm = lazyWithRetry(() => import("@/pages/ChangesConfirm"));
const AssistedChangesReview = lazyWithRetry(() => import("@/pages/AssistedChangesReview"));

// Lazy-load support and legal pages
const ContactSupport = lazyWithRetry(() => import("@/pages/ContactSupport"));
const CorporateSponsorship = lazyWithRetry(() => import("@/pages/CorporateSponsorship"));
const SignAgreements = lazyWithRetry(() => import("@/pages/SignAgreements"));
const PrivacyPolicy = lazyWithRetry(() => import("@/pages/PrivacyPolicy"));
const TermsOfService = lazyWithRetry(() => import("@/pages/TermsOfService"));
const ListingGuidelines = lazyWithRetry(() => import("@/pages/ListingGuidelines"));
const NotificationPreferences = lazyWithRetry(() => import("@/pages/NotificationPreferences"));
const FAQ = lazyWithRetry(() => import("@/pages/FAQ"));


function LocaleAwareRouter() {
  const [location] = useLocation();
  const pathname = location || window.location.pathname;
  
  const locale = useMemo(() => getLocaleFromPath(pathname), [pathname]);
  const strippedPath = useMemo(() => stripLocalePrefix(pathname), [pathname]);
  
  const isAdminRoute = strippedPath.startsWith('/admin');
  const isFranchiseeRoute = strippedPath.startsWith('/franchisee');
  
  useAnalytics();
  
  if (isAdminRoute) {
    return (
      <ScrollRestoration>
        <ErrorBoundary>
          <Suspense fallback={<DelayedLoader />}>
            <AdminRoutes />
          </Suspense>
        </ErrorBoundary>
      </ScrollRestoration>
    );
  }
  
  if (isFranchiseeRoute) {
    return (
      <ScrollRestoration>
        <ErrorBoundary>
          <Suspense fallback={<DelayedLoader />}>
            <FranchiseeRoutes />
          </Suspense>
        </ErrorBoundary>
      </ScrollRestoration>
    );
  }
  
  return <PublicRoutes />;
}

function DirectoryStateRedirect() {
  return <DirectoryNotFound type="directory" />;
}

function AdminRedirectHome() {
  const { user, isLoading } = useAuth();
  const [, setLocation] = useLocation();
  const isAdmin = user?.role === 'admin' || user?.isSuperAdmin;

  useEffect(() => {
    if (!isLoading && isAdmin) {
      setLocation('/admin');
    }
  }, [isAdmin, isLoading, setLocation]);

  // While auth resolves, render BootSkeleton (no PublicLayout / HomePage chunk
  // yet) so admins never see a flash of the consumer homepage before the
  // /admin redirect fires.
  if (isLoading) return <BootSkeleton />;
  if (isAdmin) return <BootSkeleton />;

  return (
    <PublicLayout homeMode>
      <Suspense fallback={<ContentSkeleton />}>
        <HomePage />
      </Suspense>
    </PublicLayout>
  );
}

const historyStackRef = { stack: [window.location.pathname], index: 0 };

function ScrollRestoration({ children }: { children: ReactNode }) {
  const [location] = useLocation();
  const dirRef = useRef<"forward" | "back">("forward");

  useEffect(() => {
    const onPopState = () => {
      const current = window.location.pathname;
      const prevIndex = historyStackRef.index;
      const stackIndex = historyStackRef.stack.lastIndexOf(current, prevIndex - 1);
      if (stackIndex >= 0) {
        historyStackRef.index = stackIndex;
        dirRef.current = "back";
      } else {
        const fwdIndex = historyStackRef.stack.indexOf(current, prevIndex + 1);
        if (fwdIndex >= 0) {
          historyStackRef.index = fwdIndex;
        }
        dirRef.current = "forward";
      }
    };
    window.addEventListener("popstate", onPopState);
    return () => window.removeEventListener("popstate", onPopState);
  }, []);

  useEffect(() => {
    if (location !== historyStackRef.stack[historyStackRef.index]) {
      historyStackRef.stack = historyStackRef.stack.slice(0, historyStackRef.index + 1);
      historyStackRef.stack.push(location);
      historyStackRef.index = historyStackRef.stack.length - 1;
      dirRef.current = "forward";
    }
  }, [location]);

  useScrollRestoration(dirRef.current);

  return <>{children}</>;
}

function PublicRoutes() {
  return (
    <ScrollRestoration><Switch>
        <Route path="/" component={AdminRedirectHome} />
        <Route path="/index.html" component={AdminRedirectHome} />
        <Route path="/home" component={() => <PublicLayout homeMode><Suspense fallback={<ContentSkeleton />}><HomePage /></Suspense></PublicLayout>} />
        <Route path="/search" component={SearchPage} />
        <Route path="/favorites" component={() => { window.location.replace('/my-favorites'); return null; }} />
        <Route path="/deals" component={() => <PublicLayout><Deals /></PublicLayout>} />
        <Route path="/map" component={() => <MapView />} />
        <Route path="/dashboard" component={BusinessDashboard} />
        <Route path="/business-dashboard" component={BusinessDashboard} />
        <Route path="/business-dashboard/manage/:businessId" component={BusinessManagement} />
        <Route path="/business-dashboard/territory" component={BusinessTerritoryDashboard} />
        <Route path="/business-dashboard/resources" component={BusinessResources} />
        <Route path="/business/:businessId/analytics" component={BusinessAnalyticsDashboard} />
        <Route path="/business-analytics" component={() => <BusinessAnalyticsDashboard />} />
        <Route path="/business-dashboard/leads" component={BusinessLeads} />
        <Route path="/business-dashboard/leads/:businessId" component={BusinessLeads} />
        <Route path="/business-dashboard/lead-automation" component={BusinessLeadAutomation} />
        <Route path="/business-dashboard/referral-program" component={ReferralProgram} />
        <Route path="/business/:id" component={() => <PublicLayout hideSearch={true}><BusinessProfile /></PublicLayout>} />
        <Route path="/business/:businessId/review-changes" component={() => <AssistedChangesReview />} />
        <Route path="/business/:id/edit" component={EditBusiness} />
        <Route path="/businesses/:id/locations" component={BusinessLocationsDashboard} />
        <Route path="/dashboard/businesses/:businessId/locations/:locationId" component={LocationDetails} />
        <Route path="/ad-builder/:templateId" component={AdBuilderPage} />
        <Route path="/social-media" component={SocialMediaDashboard} />
        <Route path="/checkout/success" component={() => <PublicLayout><CheckoutSuccessPage /></PublicLayout>} />
        <Route path="/register" component={() => <PublicLayout hideSearch hideHeader><Register /></PublicLayout>} />
        <Route path="/login" component={() => <PublicLayout hideSearch><Login /></PublicLayout>} />
        <Route path="/changes/confirm/:token" component={() => <ChangesConfirm />} />
        <Route path="/verify-email" component={() => <PublicLayout><EmailVerification /></PublicLayout>} />
        <Route path="/accept-team-invite/:token" component={() => <PublicLayout><AcceptTeamInvite /></PublicLayout>} />
        <Route path="/r/:code" component={() => <ReferralLanding />} />
        <Route path="/list-your-business" component={() => <PublicLayout hideSearch><ListYourBusiness /></PublicLayout>} />
        <Route path="/quick-claim" component={() => <PublicLayout hideSearch><QuickClaim /></PublicLayout>} />
        <Route path="/claim/welcome/:id" component={() => <PublicLayout hideSearch><ClaimWelcome /></PublicLayout>} />
        <Route path="/claim/confirmed/:slug" component={() => <PublicLayout hideSearch><ClaimConfirmed /></PublicLayout>} />
        <Route path="/claim/:id" component={() => { const params = new URLSearchParams(window.location.search); const id = window.location.pathname.split('/claim/')[1]; window.location.replace(`/claim/welcome/${id}${params.toString() ? '?' + params.toString() : ''}`); return null; }} />
        <Route path="/claim-business" component={() => { window.location.replace('/quick-claim' + window.location.search); return null; }} />
        <Route path="/claim-invite/:token" component={() => { const token = window.location.pathname.split('/claim-invite/')[1]; fetch(`/api/claim-invites/${token}`).then(r => r.ok ? r.json() : Promise.reject()).then(d => { window.location.replace(`/claim/welcome/${d.business.id}?inviteToken=${token}`); }).catch((err: unknown) => { devWarn('[ClaimInvite] redirect fallback:', err instanceof Error ? err.message : 'unknown'); window.location.replace('/quick-claim'); }); return null; }} />
        <Route path="/claim-onboarding" component={() => { window.location.replace('/quick-claim' + window.location.search); return null; }} />
        <Route path="/claim-onboarding/:businessId" component={() => { window.location.replace('/quick-claim' + window.location.search); return null; }} />
        <Route path="/opt-out" component={() => <PublicLayout hideSearch logoHref="/business-dashboard"><NurtureOptOut /></PublicLayout>} />
        <Route path="/opt-out/:slug" component={() => <PublicLayout hideSearch logoHref="/business-dashboard"><OptOutRequest /></PublicLayout>} />
        <Route path="/opt-out/confirm/:slug" component={() => <PublicLayout hideSearch logoHref="/business-dashboard"><OptOutConfirm /></PublicLayout>} />
        <Route path="/data-deletion" component={() => <PublicLayout hideSearch><DataDeletionRequest /></PublicLayout>} />
        <Route path="/data-deletion/confirm" component={() => <PublicLayout hideSearch><DataDeletionConfirm /></PublicLayout>} />
        <Route path="/data-deletion/history" component={() => <PublicLayout hideSearch><DataDeletionHistory /></PublicLayout>} />
        <Route path="/my-favorites" component={() => <PublicLayout><MyFavorites /></PublicLayout>} />
        <Route path="/email-favorites" component={() => <PublicLayout><EmailFavorites /></PublicLayout>} />
        <Route path="/security-settings" component={() => <PublicLayout hideSearch><SecuritySettings /></PublicLayout>} />
        <Route path="/profile" component={() => <PublicLayout hideSearch><Profile /></PublicLayout>} />
        <Route path="/pricing" component={() => <PublicLayout hideSearch><PricingPage /></PublicLayout>} />
        <Route path="/compare-directories" component={() => <PublicLayout hideSearch><DirectoryComparison /></PublicLayout>} />
        <Route path="/recently-joined" component={() => <PublicLayout><RecentlyJoined /></PublicLayout>} />
        {/* Task R-T626: client-side redirect for legacy /new-businesses links (internal nav). */}
        <Route path="/new-businesses">{(_params) => { window.location.replace('/recently-joined' + window.location.search); return null; }}</Route>
        <Route path="/cost-comparison" component={() => <PublicLayout><CostComparison /></PublicLayout>} />
        <Route path="/compare/value" component={() => <PublicLayout hideSearch><ValueComparison /></PublicLayout>} />
        <Route path="/compare/showmelocal" component={() => <PublicLayout hideSearch><DirectoryAlternative /></PublicLayout>} />
        <Route path="/for-business" component={() => <PublicLayout hideSearch><ForBusinessPage /></PublicLayout>} />
        <Route path="/franchise" component={() => <PublicLayout><FranchisePage /></PublicLayout>} />
        <Route path="/about" component={() => <PublicLayout hideSearch><AboutPage /></PublicLayout>} />
        <Route path="/browse" component={() => <PublicLayout><Suspense fallback={<ContentSkeleton />}><Browse /></Suspense></PublicLayout>} />
        <Route path="/our-story" component={() => <PublicLayout hideSearch><FounderLetter /></PublicLayout>} />
        <Route path="/what-we-believe" component={() => <PublicLayout hideSearch><WhatWeBelieve /></PublicLayout>} />
        <Route path="/tried-and-true" component={() => <PublicLayout hideSearch><TriedAndTrue /></PublicLayout>} />
        <Route path="/tried-and-true/claim/:token" component={() => <PublicLayout hideSearch><TriedAndTrueClaim /></PublicLayout>} />
        <Route path="/tried-and-true/:year" component={() => <PublicLayout hideSearch><TriedAndTrue /></PublicLayout>} />
        <Route path="/franchise-opportunities" component={() => <PublicLayout hideSearch><FranchiseOpportunities /></PublicLayout>} />
        <Route path="/advertise" component={() => <PublicLayout hideSearch><Advertise /></PublicLayout>} />
        <Route path="/ads/purchase" component={() => <PublicLayout hideSearch><AdPurchase /></PublicLayout>} />
        <Route path="/ads/success" component={() => <PublicLayout hideSearch><AdSuccess /></PublicLayout>} />
        <Route path="/ads/paypal-checkout" component={() => <PublicLayout hideSearch><AdPayPalCheckout /></PublicLayout>} />
        <Route path="/stats/:businessSlug" component={() => <PublicLayout hideSearch><PublicStatsPage /></PublicLayout>} />
        <Route path="/trust-demo" component={() => <PublicLayout><TrustDemo /></PublicLayout>} />
        <Route path="/coverage-units" component={() => <PublicLayout><CoverageUnitsDemo /></PublicLayout>} />
        <Route path="/help-center" component={() => <PublicLayout hideSearch><HelpCenter /></PublicLayout>} />
        <Route path="/community-resources" component={() => <CommunityResources />} />
        <Route path="/affiliate-program" component={() => <PublicLayout hideSearch><AffiliateProgram /></PublicLayout>} />
        <Route path="/affiliate-terms" component={() => <PublicLayout><AffiliateTerms /></PublicLayout>} />
        <Route path="/sales-rep-terms" component={() => <PublicLayout><SalesRepTerms /></PublicLayout>} />
        <Route path="/affiliate-dashboard" component={AffiliateDashboard} />
        <Route path="/sales-rep/dashboard" component={SalesRepDashboard} />
        <Route path="/sales-rep/tools" component={() => <SalesRepTools />} />
        <Route path="/sales-rep/commissions" component={() => <SalesRepCommissions />} />
        <Route path="/sales-rep/leads" component={() => <SalesRepLeadQueue />} />
        <Route path="/join-sales-team" component={() => <JoinSalesTeam />} />
        <Route path="/careers/sales-rep" component={() => <CareersJoinSalesTeam />} />
        <Route path="/franchise-readiness-quiz" component={() => <PublicLayout><FranchiseReadinessQuiz /></PublicLayout>} />
        <Route path="/contact-support" component={() => <PublicLayout hideSearch><ContactSupport /></PublicLayout>} />
        <Route path="/corporate-sponsorship" component={() => <PublicLayout hideSearch><CorporateSponsorship /></PublicLayout>} />
        <Route path="/sign-agreements" component={SignAgreements} />
        <Route path="/privacy-policy" component={() => <PublicLayout hideSearch><PrivacyPolicy /></PublicLayout>} />
        <Route path="/terms-of-service" component={() => <PublicLayout hideSearch><TermsOfService /></PublicLayout>} />
        <Route path="/listing-guidelines" component={() => <PublicLayout hideSearch><ListingGuidelines /></PublicLayout>} />
        <Route path="/faq" component={() => <PublicLayout><FAQ /></PublicLayout>} />
        <Route path="/notification-preferences" component={() => <PublicLayout><NotificationPreferences /></PublicLayout>} />
        <Route path="/qr-error" component={QrError} />
        
        {/* Public Charity Routes */}
        <Route path="/charity/register" component={() => <PublicLayout><CharityRegistration /></PublicLayout>} />
        <Route path="/charity/vote" component={() => <PublicLayout hideSearch><CharityVoting /></PublicLayout>} />
        <Route path="/charity/results" component={() => <PublicLayout><CharityResults /></PublicLayout>} />
        <Route path="/charity/nominate" component={() => <PublicLayout hideSearch><NominateCharity /></PublicLayout>} />
        <Route path="/charity/impact" component={() => <PublicLayout hideSearch><CharityImpact /></PublicLayout>} />
        <Route path="/charity/how-it-works" component={() => <PublicLayout><CharityHowItWorks /></PublicLayout>} />
        <Route path="/charity-questionnaire/:token" component={CharityQuestionnaire} />
        <Route path="/grand-opening/:token" component={GrandOpeningStoryQuestionnaire} />
        <Route path="/refer/:token" component={() => <PublicLayout hideSearch><FoundingMemberReferralLanding /></PublicLayout>} />
        <Route path="/charity-profiles" component={() => <PublicLayout><CharityProfiles /></PublicLayout>} />
        <Route path="/corporate-sponsors" component={() => <PublicLayout hideSearch><CorporateSponsors /></PublicLayout>} />
        
        <Route path="/blog" component={() => <PublicLayout><BlogPage /></PublicLayout>} />
        <Route path="/blog/category/:slug" component={() => <PublicLayout><BlogCategoryPage /></PublicLayout>} />
        <Route path="/blog/:slug" component={() => <PublicLayout><BlogArticlePage /></PublicLayout>} />
        
        {/* Newsletter Detail Route */}
        <Route path="/newsletter/:slug" component={() => <PublicLayout><NewsletterDetail /></PublicLayout>} />
        
        <Route path="/help/resources" component={() => <PublicLayout><HelpResources /></PublicLayout>} />
        
        <Route path="/city/:citySlug" component={() => <PublicLayout><CityPage /></PublicLayout>} />
        <Route path="/washington-dc" component={() => <PublicLayout><CityPage /></PublicLayout>} />
        <Route path="/baltimore-md" component={() => <PublicLayout><CityPage /></PublicLayout>} />
        <Route path="/frederick-md" component={() => <PublicLayout><CityPage /></PublicLayout>} />
        <Route path="/bethesda-rockville-md" component={() => <PublicLayout><CityPage /></PublicLayout>} />
        <Route path="/arlington-va" component={() => <PublicLayout><CityPage /></PublicLayout>} />
        <Route path="/alexandria-va" component={() => <PublicLayout><CityPage /></PublicLayout>} />
        <Route path="/:stateSlug/:citySlug/guide/:guideSlug" component={() => <PublicLayout><LocalGuidePage /></PublicLayout>} />
        <Route path="/:stateSlug/:citySlug/directory" component={() => <PublicLayout><DirectoryPage /></PublicLayout>} />
        {/* /directory/:stateCode — catches typos/invalid state codes like /directory/XX */}
        <Route path="/directory/:stateCode" component={() => <PublicLayout><DirectoryStateRedirect /></PublicLayout>} />
        <Route path="/category/:slug" component={() => <PublicLayout><CategoryPage /></PublicLayout>} />
        
        <Route path="/admin-promotions" component={() => {
          window.location.href = '/admin/promotions';
          return null;
        }} />
        
        <Route path="/:stateSlug/:citySlug/:serviceSlug">{(params) => {
          if (params.serviceSlug?.startsWith('emergency-')) {
            return <PublicLayout><EmergencyServicePage /></PublicLayout>;
          }
          return <PublicLayout hideBottomNavOnMobile disableMobileBottomClearance><ServiceCategoryPage /></PublicLayout>;
        }}</Route>
        <Route path="/:stateSlug/:citySlug" component={() => <PublicLayout><CityPage /></PublicLayout>} />
        <Route path="/:stateSlug" component={() => <PublicLayout><StatePage /></PublicLayout>} />
        
        {/* Wildcard business profile routes - MUST come after all specific routes */}
        <Route path="/:state/:city/:slug/:id" component={() => <PublicLayout hideSearch={true}><BusinessProfile /></PublicLayout>} />
        <Route path="/business/:id" component={() => <PublicLayout hideSearch={true}><BusinessProfile /></PublicLayout>} />
        
        <Route component={NotFound} />
      </Switch></ScrollRestoration>
  );
}

function LocaleRouter() {
  const [pathname, setPathname] = useState(window.location.pathname);
  
  useEffect(() => {
    return subscribeToLocationChanges(() => {
      setPathname(window.location.pathname);
    });
  }, []);
  
  const locale = getLocaleFromPath(pathname);
  const base = locale === 'es-US' ? LOCALE_PREFIX : '';
  
  return (
    <WouterRouter base={base} key={base}>
      <LocaleAwareRouter />
    </WouterRouter>
  );
}

function App() {
  // Initialize accessibility features
  useAccessibility(); // Apply accessibility preferences globally (large text, reduced motion, high contrast)
  useSwipeBack();
  const { announcePageChange } = useAccessibilityAnnouncements();
  useKeyboardNavigation({
    enableSkipLinks: true,
    onEscape: () => {
      // Close any open modals or dropdowns on Escape
      const activeElement = document.activeElement as HTMLElement;
      activeElement?.blur();
    }
  });

  const { showHelp, closeHelp } = useConsumerShortcuts();

  useWebVitals();

  useEffect(() => {
    initReferralTracking();
  }, []);

  // Load the lazy English namespace bundle (business, auth, dashboard,
  // admin, etc.) so non-home routes never key-flash. Three triggers, all
  // sharing the same memoized promise inside loadEnRest():
  //   1. Deep links to non-shell paths fetch immediately on mount, in
  //      parallel with the route chunk download.
  //   2. Home-route entry defers until requestIdleCallback (setTimeout
  //      fallback for Safari) so first paint isn't blocked.
  //   3. First client-side navigation away from a shell-only path also
  //      triggers immediately — closes the race where a user clicks a
  //      link before the idle callback fires.
  useEffect(() => {
    let cancelled = false;
    let triggered = false;
    const warm = () => {
      if (cancelled || triggered) return;
      triggered = true;
      void import("@/lib/i18n/loadLocale").then((m) => m.loadEnRest()).catch(() => {});
    };
    const isShellOnlyPath = (raw: string) => {
      const path = raw.replace(/\/+$/, "");
      return path === "" || path === "/en";
    };

    // Trigger 3: first nav to a non-shell path.
    const unsubscribe = subscribeToLocationChanges(() => {
      if (!isShellOnlyPath(window.location.pathname)) warm();
    });

    // Trigger 1: deep-link entry already on a non-shell path.
    if (!isShellOnlyPath(window.location.pathname)) {
      warm();
      return () => { cancelled = true; unsubscribe(); };
    }

    // Trigger 2: idle warm for shell-entry sessions.
    const w = window as Window & { requestIdleCallback?: (cb: () => void, opts?: { timeout: number }) => number };
    const handle = w.requestIdleCallback
      ? w.requestIdleCallback(warm, { timeout: 2000 })
      : window.setTimeout(warm, 1500);
    return () => {
      cancelled = true;
      unsubscribe();
      if (w.requestIdleCallback) {
        (window as unknown as { cancelIdleCallback?: (id: number) => void }).cancelIdleCallback?.(handle as number);
      } else {
        window.clearTimeout(handle as number);
      }
    };
  }, []);

  // Google Analytics: dynamic import keeps the GA client + PII-sanitization
  // utilities (~230 LOC) off the eager critical path. Gated on env var so
  // the chunk is never even requested when GA is disabled.
  useEffect(() => {
    if (!import.meta.env.VITE_GA_MEASUREMENT_ID) return;
    void import("@/lib/analytics")
      .then((m) => m.initGA())
      .catch((err) => devWarn("[analytics] init failed", err));
  }, []);

  // Spotify pixel: dynamic import for the same reason. Pixel attribution
  // tolerates the few-ms post-mount delay since events are deduped server-side.
  useEffect(() => {
    void import("@/lib/spotifyPixel")
      .then((m) => m.initSpotifyPixel())
      .catch((err) => devWarn("[spotify-pixel] init failed", err));
  }, []);

  const siteSchemas = useMemo(() => {
    const baseUrl = window.location.origin;

    const organizationSchema = {
      "@context": "https://schema.org",
      "@id": `${baseUrl}/#organization`,
      ...CHOICY_LOCAL_ORG,
    };

    const websiteSchema = {
      "@context": "https://schema.org",
      "@type": "WebSite",
      "@id": `${baseUrl}/#website`,
      "url": baseUrl,
      "name": "Choicy Local",
      "description": "Find and connect with great local businesses in your area",
      "publisher": {
        "@id": `${baseUrl}/#organization`
      },
      "potentialAction": {
        "@type": "SearchAction",
        "target": {
          "@type": "EntryPoint",
          "urlTemplate": `${baseUrl}/search?q={search_term_string}`
        },
        "query-input": "required name=search_term_string"
      }
    };

    return [organizationSchema, websiteSchema];
  }, []);

  return (
    <GateLogin>
    <ErrorBoundary>
      <I18nProvider>
      <HelmetProvider>
          <QueryClientProvider client={queryClient}>
            <AuthProvider>
              <GuidanceProvider>
                <BottomOverlayProvider>
                <TooltipProvider>
                {/* Site-wide Schema.org structured data */}
                <Helmet>
                  <title>Choicy Local - Find Trusted Local Businesses Near You</title>
                  <meta name="description" content="Find and support trusted local businesses in your area. Real reviews, real results. Save time finding the right local business." />
                  {siteSchemas.map((schema, index) => (
                    <script
                      key={`site-schema-${index}`}
                      type="application/ld+json"
                      dangerouslySetInnerHTML={{
                        __html: JSON.stringify(schema)
                          .replace(/</g, "\\u003c")
                          .replace(/>/g, "\\u003e") 
                          .replace(/&/g, "\\u0026")
                      }}
                    />
                  ))}
                </Helmet>
                <SkipLinks />
                <NpsSurveyProvider>
                <ImpersonationGate>
                  <div id="main-content" role="main" tabIndex={-1} className="focus:outline-none flex min-h-screen w-full flex-col">
                    <Suspense fallback={<DelayedLoader />}>
                      <LocaleRouter />
                    </Suspense>
                  </div>
                </ImpersonationGate>
                <Suspense fallback={null}>
                  <Toaster />
                </Suspense>
                <AppUpdateToast />
                <Suspense fallback={null}><WhatsNewDialog /></Suspense>
                <Suspense fallback={null}><ConditionalFeedbackButton /></Suspense>
                <Suspense fallback={null}><PWAInstallPrompt /></Suspense>
                <Suspense fallback={null}><CookieConsent /></Suspense>
                {showHelp && (
                  <Suspense fallback={null}>
                    <KeyboardShortcutHelp open={showHelp} onOpenChange={(open) => { if (!open) closeHelp(); }} />
                  </Suspense>
                )}
                <Suspense fallback={null}><OfflineIndicator /></Suspense>
                </NpsSurveyProvider>
                </TooltipProvider>
                </BottomOverlayProvider>
              </GuidanceProvider>
            </AuthProvider>
          </QueryClientProvider>
      </HelmetProvider>
      </I18nProvider>
    </ErrorBoundary>
    </GateLogin>
  );
}

export default App;