import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
import { SessionProvider } from '@documenso/lib/client-only/providers/session';
import { getBasePath } from '@documenso/lib/constants/app';
import { APP_I18N_OPTIONS, type SupportedLanguageCodes } from '@documenso/lib/constants/i18n';
import { getSigningBrand, signingBrandAsset } from '@documenso/lib/constants/signing-brand';
import { signingBrandStyle } from '@documenso/lib/constants/signing-brand-style';
import { createPublicEnv } from '@documenso/lib/utils/env';
import { extractLocaleData } from '@documenso/lib/utils/i18n';
import { TrpcProvider } from '@documenso/trpc/react';
import { getOrganisationSession } from '@documenso/trpc/server/organisation-router/get-organisation-session';
import { Toaster } from '@documenso/ui/primitives/toaster';
import { TooltipProvider } from '@documenso/ui/primitives/tooltip';
import { useLingui } from '@lingui/react';
import { NuqsAdapter } from 'nuqs/adapters/react-router/v7';
import { useEffect } from 'react';
import {
  data,
  isRouteErrorResponse,
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useMatches,
  useRouteLoaderData,
} from 'react-router';
import { PreventFlashOnWrongTheme, Theme, ThemeProvider, useTheme } from 'remix-themes';
import { nonceMiddleware } from '~/middleware/nonce';
import type { Route } from './+types/root';
import stylesheet from './app.css?url';
import { GenericErrorLayout } from './components/general/generic-error-layout';
import { langCookie } from './storage/lang-cookie.server';
import { themeSessionResolver } from './storage/theme-session.server';
import { appMetaTags } from './utils/meta';
import { nonce, nonceContext, useCspNonce } from './utils/nonce';

export const middleware = [nonceMiddleware];

export const links: Route.LinksFunction = () => [{ rel: 'stylesheet', href: stylesheet }];

export function meta() {
  return appMetaTags();
}

/**
 * Don't revalidate (run the loader on sequential navigations) on the root layout
 *
 * Update values via providers.
 */
export const shouldRevalidate = () => false;

export async function loader({ context, request }: Route.LoaderArgs) {
  const session = await getOptionalSession(request);

  const { getTheme } = await themeSessionResolver(request);

  const cookieHeader = request.headers.get('cookie') ?? '';

  let lang: SupportedLanguageCodes = await langCookie.parse(cookieHeader);

  if (!APP_I18N_OPTIONS.supportedLangs.includes(lang)) {
    lang = extractLocaleData({ headers: request.headers }).lang;
  }

  const disableAnimations = cookieHeader.includes('__disable_animations=true');

  let organisations = null;

  if (session.isAuthenticated) {
    organisations = await getOrganisationSession({ userId: session.user.id });
  }

  return data(
    {
      lang,
      theme: getTheme() ?? (getSigningBrand().theme === 'dark' ? Theme.DARK : Theme.LIGHT),
      disableAnimations,
      basePath: getBasePath(),
      // Surface the per-request CSP nonce produced by `securityHeadersMiddleware` so all
      // SSR-rendered <script>/<style> elements in this layout (and child
      // routes that need it) can carry the matching nonce attribute.
      nonce: context.get(nonceContext),
      session: session.isAuthenticated
        ? {
            user: session.user,
            session: session.session,
            organisations: organisations || [],
          }
        : null,
      publicEnv: createPublicEnv(),
    },
    {
      headers: {
        'Set-Cookie': await langCookie.serialize(lang),
      },
    },
  );
}

export function Layout({ children }: { children: React.ReactNode }) {
  const { theme, basePath } = useRouteLoaderData<typeof loader>('root') || {};
  const defaultTheme = getSigningBrand().theme === 'dark' ? Theme.DARK : Theme.LIGHT;

  return (
    <ThemeProvider specifiedTheme={theme ?? defaultTheme} themeAction={`${basePath ?? ''}/api/theme`}>
      <LayoutContent>{children}</LayoutContent>
    </ThemeProvider>
  );
}

