/* globals process */
import React, { lazy, Suspense } from 'react';
import { ErrorMessage } from '@theorchard/suite-components';
import { Sentry, Segment } from '@theorchard/suite-frontend';
import IdentityContext from 'src/context';
import { setPageTitle } from 'src/utils/set-page-title';
import { DEFAULT_CDN_URL, getFullBrandName } from '../../constants';
import { formatMessage } from '../../utils';
import createAmdLoader from './amd-loader';
import LoadingIndicator from './loading-indicator';
import ManifestLoader from './manifest';

export class UnknownModuleError extends Error {
    constructor(name: string) {
        super(`404: Unknown module "${name}"`);
    }
}

const manifest = new ManifestLoader(
    process.env.CDN_URL || DEFAULT_CDN_URL,
    process.env.DEPLOY_TIMESTAMP
);
const amdLoader = createAmdLoader(manifest);
window.amdLoader = amdLoader;

interface Props {
    module?: string;
    pageTitle?: string;
    brand?: string;
    className?: string;
}

interface State {
    failed: string | null;
}

export class ModuleLoaderComponent extends React.Component<Props, State> {
    context!: React.ContextType<typeof IdentityContext>;

    static contextType = IdentityContext;

    static configure({
        cdnUrl,
        deployTimestamp,
    }: {
        cdnUrl?: string;
        deployTimestamp?: string;
    }): void {
        if (cdnUrl) manifest.cdnUrl = cdnUrl;
        if (deployTimestamp) manifest.deployTimestamp = deployTimestamp;
    }

    static getDerivedStateFromProps(
        props: Props,
        { failed }: State
    ): Partial<State> | null {
        const { module } = props;

        if (module !== failed) return { failed: null };
        return null;
    }

    /* eslint-disable-next-line react/sort-comp */
    static manifest = manifest;

    constructor(props: Props) {
        super(props);

        this.state = { failed: null };
    }

    shouldComponentUpdate(
        nextProps: Props,
        nextState: State,
        nextContext: React.ContextType<typeof IdentityContext>
    ): boolean {
        const { failed } = this.state;
        const { identity } = this.context;
        const { module } = this.props;

        return (
            nextProps.module !== module ||
            nextState.failed !== failed ||
            nextContext.identity !== identity
        );
    }

    componentDidCatch(error: Error, info: React.ErrorInfo): void {
        const { module } = this.props;

        Sentry.withScope(scope => {
            scope.setExtras(info as Record<string, unknown>);
            Sentry.captureException(error);
        });

        this.setState({ failed: module ?? null });
    }

    render(): React.ReactNode {
        const { identity } = this.context;
        const { failed } = this.state;
        const { module, pageTitle, className, brand } = this.props;

        if (failed)
            return (
                <ErrorMessage
                    variant="full-page"
                    illustration="fatalError"
                    title={formatMessage('errors.default')}
                    className="w-auto"
                />
            );

        if (identity.isAnonymous() || !module) return null;

        const OnDemandModule = lazy(async () => await amdLoader(module));

        if (pageTitle) setPageTitle(pageTitle, getFullBrandName(brand));

        void Segment.trackPage();

        const moduleClassName = `module-${module} ${className || ''}`;

        return (
            <div className={moduleClassName}>
                <Suspense fallback={<LoadingIndicator />}>
                    <OnDemandModule />
                </Suspense>
            </div>
        );
    }
}
