import { Admin } from './Admin';
import { CatchResult, IErrorEventHandler, useErrorEventCatcher } from './dev';
import { useI18nProvider } from './i18n';
import { ThemeCustomizationProvider } from './themes';
import {
  AuthBackground,
  GenericErrorPage,
  Layout,
  LoginPage,
  MainIcon,
  MenuConfigProvider,
  MenuItem,
  Notification,
  SmallIcon
} from '@/components';
import type { PushNotificationConfig } from '@/hooks/usePushNotifications';
import { AppStateProvider } from '@/components/AppStateProvider';
import { ThemeConfig, ThemeProvider } from '@/components/Layout/ThemeProvider';
import { GoogleOAuthProvider } from '@react-oauth/google';
import React, { useMemo } from 'react';
import { AdminProps, AuthProvider, DataProvider, I18nProvider } from 'react-admin';
import { QueryClient } from 'react-query';

const defaultQueryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false
    }
  }
});

/**
 * Define a basic super cool application based on React Admin, Mantis Theme and our style.
 */
function ApplicaAdmin({
  theme,
  themeConfig,
  apiUrl,
  defaultLocale = 'en',
  development = false,
  logoMain,
  logoIcon,
  loginPage = <LoginPage version="0.0.0" name="Applica" redirectTo="/" background={<AuthBackground />} />,
  menu,
  name,
  copy,
  version = '0.0.0',
  dataProvider,
  authProvider,
  i18nProvider: _i18nProvider,
  errorHandler,
  error,
  enableNotification = false,
  enableRegistration = false,
  enablePasswordRecover = false,
  enableThemeToggler = false,
  enableLocaleSwitcher = false,
  oauth = {},
  queryClient = defaultQueryClient,
  background,
  notificationAPI = 'entities/notification',
  pushNotifications,
  ...props
}: ApplicaAdminProps): JSX.Element {
  useErrorEventCatcher({
    apiUrl,
    errorHandler,
    catcherFn: (error: ErrorEvent | string): CatchResult => {
      const errorMessage = error?.toString();
      const knownErrors = [
        // @see https://github.com/marmelab/react-admin/pull/8884
        'Invalid prop `file` of type `string` supplied to `FileInputPreview`, expected `object`.',
        'Failed prop type: Invalid prop `checked` of type `string` supplied to `ForwardRef(Switch)`, expected `boolean`.',
        'Failed prop type: Invalid prop `checked` of type `string` supplied to `ForwardRef(SwitchBase)`, expected `boolean`.',
        'Support for defaultProps will be removed from function components in a future major release.',
        'validateDOMNesting(...): <form> cannot appear as a descendant of <form>.',
        'Missing translation for key:',
        'HttpError: '
      ];
      const isKnowBug = knownErrors.some((knownError) => errorMessage?.includes(knownError));
      const result = new CatchResult({
        catch: isKnowBug,
        display: !isKnowBug,
        log: !isKnowBug && development === true,
        error: errorMessage
      });
      return result;
    }
  });
  const login = useMemo(() => {
    if (React.isValidElement(loginPage)) {
      return React.cloneElement(loginPage, {
        // @ts-ignore
        name,
        copy,
        logo: logoMain,
        version,
        background,
        enableRegistration,
        enablePasswordRecover,
        oauth
      });
    }
    return loginPage;
  }, [loginPage, name, version, copy, background, logoMain, enableRegistration, enablePasswordRecover, oauth]);
  const layout = useMemo(
    () => (props: any) => {
      const _logoMain = name ? <MainIcon title={name} /> : logoMain;
      const _logoIcon = name ? <SmallIcon title={name} /> : logoIcon;
      return (
        <Layout
          {...props}
          name={name}
          copy={copy}
          version={version}
          logoMain={_logoMain}
          logoIcon={_logoIcon}
          notification={notificationAPI}
          enableThemeToggler={enableThemeToggler}
          enableNotification={enableNotification}
          enableLocaleSwitcher={enableLocaleSwitcher}
          error={error}
          pushNotifications={pushNotifications}
        />
      );
    },
    [
      logoMain,
      logoIcon,
      name,
      copy,
      version,
      error,
      notificationAPI,
      enableNotification,
      enableThemeToggler,
      enableLocaleSwitcher,
      pushNotifications
    ]
  );

  const i18nProvider = useI18nProvider({
    apiUrl: apiUrl,
    defaultLocale: defaultLocale,
    allowMissing: development,
    createMissing: development,
    customProvider: _i18nProvider
  }) as I18nProvider;

  if (i18nProvider === undefined) {
    return <></>;
  } else if (i18nProvider.error) {
    return (
      <ThemeProvider initialConfig={themeConfig}>
        <ThemeCustomizationProvider themeOverrides={theme}>
          <GenericErrorPage />
        </ThemeCustomizationProvider>
      </ThemeProvider>
    );
  }

  const appContent = (
    <AppStateProvider>
      <MenuConfigProvider menu={menu}>
        <ThemeProvider initialConfig={themeConfig}>
          <Admin
            theme={theme}
            layout={layout}
            notification={Notification}
            queryClient={queryClient}
            dataProvider={dataProvider}
            authProvider={authProvider}
            i18nProvider={i18nProvider}
            loginPage={login}
            {...props}
          />
        </ThemeProvider>
      </MenuConfigProvider>
    </AppStateProvider>
  );

  if (oauth.google?.clientId) {
    return <GoogleOAuthProvider clientId={oauth.google.clientId}>{appContent}</GoogleOAuthProvider>;
  } else {
    return appContent;
  }
}

