import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as ReactDOMServer from 'react-dom/server';
import deepmerge from 'deepmerge';
import RuntimeModule from './runtimeModule';
import { ErrorBoundary } from './components';
import { setAppConfig } from './appConfig';
import { createHistory } from './history';
import { IAppConfig } from './types';
<% if (globalStyle) {%>import '../<%= globalStyle %>'<% } %>

export interface IContext {
  initialData?: any;
  pageInitialProps?: any;
  pathname?: string;
}

const defaultAppConfig = {
  app: {
    rootId: 'ice-container'
  },
  router: {
    type: 'hash'
  }
}

function createAppWithSSR(customConfig: IAppConfig, context: IContext) {
  const appConfig = deepmerge(defaultAppConfig, customConfig);
  appConfig.router.type = 'static';
  return renderApp(appConfig, context);
}

function createApp(customConfig: IAppConfig) {
  const appConfig = deepmerge(defaultAppConfig, customConfig);

  // set appConfig to application life cycle
  setAppConfig(appConfig);

  if (process.env.__IS_SERVER__) {
    return;
  }

  // client side rendering
  // get history instance
  const { type, basename = '', history: customHistory } = appConfig.router;
  const history = customHistory || createHistory(type, basename);
  appConfig.router.history = history;

  // load module to run before createApp ready
  loadStaticModules(appConfig);

  let initialData = {};
  let pageInitialProps = {};

  // ssr enabled and the server has returned data
  if (window.__ICE_APP_DATA__) {
    initialData = window.__ICE_APP_DATA__;
    pageInitialProps = window.__ICE_PAGE_PROPS__;
    renderApp(appConfig, { initialData, pageInitialProps });
  } else {
    // ssr not enabled, or SSR is enabled but the server does not return data
    if (appConfig.app.getInitialData) {
      (async() => {
        // @ts-ignore
        initialData = await appConfig.app.getInitialData();
        renderApp(appConfig, { initialData, pageInitialProps });
      })();
    } else {
      renderApp(appConfig);
    }
  }
}

function renderApp(config: IAppConfig, context?: IContext) {
  const runtime = new RuntimeModule(config, <%- buildConfig %>, context)
  loadModlues(runtime);
  const { appConfig, modifyDOMRender } = runtime
  const { rootId, mountNode, ErrorBoundaryFallback, onErrorBoundaryHander, errorBoundary } = appConfig.app
  const AppProvider = runtime.composeAppProvider();
  const AppRouter = runtime.getAppRouter();

  function App() {
    const appRouter = <AppRouter />;
    const rootApp = AppProvider ? <AppProvider>{appRouter}</AppProvider> : appRouter;
    if (errorBoundary) {
      return (
        <ErrorBoundary Fallback={ErrorBoundaryFallback} onError={onErrorBoundaryHander}>
          {rootApp}
        </ErrorBoundary>
      )
    }

    return rootApp;
  }

  if (process.env.__IS_SERVER__) {
    return ReactDOMServer.renderToString(<App />);
  } else {
    const appMountNode = mountNode || document.getElementById(rootId);
    if (modifyDOMRender) {
      return modifyDOMRender({ App, appMountNode });
    } else {
      return ReactDOM[window.__ICE_SSR_ENABLED__ ? 'hydrate' : 'render'](<App />, appMountNode);
    }
  }
}

function loadModlues(runtime) {
  <% if (runtimeModules.length) {%>
    <% runtimeModules.forEach((runtimeModule) => { %>
      <% if(!runtimeModule.staticModule){ %>
        runtime.loadModlue(require('<%= runtimeModule.path %>'));
      <% } %>
    <% }) %>
  <% } %>
}

function loadStaticModules(appConfig: IAppConfig) {
  <% if (runtimeModules.length) {%>
    <% runtimeModules.forEach((runtimeModule) => { %>
      <% if(runtimeModule.staticModule){ %>
        require('<%= runtimeModule.path %>').default({appConfig});
      <% } %>
    <% }) %>
  <% } %>
}

export {
  createApp,
  createAppWithSSR,
  loadStaticModules
}
