import { AppInsightsBoundary } from '@logto/app-insights/react';
import { UserScope } from '@logto/core-kit';
import { LogtoProvider, useLogto } from '@logto/react';
import { adminConsoleApplicationId, PredefinedScope } from '@logto/schemas';
import { conditionalArray, deduplicate } from '@silverhand/essentials';
import { useContext, useMemo } from 'react';
import { Helmet } from 'react-helmet';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

import 'overlayscrollbars/overlayscrollbars.css';
import './scss/normalized.scss';
import './scss/overlayscrollbars.scss';
// eslint-disable-next-line import/no-unassigned-import
import '@fontsource/roboto-mono';

import CloudAppRoutes from '@/cloud/AppRoutes';
import AppLoading from '@/components/AppLoading';
import { isCloud } from '@/consts/env';
import { cloudApi, getManagementApi, meApi } from '@/consts/resources';
import useMeCustomData from '@/hooks/use-me-custom-data';
import useTrackUserId from '@/hooks/use-track-user-id';
import { OnboardingRoutes } from '@/onboarding';
import useUserOnboardingData from '@/onboarding/hooks/use-user-onboarding-data';
import { ConsoleRoutes } from '@/pages/ConsoleRoutes';

import { adminTenantEndpoint, mainTitle } from './consts';
import ErrorBoundary from './containers/ErrorBoundary';
import LogtoErrorBoundary from './containers/LogtoErrorBoundary';
import AppConfirmModalProvider from './contexts/AppConfirmModalProvider';
import AppDataProvider, { AppDataContext } from './contexts/AppDataProvider';
import { AppThemeProvider } from './contexts/AppThemeProvider';
import TenantsProvider, { TenantsContext } from './contexts/TenantsProvider';
import initI18n from './i18n/init';

void initI18n();

/**
 * The main entry of the project. It provides two fundamental context providers:
 *
 * - `RouterProvider`: the sole router provider of the project.
 * - `TenantsProvider`: manages the tenants data, requires the `RouterProvider` to
 * get the current tenant ID from the URL.
 */
function App() {
  const router = createBrowserRouter([
    {
      path: '*',
      element: (
        <TenantsProvider>
          <Providers />
        </TenantsProvider>
      ),
    },
  ]);

  return <RouterProvider router={router} />;
}

export default App;

/**
 * This component serves as a container for all the providers and boundary components.
 *
 * Since `TenantsContext` requires the `TenantsProvider` to be mounted, and the initialization
 * of `LogtoProvider` requires the `TenantsContext` to be available, we have to put them into
 * different components.
 */
function Providers() {
  const { tenants, currentTenantId } = useContext(TenantsContext);

  const resources = useMemo(
    () =>
      deduplicate(
        conditionalArray(
          // Explicitly add `currentTenantId` and deduplicate since the user may directly
          // access a URL with Tenant ID, adding the ID from the URL here can possibly remove one
          // additional redirect.
          currentTenantId && getManagementApi(currentTenantId).indicator,
          ...tenants.map(({ id }) => getManagementApi(id).indicator),
          isCloud && cloudApi.indicator,
          meApi.indicator
        )
      ),
    [currentTenantId, tenants]
  );

  const scopes = useMemo(
    () => [
      UserScope.Email,
      UserScope.Identities,
      UserScope.CustomData,
      PredefinedScope.All,
      ...conditionalArray(
        isCloud && cloudApi.scopes.CreateTenant,
        isCloud && cloudApi.scopes.ManageTenant,
        isCloud && cloudApi.scopes.ManageTenantSelf
      ),
    ],
    []
  );

  return (
    <LogtoProvider
      unstable_enableCache
      config={{
        endpoint: adminTenantEndpoint.href,
        appId: adminConsoleApplicationId,
        resources,
        scopes,
      }}
    >
      <AppThemeProvider>
        <AppInsightsBoundary cloudRole="console">
          <Helmet titleTemplate={`%s - ${mainTitle}`} defaultTitle={mainTitle} />
          <ErrorBoundary>
            <LogtoErrorBoundary>
              {/**
               * If it's not Cloud (OSS), render the tenant app container directly since only default tenant is available;
               * if it's Cloud, render the tenant app container only when a tenant ID is available (in a tenant context).
               */}
              {!isCloud || currentTenantId ? (
                <AppDataProvider>
                  <AppConfirmModalProvider>
                    <AppRoutes />
                  </AppConfirmModalProvider>
                </AppDataProvider>
              ) : (
                <CloudAppRoutes />
              )}
            </LogtoErrorBoundary>
          </ErrorBoundary>
        </AppInsightsBoundary>
      </AppThemeProvider>
    </LogtoProvider>
  );
}

/** Renders different routes based on the user's onboarding status. */
function AppRoutes() {
  const { tenantEndpoint } = useContext(AppDataContext);
  const { isLoaded } = useMeCustomData();
  const { isOnboarding } = useUserOnboardingData();
  const { isAuthenticated } = useLogto();

  useTrackUserId();

  // Authenticated user should load onboarding data before rendering the app.
  // This looks weird and it will be refactored soon by merging the onboarding
  // routes with the console routes.
  if (!tenantEndpoint || (isCloud && isAuthenticated && !isLoaded)) {
    return <AppLoading />;
  }

  return isAuthenticated && isOnboarding ? <OnboardingRoutes /> : <ConsoleRoutes />;
}
