import * as _ from 'lodash';
import { provide } from 'ioc';
import * as React from 'react';
import * as qs from 'query-string';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ApolloProvider } from 'react-apollo';
import * as UniversalRouter from 'universal-router';
import createBrowserHistory from 'history/createBrowserHistory';
import { IReactFeature, createStore, createApollo } from '@module-stack/core-client';

import { Application } from './Application';

const history = createBrowserHistory();
let current = history.location;

const normalize = (list: any[]) => (list || []).sort((s1, s2) => parseInt(s1.index || s1.root ? "9999" : "0") - parseInt(s2.index || s2.root ? "9999" : "0"));

export const tree = (list) => {
    const nested = (root, list) => {
        if(!root.id) return root;
        return _.assing({}, root, {
            children: (root.children || []).concat(list
                .filter(item => item.top === root.id)
                .map(child => nested(child, list)))
        });
    };

    return list.filter(item => !item.top).map(root => nested(root, list));
};

export async function start(module: IReactFeature) {
    // Build route tree, routes normalization
    const ordered = normalize(module.routes);
    const children = tree(_.cloneDeep(ordered));

    // Config application elements
    const store = createStore(module);
    const apollo = createApollo(module);

    const router = { children };

    // Create router after service creation
    const handler = new UniversalRouter(router, {
        resolveRoute: (context, params) => {
            if (typeof context.route.load === 'function') {
                return context.route
                    .load()
                    .then(action => action.default(context, params));
            }

            if (typeof context.route.action === 'function') {
                return context.route.action(context, params);
            }

            return undefined;
        },
    });

    async function resolve(location: any) {
        current = location;

        // Resolve route for path
        const route = await handler.resolve({
            ...module.context,
            history,
            pathname: current.pathname,
            query: qs.parse(location.search),
        });

        // Stop re-render
        if (current.key !== location.key) {
            return;
        }

        // Handle application redirects
        if (route.redirect) {
            history.replace(route.redirect);
            return;
        }

        return route;
    }

    async function listener(location, action?) {
        // Fetch root component
        const isInitialRender = !action;

        // Resolve route
        const route = await resolve(location) as any;
        const IOCContainer = provide(module)(Application);

        const CombinedApplication = props => (
            <ApolloProvider client={apollo}>
                <Provider store={store}>
                    <IOCContainer>{props.children}</IOCContainer>
                </Provider>
            </ApolloProvider>
        );

        return render((
            <CombinedApplication>
                {route.component}
            </CombinedApplication>
        ), isInitialRender);
    };

    // Subscribe for history updates
    history.listen(listener);

    // Initial renderer
    return await listener(location, false);
}

export function render(elements, isInitialRender = true) {
    const render = isInitialRender ? ReactDOM.render : ReactDOM.hydrate;
    console.log("elements: ", elements);
    return render(
        elements,
        document.getElementById("root"),
    );
}
