import { createLogger } from '../logger.mjs.js';

const logger = createLogger('modules/store/state-manager');

export const createStore = (reducer, initialState) => {
    logger.log(`Initializing state w/ `, { initialState });
    let localState = initialState;
    let dispatching = false;
    let isCorrupt = false;
    let actions = [];
    const handlers = {
        change: new Set()
    };

    const defaultGetState = function() {
        if (dispatching) {
            throw new MailmindrStateError(
                'Cannot get state while in dispatching mode'
            );
        }
        return localState;
    };

    const defaultDispatch = function(action) {
        if (dispatching) {
            throw new MailmindrStateError(
                'Cannot update state while state is in dispatching mode'
            );
        }
        try {
            dispatching = true;
            logger.log(`★ start reduce '${action.type}'`, {
                localState,
                action
            });
            actions.push({ name: action.type });
            localState = reducer(localState, action);
            if (!localState) {
                logger.error(`Action corrupted the state: ${action.type}`);
            }
            if (handlers.change.size) {
                for (let handler of handlers.change.values()) {
                    setTimeout(() => handler(localState), 0);
                }
            }
            logger.log(`★ end reduce '${action.type}'`, { localState });
            dispatching = false;
        } catch (error) {
            dispatching = false;
            isCorrupt = true;
            logger.error(
                `Something went wrong during dispatching the action '${action.type}'`,
                {
                    action,
                    actions,
                    error
                }
            );
        }

        return action;
    };

    const extendedDispatch = function(action) {
        if (action && action.constructor && action.constructor.name) {
            const constructorName = action.constructor.name.toLocaleLowerCase();
            switch (constructorName) {
                case 'promise':
                    logger.warn('Promise as action?', action);
                    return action;
                case 'asyncfunction':
                    return new Promise(async (success, failure) => {
                        try {
                            const result = await action(
                                extendedDispatch,
                                defaultGetState
                            );
                            actions.push({
                                name: `[async] ${action.name}`
                            });
                            success(result);
                        } catch (asyncFunctionError) {
                            failure(asyncFunctionError);
                        }
                    });
                case 'function':
                    try {
                        actions.push({ name: `[func] ${action.name}` });
                        return action(extendedDispatch, defaultGetState);
                    } catch (functionError) {
                        throw new MailmindrStateError(
                            `Error in function ${action.name}`,
                            functionError
                        );
                    }
                default:
                    return defaultDispatch(action);
            }
        }
    };

    let def = {
        dispatch: extendedDispatch,
        getState: defaultGetState,
        addEventListener: (eventName, eventHandler) => {
            const eventNameNormalized = eventName.toLocaleLowerCase();
            if (Object.keys(handlers).includes(eventNameNormalized)) {
                if (handlers[eventNameNormalized].has(eventHandler)) {
                    logger.warn(`Handler for ${eventName} already defined.`);
                } else {
                    handlers[eventNameNormalized].add(eventHandler);
                }
            } else {
                logger.error(
                    `No event handler for event '${eventNameNormalized}' exist.`
                );
            }
        },
        removeEventListener: (eventName, eventHandler) => {
            const eventNameNormalized = eventName.toLocaleLowerCase();
            if (Object.keys(handlers).includes(eventNameNormalized)) {
                if (handlers[eventNameNormalized].has(eventHandler)) {
                    handlers[eventNameNormalized].delete(eventHandler);
                } else {
                    logger.warn(`Handler for ${eventName} is not registered.`);
                }
            } else {
                logger.error(
                    `No event handler for event '${eventNameNormalized}' exist.`
                );
            }
        }
    };

    return def;
};

export class MailmindrStateError extends Error {
    constructor(...args) {
        super(...args);
    }
}
