/**
 * Application root: routing, data fetching, and the auth gate.
 */
import { lazy, Suspense, useEffect, useState } from 'react';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { ApiError, get, getSupabase, loadPublicConfig, patch, type PublicConfig } from '@/lib/api';
import { ThemeProvider } from '@/lib/theme';
import { Shell } from '@/components/Shell';
import { SignIn } from '@/pages/SignIn';
import { CreateProfile } from '@/pages/CreateProfile';
import { Register } from '@/pages/Register';
import { PiggyMark } from '@/components/PiggyMark';
import { Badge, Card, EmptyState, Skeleton } from '@/components/ui';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Toaster } from '@/components/ui/sonner';
import { usePageTitle } from '@/lib/title';

const Overview = lazy(() => import('@/pages/Overview').then(({ Overview }) => ({ default: Overview })));
const Capacity = lazy(() => import('@/pages/Capacity').then(({ Capacity }) => ({ default: Capacity })));
const DemandPipeline = lazy(() => import('@/pages/Pipeline').then(({ DemandPipeline }) => ({ default: DemandPipeline })));
const SupplyPipeline = lazy(() => import('@/pages/Pipeline').then(({ SupplyPipeline }) => ({ default: SupplyPipeline })));
const Settings = lazy(() => import('@/pages/Settings').then(({ Settings }) => ({ default: Settings })));
const Accounts = lazy(() => import('@/pages/Accounts').then(({ Accounts }) => ({ default: Accounts })));
const Margin = lazy(() => import('@/pages/Margin').then(({ Margin }) => ({ default: Margin })));
const FactReview = lazy(() => import('@/pages/FactReview').then(({ FactReview }) => ({ default: FactReview })));
const Contracts = lazy(() => import('@/pages/Contracts').then(({ Contracts }) => ({ default: Contracts })));
const Imports = lazy(() => import('@/pages/Imports').then(({ Imports }) => ({ default: Imports })));
const Piggy = lazy(() => import('@/pages/Piggy').then(({ Piggy }) => ({ default: Piggy })));
const Growth = lazy(() => import('@/pages/Growth').then(({ Growth }) => ({ default: Growth })));

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 30_000,
      // Refetching every time a phone user switches apps and comes back is
      // wasteful on cellular; the interval on the dashboard covers freshness.
      refetchOnWindowFocus: false,
      retry: (failureCount, error) => {
        // Retrying an auth failure just produces the same failure slower.
        if (error instanceof ApiError && (error.needsSignIn || error.needsProfile)) return false;
        return failureCount < 2;
      },
    },
  },
});

export function App() {
  const [config, setConfig] = useState<PublicConfig | null>(null);
  const [configError, setConfigError] = useState<string | null>(null);

  useEffect(() => {
    loadPublicConfig()
      .then(setConfig)
      .catch((error: unknown) =>
        setConfigError(error instanceof Error ? error.message : 'Could not reach the server.'),
      );
  }, []);

  if (configError) {
    return (
      <Centered>
        <EmptyState
          title="Cannot reach PIG"
          description={configError}
        />
      </Centered>
    );
  }

  if (!config) return <Splash />;

  return (
    <QueryClientProvider client={queryClient}>
      <ThemeProvider
        onPersist={(prefs) => {
          // Fire and forget: a failed preference save must never block the UI,
          // and the local copy already applied the change.
          void patch('/api/me/preferences', prefs).catch(() => {});
        }}
      >
        <BrowserRouter>
          <AuthGate config={config} />
        </BrowserRouter>
        {/* Inside ThemeProvider: the host reads the resolved light/dark value. */}
        <Toaster />
      </ThemeProvider>
    </QueryClientProvider>
  );
}

/**
 * Decides what to show based on *why* a request failed.
 *
 * The distinction between "not signed in" and "signed in but not a member" is
 * the one that matters: sending an invited-but-unprovisioned user back to a
 * login screen they have already completed is an infuriating loop, and it is
 * the default behaviour if both are treated as "auth error".
 */
