import { useMemo } from 'react';
import Debug from 'debug';

import type { AppReduxStore, State } from '~/src/store/createStore';
import type { SessionState } from '~/src/store/session/types';
import type { AppPageContext, NextApp } from '../types';

import { createStore } from '~/src/store/createStore';
import { Provider } from '~/src/store/redux';

const debug = Debug('songwhip/store/withReduxStore');
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__';

type ServerAppPageContext = AppPageContext &
  Required<Pick<AppPageContext, 'req' | 'query'>>;

export interface WithReduxStaticProps {
  getInitialReduxStateServer?: (
    params: ServerAppPageContext
  ) => Promise<{ session: Partial<SessionState> }>;
  onReduxInitClient?: ({ reduxStore }: { reduxStore: AppReduxStore }) => void;
}

const withRedux = (AppComponent: NextApp<{}, WithReduxStaticProps>) => {
  const AppWithRedux: NextApp<{ initialReduxState: State }> = ({
    initialReduxState,
    ...props
  }) => {
    const reduxStore = useMemo(() => getOrCreateStore(initialReduxState), []);

    return (
      <Provider store={reduxStore}>
        <AppComponent {...props} />
      </Provider>
    );
  };

  AppWithRedux.getInitialProps = async (appCtx) => {
    debug('get initial props');

    // initial redux state is only fetched on the server as
    // the client uses rehydrated server state
    const initialReduxState =
      typeof window === 'undefined'
        ? AppComponent.getInitialReduxStateServer &&
          (await AppComponent.getInitialReduxStateServer(
            appCtx.ctx as ServerAppPageContext
          ))
        : undefined;

    debug('got initial redux state', initialReduxState);

    // Get or Create the store with `undefined` as initialState
    // This allows you to set a custom default initialState
    const reduxStore = getOrCreateStore(initialReduxState);

    // Provide the store to getInitialProps of pages
    appCtx.ctx.reduxStore = reduxStore;

    // this calls the safe withErrorHandler() wrapped getInitialProps
    const appProps = await AppComponent.getInitialProps!(appCtx);

    return {
      ...appProps,
      initialReduxState: reduxStore.getState(),
    };
  };

  function getOrCreateStore(initialState) {
    // Always make a new store if server, otherwise state is shared between requests
    if (typeof window === 'undefined') return createStore(initialState);

    // Create store if unavailable on the client and set it on the window object
    if (!window[__NEXT_REDUX_STORE__]) {
      const reduxStore = createStore(initialState);
      window[__NEXT_REDUX_STORE__] = reduxStore;
      AppComponent.onReduxInitClient!({ reduxStore });
    }

    return window[__NEXT_REDUX_STORE__];
  }

  return AppWithRedux;
};

export default withRedux;
