/* eslint-disable react/prop-types */
import React, { useCallback, useEffect, useMemo, useState, } from "react";
import GlobalAppState from "./GlobalAppState";
import { GlobalAppStateContextProvider } from "./GlobalAppStateContext";
import cookieConsent from "../cookieConsent";
/**
 * Function that returns a Next.js custom `App` component.
 * Takes an optional argument where you can specify
 * options to customize the output of the factory.
 * @param options An optional argument where you can specify options to customize the output of the factory
 * @return A Next.js custom `App` component
 */
function appFactory(options) {
    const { Head, Wrapper = ({ children }) => (<>{children}</>), properties = [], } = options || {};
    const globalAppState = new GlobalAppState([...properties, cookieConsent]);
    const keysForURLParamListeningProperties = globalAppState.getKeysForURLParamListeningProperties();
    const [contextKeys, contextProviders,] = globalAppState.getContextKeysAndProviders();
    const ContextProviders = function ContextProviders({ globalAppState, contextValues, children, }) {
        return contextProviders.reduce((accumulator, ContextProvider, index) => (<ContextProvider value={contextValues[index]}>
            {accumulator}
          </ContextProvider>), <GlobalAppStateContextProvider globalAppState={globalAppState}>
          <Wrapper>{children}</Wrapper>
        </GlobalAppStateContextProvider>);
    };
    const App = function App({ Component, ...props }) {
        const [state, setState] = useState(() => globalAppState.initializeStateClientSidePhase1(props.dehydratedState));
        const mergeState = useCallback((deltaState) => {
            if (deltaState) {
                setState((prevState) => ({
                    ...prevState,
                    ...deltaState,
                    globalAppState: {
                        ...prevState.globalAppState,
                        ...deltaState.globalAppState,
                    },
                }));
            }
        }, [setState]);
        useEffect(() => {
            globalAppState.initializeStateClientSidePhase2(state).then(mergeState);
        }, []);
        const urlParams = keysForURLParamListeningProperties.map((key) => props.urlParams[key]);
        useEffect(() => {
            if (state._mounted) {
                const justReady = urlParams.every((key) => typeof key === "undefined");
                globalAppState
                    .onURLParamCallback(state, props.urlParams, justReady)
                    .then(mergeState);
            }
        }, [...urlParams, state._mounted]);
        const setterDependencies = globalAppState.propertyKeysPlural.map((key) => state.globalAppState[key]);
        const setters = useMemo(() => {
            if (state._mounted) {
                const setters = {};
                Object.entries(globalAppState.getSetters()).forEach(([key, setter]) => {
                    setters[key] = (value) => {
                        setter(setterDependencies[globalAppState.setterNames.indexOf(key)], state.globalAppState.cookieConsent, value)
                            .then((contextValue) => {
                            setState((prevState) => ({
                                ...prevState,
                                [globalAppState.propertyKeys[globalAppState.setterNames.indexOf(key)]]: contextValue,
                                globalAppState: {
                                    ...prevState.globalAppState,
                                    [globalAppState.propertyKeys[globalAppState.setterNames.indexOf(key)]]: value,
                                },
                            }));
                        })
                            .catch((error) => {
                            console.error(error.stack);
                        });
                    };
                });
                return setters;
            }
            else {
                return {};
            }
        }, [
            ...setterDependencies,
            state.globalAppState.cookieConsent,
            state._mounted,
        ]);
        return (<>
        {Head}
        <ContextProviders globalAppState={{
            ...state.globalAppState,
            ...setters,
            mounted: state._mounted,
            ready: state._ready,
        }} contextValues={contextKeys.map((key) => state[key])}>
          <Component {...props.pageProps}/>
        </ContextProviders>
      </>);
    };
    App.getInitialProps = async ({ Component, ctx, }) => {
        let pageProps = {};
        if (Component.getInitialProps) {
            pageProps = { ...pageProps, ...(await Component.getInitialProps(ctx)) };
        }
        let dehydratedState = {};
        if (ctx.req) {
            dehydratedState = await App.getInitialState(ctx.req);
        }
        const urlParams = await App.getURLParams(ctx.query);
        return { pageProps, dehydratedState, urlParams };
    };
    App.getInitialState = async (req) => {
        return await globalAppState.initializeStateServerSide(req);
    };
    App.getURLParams = async (query) => {
        const urlParams = {};
        globalAppState.propertyKeys.forEach((key) => {
            urlParams[key] = Array.isArray(query[key]) ?
                query[key][query[key].length - 1] :
                query[key];
        });
        return urlParams;
    };
    return App;
}
export default appFactory;
//# sourceMappingURL=index.jsx.map