function AuthGate({ config }: { config: PublicConfig }) {
  // Which unauthenticated screen to show. Kept in state rather than a route so
  // that a half-filled registration form is not lost to an accidental Back.
  const [showRegister, setShowRegister] = useState(false);

  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ['me'],
    queryFn: () => get<{ id: string; name: string }>('/api/me'),
  });

  // Adopt the server's stored appearance preferences once we know who this is.
  useEffect(() => {
    if (!data) return;
    void get<{ themeMode?: string; accentColor?: string }>('/api/me/profile')
      .then((profile) => {
        const adopt = (window as unknown as { __pigAdoptTheme?: (p: unknown) => void })
          .__pigAdoptTheme;
        if (profile && adopt) adopt(profile);
      })
      .catch(() => {});
  }, [data]);

  // React to sign-in and sign-out without a page reload.
  useEffect(() => {
    const supabase = getSupabase();
    if (!supabase) return;
    const { data: subscription } = supabase.auth.onAuthStateChange(() => {
      void refetch();
    });
    return () => subscription.subscription.unsubscribe();
  }, [refetch]);

  if (isLoading) return <Splash />;

  if (error instanceof ApiError) {
    if (error.needsSignIn) {
      return showRegister ? (
        <Register
          config={config}
          onBack={() => setShowRegister(false)}
          onRegistered={() => {
            setShowRegister(false);
            void refetch();
          }}
        />
      ) : (
        <SignIn config={config} onCreateAccount={() => setShowRegister(true)} />
      );
    }
    // Authenticated but not a member. This is a step in the flow, not an
    // error — sending them back to a login screen they have already completed
    // would be a loop with no exit.
    if (error.needsProfile) {
      return <CreateProfile config={config} onCreated={() => void refetch()} />;
    }
  }

  if (error || !data) {
    return (
      <Centered>
        <EmptyState
          title="Something went wrong"
          description={error instanceof Error ? error.message : 'Unknown error.'}
        />
      </Centered>
    );
  }

  return (
    <Routes>
      <Route element={<Shell />}>
        <Route index element={<RoutePage><Overview /></RoutePage>} />
        <Route path="margin" element={<RoutePage><Margin /></RoutePage>} />
        <Route path="growth" element={<RoutePage><Growth /></RoutePage>} />
        <Route path="capacity" element={<RoutePage><Capacity /></RoutePage>} />
        <Route path="demand" element={<RoutePage><DemandPipeline /></RoutePage>} />
        <Route path="supply" element={<RoutePage><SupplyPipeline /></RoutePage>} />
        <Route path="accounts" element={<RoutePage><Accounts /></RoutePage>} />
        <Route path="contracts" element={<RoutePage><Contracts /></RoutePage>} />
        <Route path="imports" element={<RoutePage><Imports /></RoutePage>} />
        <Route path="piggy" element={<RoutePage><Piggy /></RoutePage>} />
        <Route path="team" element={<Team />} />
        <Route path="facts" element={<RoutePage><FactReview /></RoutePage>} />
        <Route path="settings" element={<RoutePage><Settings /></RoutePage>} />
        <Route path="*" element={<Placeholder title="Not found" />} />
      </Route>
    </Routes>
  );
}

function RoutePage({ children }: { children: React.ReactNode }) {
  return (
    <Suspense fallback={<RouteLoading />}>
      {children}
    </Suspense>
  );
}

function RouteLoading() {
  return (
    <div
      className="flex min-h-[50dvh] items-center justify-center"
      role="status"
      aria-live="polite"
    >
      <div className="flex flex-col items-center gap-3 text-sm text-muted">
        <PiggyMark className="h-9 w-9 animate-pulse text-fg" aria-hidden />
        <span>Loading view…</span>
      </div>
    </div>
  );
}

function Splash() {
  return (
    <Centered>
      <PiggyMark className="h-12 w-12 animate-pulse text-fg" title="Loading pig" />
    </Centered>
  );
}

function Centered({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex min-h-dvh items-center justify-center bg-bg px-6">
      <div className="w-full max-w-md">{children}</div>
    </div>
  );
}

