import * as React from "react";

import { AppState } from "@client/state";

const StateContext = React.createContext<AppState | null>(null);

export interface ProvideStateProps {
    state: AppState;
    children: React.ReactNode;
}

export function ProvideState({ state, children }: ProvideStateProps) {
    return (
        <StateContext.Provider value={state}>
            {children}
        </StateContext.Provider>
    );
}

export interface ConsumeStateProps {
    children: (state: AppState) => React.ReactNode;
}

export function ConsumeState({ children }: ConsumeStateProps) {
    const consumeState = (state: AppState | null) => {
        if (state == null) {
            throw new Error("Component with ConsumeState must be mounted inside ProvideState");
        }
        return children(state);
    };
    return <StateContext.Consumer>{consumeState}</StateContext.Consumer>;
}

type Diff<T extends string | number | symbol, U extends string | number | symbol> =
    ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};

export function injectState<P extends { appState: AppState }>(Component: React.ComponentType<P>):
    React.ComponentClass<Omit<P, "appState">> {
    return class extends React.Component<Omit<P, "appState">> {
        render() {
            const consumeState = (state: AppState | null) => {
                if (state == null) {
                    throw new Error("Component with injectState must be mounted inside ProvideState");
                }
                return <Component {...this.props} appState={state}/>;
            };
            return <StateContext.Consumer>{consumeState}</StateContext.Consumer>;
        }
    };
}