export function LayoutContent({ children }: { children: React.ReactNode }) {
  const {
    publicEnv: loaderPublicEnv,
    session,
    lang: loaderLang,
    disableAnimations,
    basePath: loaderBasePath,
  } = useRouteLoaderData<typeof loader>('root') || {};
  const cspNonce = useCspNonce();
  const { i18n } = useLingui();
  const lang = loaderLang ?? i18n.locale;
  const publicEnv = loaderPublicEnv ?? (typeof window === 'undefined' ? createPublicEnv() : window.__ENV__);

  const [theme] = useTheme();

  const basePath = loaderBasePath ?? getBasePath();

  // Recipient routes (signing pages) put `documenso-branded` on <body> so the
  // <style> block from `RecipientBranding` applies to BOTH the main tree and
  // any portaled content (Radix dialogs/popovers/dropdowns mount outside the
  // route tree, attached directly to document.body).
  const matches = useMatches();
  const isRecipientRoute = matches.some((m) => m.id?.startsWith('routes/_recipient+'));

  return (
    // `suppressHydrationWarning` because `remix-themes` intentionally mutates
    // `data-theme`/`class` on <html> before hydration (PreventFlashOnWrongTheme),
    // so the server-rendered attributes never match the client render when the
    // theme is resolved from the system preference. Attribute-only, one level deep.
    <html
      data-signing-brand={getSigningBrand().id}
      translate="no"
      lang={lang}
      data-theme={theme}
      className={theme ?? ''}
      suppressHydrationWarning
    >
      <head>
        <meta charSet="utf-8" />
        <link rel="apple-touch-icon" sizes="180x180" href={`${basePath}${signingBrandAsset('apple-touch-icon.png')}`} />
        <link rel="icon" type="image/png" sizes="32x32" href={`${basePath}${signingBrandAsset('favicon-32x32.png')}`} />
        <link rel="icon" type="image/png" sizes="16x16" href={`${basePath}${signingBrandAsset('favicon-16x16.png')}`} />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="manifest" href={`${basePath}${signingBrandAsset('site.webmanifest')}`} />
        <meta name="google" content="notranslate" />
        <Meta />
        <Links nonce={nonce(cspNonce)} />
        <style nonce={nonce(cspNonce)}>{signingBrandStyle()}</style>
        <meta name="google" content="notranslate" />
        <PreventFlashOnWrongTheme ssrTheme={Boolean(theme)} nonce={nonce(cspNonce)} />

        {disableAnimations && (
          <style
            nonce={nonce(cspNonce)}
            dangerouslySetInnerHTML={{
              __html: `*, *::before, *::after { animation: none !important; transition: none !important; }`,
            }}
          />
        )}

        {/* Fix: https://stackoverflow.com/questions/21147149/flash-of-unstyled-content-fouc-in-firefox-only-is-ff-slow-renderer */}
        <script nonce={nonce(cspNonce)}>0</script>
      </head>
      <body className={isRecipientRoute ? 'documenso-branded' : undefined}>
        {/* Global license banner currently disabled. Need to wait until after a few releases. */}
        {/* {licenseStatus === '?' && (
          <div className="bg-destructive text-destructive-foreground">
            <div className="mx-auto flex h-auto max-w-screen-xl items-center justify-center px-4 py-3 text-sm font-medium">
              <div className="flex items-center">
                <AlertTriangleIcon className="mr-2 h-4 w-4" />
                <Trans>This is an expired license instance of Documenso</Trans>
              </div>
            </div>
          </div>
        )} */}

        <NuqsAdapter>
          <SessionProvider initialSession={session ?? null}>
            <TooltipProvider>
              <TrpcProvider>
                {children}

                <Toaster />
              </TrpcProvider>
            </TooltipProvider>
          </SessionProvider>
        </NuqsAdapter>

        <script
          nonce={nonce(cspNonce)}
          dangerouslySetInnerHTML={{
            // `__webpack_nonce__` is read by `get-nonce` (used by
            // react-remove-scroll / react-style-singleton inside Radix menus and
            // dialogs) to stamp runtime-injected <style> elements. Without it the
            // strict `style-src-elem` CSP blocks the scroll-lock styles.
            __html: `window.__ENV__ = ${JSON.stringify(publicEnv)}; window.__webpack_nonce__ = ${JSON.stringify(cspNonce ?? '')}`,
          }}
        />

        <footer
          className="px-4 py-2 text-center text-muted-foreground text-xs print:hidden"
          data-verocsign-notice="2.18.0-verocsign.20260917-r4"
        >
          <a className="underline" href={basePath + '/verocsign/notices.html'}>
            Informace o aplikaci
          </a>
        </footer>

        <ScrollRestoration nonce={nonce(cspNonce)} />
        <Scripts nonce={nonce(cspNonce)} />
      </body>
    </html>
  );
}

export default function App() {
  return <Outlet />;
}

export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
  const analytics = useAnalytics();

  const errorCode = isRouteErrorResponse(error) ? error.status : 500;

  if (errorCode !== 404) {
    console.error('[RootErrorBoundary]', error);
  }

  useEffect(() => {
    if (errorCode !== 404) {
      analytics.captureException(error, { source: 'app', location: 'root_boundary' });
    }
  }, [error]);

  return <GenericErrorLayout errorCode={errorCode} />;
}
