#include args.js
#include uniqstr.js

const MamiTabsControl = function(options) {
    options = MamiArguments.verify(options, [
        MamiArguments.type('onAdd', 'function', undefined, true),
        MamiArguments.type('onRemove', 'function', undefined, true),
        MamiArguments.type('onSwitch', 'function', undefined, true),
    ]);

    const onAdd = options.onAdd,
        onRemove = options.onRemove,
        onSwitch = options.onSwitch;

    const tabs = new Map;
    let currentTab;

    const extractElement = elementInfo => {
        let element;
        if(elementInfo instanceof Element) {
            element = elementInfo;
        } else if('getElement' in elementInfo) {
            element = elementInfo.getElement();
        } else throw 'elementInfo is not a valid type';

        if(!(element instanceof Element))
            throw 'element is not an instance of Element';

        return element;
    };

    const doTransition = async (transition, ctx) => {
        if(transition === undefined)
            return;

        if(typeof transition !== 'function')
            return;

        await transition(ctx);
    };

    const getTabInfoAndId = tabInfoOrId => {
        const result = {
            id: undefined,
            info: undefined,
        };

        const type = typeof tabInfoOrId;
        if(type === 'string') {
            result.id = tabInfoOrId;
            result.info = tabs.get(result.id);
        } else if(type === 'object') {
            result.info = tabInfoOrId;
            result.id = getExistingTabId(result.info);
        } else throw 'tabInfoOrId must be an object or a string';

        if(result.id === undefined || result.info === undefined)
            throw 'tab not found';

        return result;
    };

    const switchTab = async (tabInfoOrId, transition) => {
        const result = getTabInfoAndId(tabInfoOrId);

        const prevTab = currentTab;
        currentTab = result.info;

        const cbCtx = {
            id: result.id,
            info: result.info,
            elem: extractElement(result.info),
            from: undefined,
        };

        if(prevTab !== undefined) {
            const prev = getTabInfoAndId(prevTab);
            cbCtx.from = {
                id: prev.id,
                info: prev.info,
                elem: extractElement(prev.info),
            };
        }

        await onSwitch(cbCtx);

        if(typeof result.info.onTabForeground === 'function')
            await result.info.onTabForeground();

        if(prevTab !== undefined) {
            if(typeof prevTab.onTabBackground === 'function')
                await prevTab.onTabBackground();

            await doTransition(transition, cbCtx);
        }
    };

    const getExistingTabId = tabInfo => {
        if(tabInfo !== undefined)
            for(const [key, value] of tabs.entries())
                if(value === tabInfo)
                    return key;

        return undefined;
    };

    return {
        switch: switchTab,
        current: () => currentTab,
        currentId: () => getExistingTabId(currentTab),
        currentElement: () => {
            if(currentTab === undefined)
                return undefined;

            return extractElement(currentTab);
        },
        count: () => tabs.size,
        has: tabInfo => getExistingTabId(tabInfo) !== undefined,
        add: async (tabInfo, title) => {
            if(typeof tabInfo !== 'object')
                throw 'tabInfo must be an object';

            let tabId = getExistingTabId(tabInfo);
            if(tabId !== undefined)
                throw 'tabInfo has already been added';

            tabId = MamiUniqueStr(8);
            tabs.set(tabId, tabInfo);

            if(title !== 'string' && 'getTitle' in tabInfo)
                title = tabInfo.getTitle();

            const element = extractElement(tabInfo);

            await onAdd({
                id: tabId,
                info: tabInfo,
                elem: element,
                title: title,
                onClick: () => switchTab(tabInfo),
            });

            if(typeof tabInfo.onTabAdd === 'function')
                await tabInfo.onTabAdd();

            if(currentTab === undefined)
                await switchTab(tabInfo);

            return tabId;
        },
        remove: async tabInfoOrId => {
            const result = getTabInfoAndId(tabInfoOrId);

            if(!tabs.has(result.id))
                throw 'this tab does not exist';

            const element = extractElement(result.info);
            tabs.delete(result.id);

            await onRemove({
                id: result.id,
                info: result.info,
                elem: element,
            });

            if(typeof tabInfo.onTabRemove === 'function')
                await tabInfo.onTabRemove();

            if(currentTab === result.info)
                await switchTab(tabs.size > 0 ? tabs.keys().next().value : undefined);
        },
    };
};
