// @flow

import path from "path";

import * as React from "react";
import ReactDOMServer from "react-dom/server";
import { match, RouterContext } from "react-router";
import { createRouteFromReactElement } from "react-router/lib/RouteUtils";
import createURL from "@phenomic/api-client/lib/url";

import Provider from "../components/Provider";
import createStore from "../shared/store";
import performQuery from "../shared/performQuery";
import { encode, decode } from "../shared/QueryString";
import renderHTML from "../renderHTML";
import type { StoreType } from "../shared/store";

const debug = require("debug")("phenomic:plugin:renderer-react");

function getMatch({ routes, location }) {
  return new Promise((resolve, reject) => {
    match(
      { routes, location: `/${location}` },
      (error, redirectLocation, renderProps) => {
        if (error) reject(error);
        else resolve({ renderProps, redirectLocation });
      },
    );
  });
}

function staticRenderToString(
  config: PhenomicConfig,
  store: StoreType,
  { renderProps }: { renderProps: Object },
  renderHTML: typeof renderHTML,
  assets: PhenomicAssets,
) {
  return renderHTML(
    {
      WrappedApp: () => (
        <Provider store={store}>
          <RouterContext {...renderProps} />
        </Provider>
      ),
      renderAsObject: (UserWrappedApp: React.Node) => ({
        main: ReactDOMServer.renderToString(UserWrappedApp),
        state: store.getState(),
        assets,
      }),
    },
    config,
  );
}

const _renderStatic = async (
  config: PhenomicConfig,
  {
    app,
    assets,
    location,
  }: {|
    app: PhenomicAppType,
    assets: PhenomicAssets,
    location: string,
  |},
) => {
  debug(location, "server renderering");

  const routes = createRouteFromReactElement(app.routes);
  const store = createStore();
  const { renderProps, redirectLocation } = await getMatch({
    routes,
    location,
  });

  // debug(location, "renderProps", renderProps);

  // debug(location, "phenomic api store is going to be filled");
  const phenomicApiContainers = renderProps.components.filter(
    item => item && typeof item.getQueries === "function",
  );
  await Promise.all(
    phenomicApiContainers.map(item => {
      const queries = item.getQueries(renderProps);
      return performQuery(
        store,
        Object.keys(queries).map(key => encode(queries[key])),
      );
    }),
  );
  // debug(location, "phenomic api store has been prepared");

  const containers = renderProps.components.filter(
    item => item && typeof item.getInitialProps === "function",
  );
  // we should only have one
  if (containers.length > 1) {
    throw Error(
      "Only a single async container can be used on a given route (`static async getInitialProps`), found " +
        containers.length,
    );
  }
  await Promise.all(
    containers.map(async item => {
      renderProps.params.__initialPropsForSSR = await item.getInitialProps({
        pathname: renderProps.location.pathname,
        params: renderProps.params,
      });
    }),
  );

  const contents = await staticRenderToString(
    config,
    store,
    { renderProps, redirectLocation },
    renderHTML,
    assets,
  );
  const state = store.getState();
  // don't prepend index.html if location already have .html
  const filepath = location.match(/\.html$/)
    ? location
    : path.join(location, "index.html");
  return [
    { path: filepath, contents },
    ...Object.keys(state).map(key => ({
      path: createURL({ root: "phenomic", ...decode(key) }),
      contents: JSON.stringify(state[key].node),
    })),
  ];
};

const renderStatic = (config: PhenomicConfig) => (args: {|
  app: PhenomicAppType,
  assets: PhenomicAssets,
  location: string,
|}) => _renderStatic(config, args);

export default renderStatic;
