import { contentModeCookieName, isSiteInShellCookieName } from "@insite/client-framework/Common/ContentMode";
import { getCookie, setCookie } from "@insite/client-framework/Common/Cookies";
import { getValueCaseInsensitive, SafeDictionary } from "@insite/client-framework/Common/Types";
import { checkIsWebCrawler, setIsWebCrawler } from "@insite/client-framework/Common/WebCrawler";
import {
    FoundModule,
    registerAsyncWidgetFieldDefinitions,
    registerPageModule,
    registerWidgetModule,
} from "@insite/client-framework/Components/ContentItemStore";
import importChunk from "@insite/client-framework/Components/importChunk";
import { ShellContext } from "@insite/client-framework/Components/IsInShell";
import Maintenance from "@insite/client-framework/Components/Maintenance";
import PreviewLogin from "@insite/client-framework/Components/PreviewLogin";
import SessionLoader from "@insite/client-framework/Components/SessionLoader";
import SpireRouter from "@insite/client-framework/Components/SpireRouter";
import logger from "@insite/client-framework/Logger";
import { OdpEvent, sendEvent } from "@insite/client-framework/OdpEvents/OdpEvent";
import { getContentByVersionPath, getTheme } from "@insite/client-framework/Services/ContentService";
import { setResolver } from "@insite/client-framework/SiteMessage";
import ApplicationState from "@insite/client-framework/Store/ApplicationState";
import { configureStore } from "@insite/client-framework/Store/ConfigureStore";
import { getSettingsCollection } from "@insite/client-framework/Store/Context/ContextSelectors";
import { getCurrentPage } from "@insite/client-framework/Store/Data/Pages/PageSelectors";
import { getWidgetsByPageId } from "@insite/client-framework/Store/Data/Widgets/WidgetSelectors";
import { theme as defaultTheme } from "@insite/client-framework/Theme";
import { postStyleGuideTheme, preStyleGuideTheme } from "@insite/client-framework/ThemeConfiguration";
import translate, { setTranslationResolver } from "@insite/client-framework/Translate";
import FieldDefinition from "@insite/client-framework/Types/FieldDefinition";
import PageModule from "@insite/client-framework/Types/PageModule";
import WidgetModule from "@insite/client-framework/Types/WidgetModule";
import { BaseTheme } from "@insite/mobius/globals/baseTheme";
import ThemeProvider from "@insite/mobius/ThemeProvider";
import merge from "lodash/merge";
import * as React from "react";
import { createRoot, hydrateRoot, Root } from "react-dom/client";
import { Provider } from "react-redux";

/**
 * To debug potentially unnecessary rendering, import "@welldone-software/why-did-you-render": "^4.2.5" in
 * client-framework and frontend. Then attach why-did-you-render to your component as follows:
 *    * class component, inside the component add `static whyDidYouRender = true;`
 *    * functional component, after the component add `ComponentName.whyDidYouRender = true;`
 */
// if (process.env.NODE_ENV !== "production") {
//     // eslint-disable-next-line @typescript-eslint/no-var-requires
//     const whyDidYouRender = require("@welldone-software/why-did-you-render");
//     whyDidYouRender(React, { exclude: [/^ConnectFunction/], trackHooks: true });
// }

type customWindow = {
    siteMessages: SafeDictionary<string>;
    translationDictionaries: SafeDictionary<string>;
    initialReduxState: ApplicationState;
    odpEvents: OdpEvent[];
    asyncWidgetFieldDefinitions?: SafeDictionary<FieldDefinition[]>;
};

const theWindow = window as any as customWindow;
setResolver(messageName => getValueCaseInsensitive(theWindow.siteMessages, messageName));
setTranslationResolver(keyword => getValueCaseInsensitive(theWindow.translationDictionaries, keyword));

// Get the application-wide store instance, prepopulating with state from the server where available.
const initialState = theWindow.initialReduxState;
const store = configureStore(initialState);
if (theWindow.asyncWidgetFieldDefinitions) {
    for (const key of Object.keys(theWindow.asyncWidgetFieldDefinitions)) {
        if (theWindow.asyncWidgetFieldDefinitions[key]) {
            registerAsyncWidgetFieldDefinitions(theWindow.asyncWidgetFieldDefinitions[key], key);
        }
    }
    theWindow.asyncWidgetFieldDefinitions = undefined;
}

const container = document.getElementById("react-app") as Element;

for (const odpEvent of theWindow.odpEvents) {
    sendEvent(odpEvent, initialState);
}

