import type { FunctionComponent } from 'react';
import React, { useEffect, useRef, useState, useMemo } from 'react';
import type { AsyncPanelExtension } from '@atlassian/clientside-extensions';
import { onDebug } from '@atlassian/clientside-extensions-debug';
import type { Context } from '@atlassian/clientside-extensions-registry';
import type { PanelHandlerProps } from './PanelHandler';
import { PanelHandler } from './PanelHandler';

export type AsyncPanelRenderExtension = () => Promise<{ default: AsyncPanelExtension.AsyncPanelRenderExtension; __esModule: boolean }>;

export interface AsyncPanelHandlerProps {
    fallback?: JSX.Element | string;
    location: string;
    pluginKey: string;
    extensionKey?: string;
    renderProvider: AsyncPanelRenderExtension;
    contextProvider?: () => Context<{}>;
    RootType?: PanelHandlerProps['RootType'];
}

const noop = () => {};

const loadModule = async (
    moduleProvider: () => Promise<{ default: AsyncPanelExtension.AsyncPanelRenderExtension; __esModule: boolean }>,
    extensionKey: string,
    location: string,
): Promise<AsyncPanelExtension.AsyncPanelRenderExtension> => {
    try {
        const resolvedModule = await moduleProvider();
        // eslint-disable-next-line no-underscore-dangle
        if (!resolvedModule || !resolvedModule.__esModule || typeof resolvedModule.default !== 'function') {
            onDebug(({ error }) => ({
                level: error,

                message: `The provided dynamic import of extension "${extensionKey}" at location "${location}" does not look like a module that can be asynchronously loaded.`,
                components: ['AsyncPanelHandler'],
                meta: {
                    location,
                    extension: extensionKey,
                },
            }));

            return noop;
        }

        return resolvedModule.default;
    } catch (e) {
        onDebug(({ error }) => ({
            level: error,

            message: `Failed trying to execute the dynamic import from extension "${extensionKey}" for the async panel at location "${location}"`,
            components: ['AsyncPanelHandler'],
            meta: {
                location,
                extension: extensionKey,
                error: e,
            },
        }));

        return noop;
    }
};

export const AsyncPanelHandler: FunctionComponent<AsyncPanelHandlerProps> = ({
    contextProvider,
    fallback,
    location,
    pluginKey,
    extensionKey = pluginKey,
    renderProvider,
    RootType,
}) => {
    const rendererRef = useRef<AsyncPanelExtension.AsyncPanelRenderExtension>();
    const [isModuleLoaded, setIsModuleLoaded] = useState(false);

    const context = useMemo<Context<{}>>(() => {
        let contextValue: Context<{}> = {};

        if (typeof contextProvider === 'function') {
            try {
                contextValue = contextProvider();
            } catch (e) {
                onDebug(({ error }) => ({
                    level: error,

                    message: `Failed to invoke "contextProvider" function for extension "${extensionKey}" at location "${location}".\nError: ${e}`,
                    components: ['AsyncPanelHandler'],
                    meta: {
                        location,
                        extension: extensionKey,
                    },
                }));
            }
        }

        return contextValue;
    }, [contextProvider, location, extensionKey]);

    useEffect(
        function asyncLoadRendererModule() {
            let didCancel = false;

            async function loadRendererModule() {
                setIsModuleLoaded(false);
                rendererRef.current = undefined;

                const resolvedRenderer = await loadModule(renderProvider, extensionKey, location);

                if (didCancel) {
                    return;
                }

                rendererRef.current = resolvedRenderer;
                setIsModuleLoaded(true);
            }

            loadRendererModule();

            return () => {
                didCancel = true;
            };
        },
        [renderProvider, extensionKey, location],
    );

    if (!isModuleLoaded || !rendererRef.current) {
        return fallback ? <>{fallback}</> : null;
    }

    return <PanelHandler render={rendererRef.current} RootType={RootType} context={context} />;
};
