/**
 * Routes, built from the registry.
 *
 * There is exactly one demo route — `/demos/:slug` — and it resolves through
 * `@/lib/demo-kit/registry`. Adding a demo to this site is creating a directory
 * under `src/demos/`; it is not, and must never become, an edit to this file.
 * The same goes for `/verticals/:slug`, which groups whatever the registry
 * found rather than a list written down anywhere.
 *
 * Every route below `/` sits under one layout with a root error boundary, and
 * the demo route adds its own on top. That nesting is the point: a demo whose
 * chunk fails to load, or whose module is malformed, renders a card inside the
 * normal page chrome. A single broken demo taking the whole site to a white
 * screen would be the most expensive bug this repo could ship, given what the
 * site is arguing.
 */

import type { ReactNode } from 'react';
import {
  createBrowserRouter,
  isRouteErrorResponse,
  Link,
  Outlet,
  ScrollRestoration,
  useRouteError,
  type LoaderFunctionArgs,
  type RouteObject,
} from 'react-router-dom';
import { getDemo, loadDemoModule } from '@/lib/demo-kit/registry';
import { DemoErrorBoundary } from '@/components/demo/DemoErrorBoundary';
import { SiteFooter } from '@/components/site/SiteFooter';
import { SiteHeader } from '@/components/site/SiteHeader';
import { SkipLink } from '@/components/site/SkipLink';
import * as s from '@/content/styles';
import Home from '@/pages/Home';

/**
 * The one element that renders on every route.
 *
 * The header, the footer and the skip link live here rather than in each page,
 * so a new page cannot ship without them. `#main` is this wrapper, not the
 * page's own `<main>`: pages own their landmark, and a second `<main>` around
 * theirs would be invalid HTML and a duplicate landmark in a screen reader's
 * rotor. The wrapper is only what the skip link focuses.
 */
export function RootLayout(): ReactNode {
  return (
    <div className="flex min-h-dvh flex-col bg-bg text-fg">
      <SkipLink />
      <SiteHeader />
      {/* Restores scroll on Back, and puts a fresh route at the top rather
          than halfway down the previous page. */}
      <ScrollRestoration />
      <div id="main" tabIndex={-1} className="flex-1 outline-none">
        <Outlet />
      </div>
      <SiteFooter />
    </div>
  );
}

function ErrorCard({
  heading,
  body,
  detail,
}: {
  heading: string;
  body: string;
  detail?: string;
}): ReactNode {
  return (
    <main className={`${s.shell} py-16 sm:py-24`}>
      <div className="card max-w-2xl p-6 sm:p-8" role="alert">
        <p className={`${s.eyebrow} text-danger`}>Something broke</p>
        <h1 className={`${s.h2} mt-2`}>{heading}</h1>
        <p className={`${s.prose} mt-3`}>{body}</p>
        {detail ? (
          <pre className="mt-4 overflow-x-auto rounded-md border border-border bg-surface-2 p-3 text-xs text-muted">
            <code>{detail}</code>
          </pre>
        ) : null}
        <div className="mt-6 flex flex-wrap gap-3">
          <Link to="/gallery" className={s.btnPrimary}>
            Back to the demos
          </Link>
          <Link to="/" className={s.btnSecondary}>
            Home
          </Link>
        </div>
      </div>
    </main>
  );
}

/** Turns whatever react-router threw into something a person can read. */
function describeError(error: unknown): { heading: string; body: string; detail?: string } {
  if (isRouteErrorResponse(error)) {
    if (error.status === 404) {
      return {
        heading: 'That page does not exist',
        body: 'The link may be from an older version of the site, or the demo it pointed at has been renamed.',
        detail: typeof error.data === 'string' ? error.data : undefined,
      };
    }
    return {
      heading: `${error.status} ${error.statusText}`,
      body: 'The page could not be loaded.',
      detail: typeof error.data === 'string' ? error.data : undefined,
    };
  }
  if (error instanceof Error) {
    return {
      heading: 'This page failed to load',
      body: 'The rest of the site still works. If this keeps happening, the source is public and the issue is reproducible from it.',
      detail: error.message,
    };
  }
  return {
    heading: 'This page failed to load',
    body: 'The rest of the site still works.',
  };
}