async function renderApp(root: Root = createRoot(container)) {
    await preloadCurrentPageAndWidgetsIfNeeded(initialState);

    let WrappingContext: React.FC<React.PropsWithChildren<{}>> = ({ children }) => <>{children}</>;
    let isInShell = false;
    try {
        isInShell =
            !!window && window.parent && window.parent.location.toString().toLowerCase().indexOf("/contentadmin/") > 0;
    } catch (error) {
        // ignore this, it means we can't access the parent which means we aren't in CMS
    }

    if (isInShell) {
        logger.debug("CMS shell detected.");
        if (window.location.pathname.startsWith(getContentByVersionPath)) {
            logger.debug("Adding shell wrapper for GetContentByVersion request, but excluding isEdit");

            const PreviewWrappingContext: React.FC<React.PropsWithChildren> = ({ children }) => (
                <ShellContext.Provider value={{ isCurrentPage: true, isInShell: true }}>
                    {children}
                </ShellContext.Provider>
            );

            WrappingContext = PreviewWrappingContext;
        } else {
            setCookie(isSiteInShellCookieName, "true");
            const isEditing = getCookie(contentModeCookieName) === "Editing";

            const ShellWrappingContext: React.FC<React.PropsWithChildren> = ({ children }) => (
                <ShellContext.Provider value={{ isEditing, isCurrentPage: true, isInShell: true }}>
                    {children}
                </ShellContext.Provider>
            );

            WrappingContext = ShellWrappingContext;
        }
    } else if (getCookie(isSiteInShellCookieName)) {
        if (window.location.pathname === "/") {
            // TODO ISC-12274 get rid of this to see if the problem exists
            window.location.href = "/ContentAdmin/Page";
        } else {
            window.location.href = `/ContentAdmin/Page/SwitchTo${window.location.pathname}${window.location.search}`;
        }
        root.render(<></>);
    } else {
        setIsWebCrawler(checkIsWebCrawler(navigator.userAgent));
    }

    const { pathname, search, hash } = window.location;
    const theme = await fetchThenMergeTheme();
    // This code starts up the React app when it runs in a browser. It sets up the routing configuration
    // and injects the app into a DOM element.
    // Changes here must be mirrored to the storefront section in Server.tsx so the render output matches.
    root.render(
        <Provider store={store}>
            <WrappingContext>
                <ThemeProvider theme={theme} createGlobalStyle={true} createChildGlobals={false} translate={translate}>
                    <SessionLoader location={{ pathname, search, hash }}>
                        <Maintenance>
                            <PreviewLogin>
                                <SpireRouter />
                            </PreviewLogin>
                        </Maintenance>
                    </SessionLoader>
                </ThemeProvider>
            </WrappingContext>
        </Provider>,
    );
}

renderApp(initialState ? hydrateRoot(container, <></>) : createRoot(container));

/**
 * The theme object must be put together on the frontend because running JSON.stringify() serverside was ignoring components passed to iconSrcByMessage,
 * which is a property found on the Toast component.
 * This function's returned theme object replaces the intialTheme, which was serverside and later accessed on the global window object on the browser
 */
async function fetchThenMergeTheme(): Promise<BaseTheme> {
    try {
        return merge({}, defaultTheme, preStyleGuideTheme, await getTheme(), postStyleGuideTheme);
    } catch (e) {
        logger.error(e);
    }
    return defaultTheme;
}

/** Preload current page and page widgets before rendering to prevent flashing after SSR */
async function preloadCurrentPageAndWidgetsIfNeeded(state: ApplicationState): Promise<void> {
    if (!state) {
        return;
    }

    const { enableServerSideRendering } = getSettingsCollection(state).websiteSettings;
    if (!enableServerSideRendering) {
        return;
    }

    const page = getCurrentPage(state);
    const importPageFn = importChunk[page.type];
    if (importPageFn) {
        const pageModule = (await importPageFn()) as FoundModule<PageModule>;
        registerPageModule(pageModule, page.type);
    }

    const widgetIds = getWidgetsByPageId(state, page.id);
    for (let i = 0; i < widgetIds.length; i++) {
        const widgetId = widgetIds[i].id;
        const widget = state.data.pages.widgetsById[widgetId];
        const importWidgetFn = importChunk[widget.type];
        if (importWidgetFn) {
            const widgetModule = (await importWidgetFn()) as FoundModule<WidgetModule>;
            registerWidgetModule(widgetModule, widget.type);
        }
    }
}