function Placeholder({ title }: { title: string }) {
  usePageTitle(title);
  return (
    <EmptyState
      title={title}
      description="That page does not exist or may have moved. Use Search to return to a workspace."
    />
  );
}

function Team() {
  usePageTitle('Team');
  const { data, isLoading, error } = useQuery({
    queryKey: ['team'],
    queryFn: () =>
      get<
        {
          id: string;
          name: string;
          email: string;
          title: string | null;
          teams: { team: string; role: string }[];
        }[]
      >('/api/team'),
  });

  const assignments = (data ?? []).reduce((total, person) => total + person.teams.length, 0);
  const representedTeams = new Set((data ?? []).flatMap((person) => person.teams.map((team) => team.team))).size;

  return (
    <div className="flex flex-col gap-6">
      <header className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
        <div>
          <p className="text-xs font-semibold uppercase tracking-[0.16em] text-accent-fg">Access map</p>
          <h1 className="mt-1 text-2xl font-semibold tracking-tight sm:text-3xl">Team</h1>
          <p className="mt-1 max-w-2xl text-sm leading-6 text-muted">
            See who can operate each side of the compute business and where ownership is thin.
          </p>
        </div>
        <Link
          to="/settings"
          className="tap inline-flex items-center self-start rounded-lg px-1 text-sm font-medium text-accent-fg underline-offset-4 hover:underline sm:self-auto"
        >
          Manage access in Settings
        </Link>
      </header>

      <div className="grid grid-cols-3 gap-2 sm:max-w-xl sm:gap-3">
        {[
          ['People', data?.length ?? 0],
          ['Teams', representedTeams],
          ['Assignments', assignments],
        ].map(([label, value]) => (
          <Card key={label} className="p-3 sm:p-4">
            <p className="text-[10px] font-semibold uppercase tracking-wide text-muted sm:text-xs">{label}</p>
            <p className="nums mt-1 text-2xl font-semibold">{value}</p>
          </Card>
        ))}
      </div>

      {error ? (
        <Card>
          <EmptyState title="Team unavailable" description={error instanceof Error ? error.message : 'Could not load team access.'} />
        </Card>
      ) : null}

      {isLoading ? (
        <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
          {[0, 1, 2].map((key) => <Skeleton key={key} className="h-40 rounded-2xl" />)}
        </div>
      ) : null}

      {!isLoading && !error && data?.length === 0 ? (
        <Card>
          <EmptyState title="No team members yet" description="Invite and assign the first operator from Settings." />
        </Card>
      ) : null}

      {!isLoading && !error && data?.length ? (
        <section aria-labelledby="team-members-heading">
          <div className="mb-3 flex items-center justify-between">
            <h2 id="team-members-heading" className="text-sm font-semibold">People and permissions</h2>
            <span className="text-xs text-muted">Roles are enforced server-side</span>
          </div>
          <div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
            {data.map((person) => {
              const initials = person.name
                .split(/\s+/)
                .filter(Boolean)
                .slice(0, 2)
                .map((part) => part[0]?.toUpperCase())
                .join('');
              return (
                <Card key={person.id} className="p-4 sm:p-5">
                  <div className="flex min-w-0 items-center gap-3">
                    <Avatar className="size-11 border border-border">
                      <AvatarFallback className="bg-accent-subtle text-sm font-semibold text-accent-fg">
                        {initials || 'P'}
                      </AvatarFallback>
                    </Avatar>
                    <div className="min-w-0">
                      <p className="truncate font-semibold">{person.name}</p>
                      <p className="truncate text-sm text-muted">{person.title || 'Team member'}</p>
                    </div>
                  </div>
                  <div className="mt-4 flex flex-wrap gap-1.5">
                    {person.teams.map((membership) => (
                      <Badge key={`${membership.team}:${membership.role}`} tone="accent">
                        {membership.team} · {membership.role}
                      </Badge>
                    ))}
                  </div>
                  {person.teams.length === 0 ? (
                    <p className="mt-4 text-sm text-warning">No operational team assigned</p>
                  ) : null}
                </Card>
              );
            })}
          </div>
        </section>
      ) : null}
    </div>
  );
}
