import React from 'react';
import * as Sentry from '@sentry/browser';
import { createSentryScrubber } from '@theorchard/sentry-scrubber';
import { AppConfigProvider } from '@theorchard/suite-config';
import { appendTranslations } from '@theorchard/suite-i18n';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { initApplicationLocale, SuiteApplication, PluginManager } from './app';
import { SentryErrorBoundary } from './components/sentryErrorBoundary';
import Datadog from './datadog';
import { getModuleTypePolicies, getModuleResourceTypeActions } from './modules';
import { FatalErrorPage } from './pages/fatalErrorPage';
import Segment from './segment';
import { mergeResourceTypeActions } from './utils/resourceTypeActions';
import { getRoutes, registerRootRoutes } from './utils/routing';
import { mergeTypePolicies } from './utils/typePolicies';
import type { FrontendAppConfig, SuiteInitProps } from './types';

type ReactDomRenderFunction = (app: React.ReactElement, element: Element) => void;

const renderApplication: ReactDomRenderFunction = (app, element) => {
    const root = createRoot(element);
    root.render(app);
};

export function initApplication<AppData = unknown>(
    options: SuiteInitProps<AppData> = {},
    domRender: ReactDomRenderFunction = renderApplication
) {
    registerRootRoutes(options.routes ?? []);

    const renderApp = (window.initFrontend = async (
        rootConfig,
        rootElement,
        reactDomRender = domRender
    ) => {
        const {
            title: appTitle,
            featureFlagFilter,
            profileType: defaultProfileType,
            secondaryProfileTypes,
            sensitiveScrubFields,
            ignoreErrors,
            trackResources,
            trackLongTasks,
        } = options;

        let config: FrontendAppConfig = {
            ...rootConfig,
            ...(appTitle ? { appTitle } : {}),
            ...(featureFlagFilter ? { featureFlagFilter } : {}),
            ...(defaultProfileType ? { defaultProfileType } : {}),
            ...(secondaryProfileTypes ? { secondaryProfileTypes } : {}),
            ...options.config,
        };

        if (!config.appTitle) config.appTitle = config.appName;

        try {
            const sentryConfig: Sentry.BrowserOptions = {
                dsn: config.sentryDsn,
                release: config.gitCommit,
                ignoreErrors,
            };

            if (sensitiveScrubFields && sensitiveScrubFields.length > 0) {
                const scrubber = createSentryScrubber({
                    sensitiveFields: sensitiveScrubFields,
                });
                sentryConfig.beforeSend = scrubber.beforeSendHandler;
            }

            Sentry.init(sentryConfig);
            Datadog.init(config, { ignoreErrors, trackResources, trackLongTasks });
            Segment.init(config);

            if (options.locales) appendTranslations(options.locales);

            initApplicationLocale({
                fixedLocale: config.locale,
                brand: config.brand,
                ...options.localization,
            });

            const updatedConfig = await PluginManager.onInit(options.plugins, {
                config,
            });

            config = {
                ...config,
                ...updatedConfig,
            };

            reactDomRender(
                <SentryErrorBoundary
                    errorComponent={({ error }) => {
                        Datadog.addError('ORCHARD_SUITE', {
                            ...error,
                            where: 'SUITE_FRONTEND_INIT_APPLICATION',
                        });
                        return <FatalErrorPage error={error} config={config} />;
                    }}
                >
                    <AppConfigProvider config={config}>
                        <BrowserRouter>
                            <SuiteApplication
                                {...options}
                                apollo={{
                                    ...options.apollo,
                                    typePolicies: mergeTypePolicies(
                                        options.apollo?.typePolicies ?? {},
                                        getModuleTypePolicies()
                                    ),
                                }}
                                resourceTypeActions={mergeResourceTypeActions(
                                    options.resourceTypeActions ?? [],
                                    getModuleResourceTypeActions()
                                )}
                                routes={getRoutes(config.module)}
                            />
                        </BrowserRouter>
                    </AppConfigProvider>
                </SentryErrorBoundary>,
                rootElement
            );
        } catch (error) {
            Sentry.captureException(error);

            reactDomRender(<FatalErrorPage error={error as Error} config={config} />, rootElement);
        }
    });

    return async (
        config: FrontendAppConfig = window.FRONTEND_CONFIG,
        domRender?: ReactDomRenderFunction
    ) => {
        const root = document.getElementById('root');

        if (!root) throw new Error('No app root element with id: "root"');

        await renderApp(config, root, domRender);
    };
}
