export interface IHookzListener {
    oldState: any;
    run?: (state: any) => void;
}

export interface IHookzStore {
    state: any;
    actions?: any;
    listeners: IHookzListener[];
    setState?: (newState: any, afterUpdateCallback: any) => void;
}

function setState(store: IHookzStore, newState: any, afterUpdateCallback: any) {
    store.state = { ...store.state, ...newState };
    store.listeners.forEach((listener: IHookzListener) => {
        if (listener.run) {
            listener.run(store.state);
        }
    });
    if (afterUpdateCallback) {
        afterUpdateCallback();
    }
}
function useCustom(
    store: IHookzStore,
    React: any,
    mapState: any,
    mapActions: any,
) {
    const [, originalHook] = React.useState(Object.create(null));
    const state = mapState ? mapState(store.state) : store.state;
    const actions = React.useMemo(
        () => (mapActions ? mapActions(store.actions) : store.actions),
        [mapActions, store.actions],
    );

    React.useEffect(() => {
        const newListener: IHookzListener = { oldState: {} };
        newListener.run = mapState
            ? (newState: any) => {
                const mappedState = mapState(newState);
                if (mappedState !== newListener.oldState) {
                    newListener.oldState = mappedState;
                    originalHook(mappedState);
                }
            }
            : originalHook;
        store.listeners.push(newListener);
        if (newListener.run) {
            newListener.run(store.state);
        }
        return () => {
            store.listeners = store.listeners.filter(
                (listener) => (listener) !== newListener,
            );
        };
    }, []); // eslint-disable-line
    return [state, actions];
}

function associateActions({ store, actions }: { store: any; actions: any }) {
    const associatedActions = {};
    Object.keys(actions).forEach((key) => {
        if (typeof actions[key] === "function") {
            // @ts-ignore
            associatedActions[key] = actions[key].bind(null, store);
        }
        if (typeof actions[key] === "object") {
            // @ts-ignore
            associatedActions[key] = associateActions({
                actions: actions[key],
                store,
            });
        }
    });
    return associatedActions;
}

/**
 * React Global Hookz, a simple global state for React with the Hooks API in less than 1kb written in TypeScript
 * @param React The imported namespace of your installed React version
 * @param initialState Any state object you'd like to use
 * @param actions An object of functions setting the state
 * @param initializer Optional initialization function
 * @see https://github.com/GaryWenneker/react-hookz
 */
const useHookz = (
    React: any,
    initialState: any,
    actions: any,
    initializer?: any,
) => {
    const store: IHookzStore = { state: initialState, listeners: [] };
    store.setState = setState.bind(null, store);
    store.actions = associateActions({ store, actions });
    if (initializer) {
        initializer(store);
    }
    return useCustom.bind(null, store, React);
};

export default useHookz;
