import traverse from 'babel-traverse';

import { DefinitionsList } from './entryDefinitionsMap';
import { parseCodeToAST, buildVisitor } from './astBuilder';
import {
    ABSTRACTION_LEVELS,
    rebuildConfigForAbstractionLevel
} from './abstractionLevelsConfigurator';
import FlowTreeModifier from './FlowTreeModifier';
import {
    DEFINED_MODIFIERS,
    MODIFIER_PRESETS,
    destructionModifier,
    expressionCallbacksModifier,
    arrowFunctionReturnModifier
} from './modifiers/modifiersFactory';
import { TOKEN_TYPES } from 'shared/constants';
import { logError } from 'shared/utils/logger';

const buildFlowTree = (astTree, astVisitorConfig) => {
    const treeNodes = [];

    traverse(astTree, buildVisitor(astVisitorConfig, treeNodes));

    const root = (treeNodes.length && treeNodes[0]) || {};
    return root.type === TOKEN_TYPES.PROGRAM
        ? root
        : { name: 'Root', type: TOKEN_TYPES.PROGRAM, body: treeNodes };
};

//TODO: seems redundant abstraction, refactor
export const createFlowTreeModifier = () => {
    const modifiers = FlowTreeModifier();

    return {
        setModifier(modifier) {
            modifiers.addModifier(modifier);
        },

        registerNewModifier(test, updates) {
            modifiers.create(test, updates);
        },

        destructNodeTree(test, newNameFn) {
            this.setModifier(destructionModifier(test, newNameFn));
        },

        applyToFlowTree(flowTree) {
            modifiers.applyTo(flowTree);
            return flowTree;
        }
    };
};

export default ({ astParser = {}, astVisitor = {} } = {}) => {
    const astParserConfig = {
        ...astParser
    };

    const astVisitorConfig = {
        definitionsMap: [...DefinitionsList],
        globalIgnore: null,
        ...astVisitor
    };

    const defaultModifier = createFlowTreeModifier();
    defaultModifier.setModifier(expressionCallbacksModifier());
    defaultModifier.setModifier(arrowFunctionReturnModifier());

    return {
        setAbstractionLevel(level) {
            astVisitorConfig.definitionsMap = rebuildConfigForAbstractionLevel(level);
        },

        resetAbstractionLevelToNormal() {
            astVisitorConfig.definitionsMap = [...DefinitionsList];
        },

        setIgnoreFilter(fn) {
            astVisitorConfig.globalIgnore = fn;
        },

        build(code) {
            const ast = this.buildAst(code);
            return this.buildFlowTreeFromAst(ast);
        },

        buildAst(code) {
            return parseCodeToAST(code, astParserConfig);
        },

        buildFlowTreeFromAst(ast) {
            let flowTree = [];

            try {
                flowTree = buildFlowTree(ast, astVisitorConfig);
                defaultModifier.applyToFlowTree(flowTree);
            } catch (e) {
                logError('Error at buildFlowTreeFromAst' + e.message, e.stack);
                throw e;
            }

            return flowTree;
        }
    };
};

export { DEFINED_MODIFIERS, MODIFIER_PRESETS, ABSTRACTION_LEVELS };