type ApplicaAdminProps = AdminProps & {
  /**
   * Optional: Push notification configuration for enabling FCM notifications.
   * If provided, enables foreground push notification listening and triggers notification panel refresh on new messages.
   */
  pushNotifications?: PushNotificationConfig;
  /**
   * Optionally, a custom theme to use in the application.
   * @remarks This theme is based on Mantis Theme (https://mantisdashboard.io/)
   *
   * @see https://marmelab.com/react-admin/Theming.html
   * @see https://material-ui.com/customization/theming/
   */
  theme: any;
  /**
   * Optionally, a custom theme configuration to use in the application.
   * Have fun with caution to modify theme configuration variables, if you make a mess Marco Colucci could use the fire extinguisher (I said everything).
   */
  themeConfig: ThemeConfig;
  /**
   * The URL of the API to use in the application.
   * Although a default dataProvider and authProvider are already defined, it is possible that the app
   * uses the specified apiUrl for further operations (e.g. handling notifications).
   */
  apiUrl: string;
  /**
   * The default language to use in the application (default: en).
   */
  defaultLocale: string;
  /**
   * If set to true, the application works in development mode.
   * Development mode is useful for testing and debugging and performs activities that are not
   * recommended in production (e.g. capturing and forwarding non-localized messages).
   */
  development: boolean;
  /**
   * The main logo to display in the sidebar when the menu is expanded laterally.
   */
  logoMain: any;
  /**
   * The icon to display in the sidebar when the menu is collapsed laterally.
   */
  logoIcon: any;
  /**
   * Configured menu to display in the application.
   */
  menu: MenuItem[];
  /**
   * The name of the application to display in the header.
   */
  name: string;
  /**
   * Text to display in the footer. Default: '&copy; Applica Software Guru for'.
   */
  copy: string;
  /**
   * Application version to display in the footer.
   */
  version: string;
  /**
   * The data provider to use in the application.
   */
  dataProvider: DataProvider;
  /**
   * The auth provider to use in the application.
   */
  authProvider: AuthProvider;
  /**
   * The i18n provider activation function.
   * If not specified, the default i18n provider is used.
   */
  i18nProvider?: Promise<I18nProvider>;

  /**
   * Indicates the handler to use for error management.
   * By default, a PUT /api/ui/error call is made with the error message "error".
   * You can implement your own handler to manage errors differently.
   */
  errorHandler?: IErrorEventHandler | undefined;
  /**
   * Indicates the component to display in case of error.
   */
  error: React.Component;
  /**
   * Indicates the name of the REST resource to use for notification management.
   * The REST resource must be compatible with standard CRUD operations.
   * @default "entities/notification"
   * @example
   * // In this case, notifications will be managed through the "entities/notification" resource
   * <ApplicaAdmin notificationAPI="entities/notification" />
   */
  notificationAPI: string;
  /**
   * Indicates whether notifications should be disabled.
   * If notifications are enabled, an icon will automatically appear in the top right of the header.
   *
   * @example
   * <ApplicaAdmin enableNotification />
   */
  enableNotification: boolean;
  /**
   * Indicates whether the registration screen should be disabled.
   * If enabled, it is necessary to register a page, in the routes, that points to /register
   *
   * @example
   * // Basic page made by Applica
   * import { RegisterPage } from "@applica-software-guru/react-admin";
   * <CustomRoutes noLayout>
   *  <Route path="/register" component={RegisterPage} />
   * </CustomRoutes>
   */
  enableRegistration: boolean;
  /**
   * Indicates whether the password recovery screen should be disabled.
   * If enabled, it is necessary to register a page, in the routes, that points to /recover
   *
   * @example
   * // Basic page made by Applica
   * import { RecoverPage } from "@applica-software-guru/react-admin";
   * <CustomRoutes noLayout>
   *  <Route path="/recover" component={RecoverPage} />
   * </CustomRoutes>
   */
  enablePasswordRecover: boolean;
  /**
   * The query client instance to use in the application.
   */
  queryClient: QueryClient;
  /**
   * Background image to display in login, recover, and register pages.
   * This image must be a component with absolute positioning.
   */
  background?: React.ReactNode;
  /**
   * Indicates whether the theme toggler should be displayed.
   */
  enableThemeToggler: boolean;
  /**
   * Indicates whether the locale switcher should be displayed.
   */
  enableLocaleSwitcher: boolean;
  /**
   * Indicates which OAuth providers to enable and configurations.
   */
  oauth: {
    google?: {
      clientId: string;
    };
  };
};

export { ApplicaAdmin };
export type { ApplicaAdminProps };