function RootErrorBoundary(): ReactNode {
  const error = useRouteError();
  const described = describeError(error);
  return (
    <ErrorCard
      heading={described.heading}
      body={described.body}
      {...(described.detail === undefined ? {} : { detail: described.detail })}
    />
  );
}

function DemoRouteError(): ReactNode {
  const error = useRouteError();
  if (isRouteErrorResponse(error) && error.status === 404) {
    return (
      <ErrorCard
        heading="No demo by that name"
        body="Every demo on this site is a directory in the repository, so a missing one is usually a renamed slug rather than a deleted page."
        {...(typeof error.data === 'string' ? { detail: error.data } : {})}
      />
    );
  }
  const described = describeError(error);
  return (
    <ErrorCard
      heading="This demo failed to load"
      body="Only this demo is affected — the others are separate bundles and still work."
      {...(described.detail === undefined ? {} : { detail: described.detail })}
    />
  );
}

/** Shown while a lazy route's chunk is in flight. */
function RouteFallback(): ReactNode {
  return (
    <div
      className="mx-auto w-full max-w-canvas px-4 py-24 sm:px-6"
      role="status"
      aria-live="polite"
    >
      <p className="text-sm text-muted">Loading…</p>
    </div>
  );
}

/**
 * Validates the slug and starts the demo's chunk during the navigation rather
 * than after the page mounts. `loadDemoModule` caches the promise, so the page
 * calling it again resolves from cache instead of fetching twice.
 */
async function demoLoader({ params }: LoaderFunctionArgs) {
  const slug = params.slug;
  if (typeof slug !== 'string' || slug === '') {
    throw new Response('No demo slug in the URL.', { status: 404 });
  }
  const meta = getDemo(slug);
  if (!meta) {
    throw new Response(`No demo named "${slug}".`, { status: 404 });
  }
  return { meta, module: await loadDemoModule(slug) };
}

export const routes: RouteObject[] = [
  {
    path: '/',
    element: <RootLayout />,
    errorElement: <RootErrorBoundary />,
    hydrateFallbackElement: <RouteFallback />,
    children: [
      // Home is the landing page and is imported eagerly on purpose: making the
      // first paint wait on a second network round trip to save bytes on a page
      // almost every visitor sees is the wrong trade.
      { index: true, element: <Home /> },
      // Two paths, one page. `/gallery` is what the site's own links use;
      // `/demos` is the shape people guess and the one older links used, and a
      // redirect would cost a round trip to say the same thing.
      {
        path: 'gallery',
        lazy: async () => ({ Component: (await import('@/pages/Gallery')).default }),
      },
      {
        path: 'demos',
        lazy: async () => ({ Component: (await import('@/pages/Gallery')).default }),
      },
      {
        path: 'demos/:slug',
        loader: demoLoader,
        errorElement: <DemoRouteError />,
        // Two boundaries, because they catch different things. `errorElement`
        // above catches a loader or chunk failure; this one catches a demo whose
        // own Surface throws while rendering a board state, which the router
        // never sees.
        lazy: async () => {
          const { default: DemoPage } = await import('@/pages/DemoPage');
          return {
            Component: () => (
              <DemoErrorBoundary>
                <DemoPage />
              </DemoErrorBoundary>
            ),
          };
        },
      },
      {
        path: 'verticals/:slug',
        lazy: async () => ({ Component: (await import('@/pages/Vertical')).default }),
      },
      {
        path: 'honesty',
        lazy: async () => ({ Component: (await import('@/pages/Honesty')).default }),
      },
      {
        path: '*',
        lazy: async () => ({ Component: (await import('@/pages/NotFound')).default }),
      },
    ],
  },
];

export const router = createBrowserRouter(routes);
