"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createStructuralDirectiveTransform = exports.createTransformContext = exports.traverseChildren = exports.traverseNode = exports.transform = exports.isScopedSlotVFor = exports.isVForScope = exports.isVIfScope = exports.isRootScope = void 0;
const shared_1 = require("@vue/shared");
const types_1 = require("@babel/types");
const compiler_core_1 = require("@vue/compiler-core");
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
const identifier_1 = __importDefault(require("./identifier"));
const runtimeHelpers_1 = require("./runtimeHelpers");
const ast_1 = require("./ast");
const utils_1 = require("./transforms/utils");
const codegen_1 = require("./codegen");
function isRootScope(scope) {
    return !isVIfScope(scope) && !isVForScope(scope);
}
exports.isRootScope = isRootScope;
function isVIfScope(scope) {
    return (!!scope.condition ||
        scope.name === 'else');
}
exports.isVIfScope = isVIfScope;
function isVForScope(scope) {
    return !!scope.source;
}
exports.isVForScope = isVForScope;
function isScopedSlotVFor({ source }) {
    if (source.type !== 8 /* NodeTypes.COMPOUND_EXPRESSION */) {
        return false;
    }
    const first = source.children[0];
    return (first.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ &&
        first.content.includes(utils_1.SCOPED_SLOT_IDENTIFIER));
}
exports.isScopedSlotVFor = isScopedSlotVFor;
function transform(root, options) {
    const context = createTransformContext(root, options);
    findRootNode(root, context);
    traverseNode(root, context);
    root.renderData = createRenderDataExpr(context.scope.properties, context);
    // finalize meta information
    root.helpers = new Set([...context.helpers.keys()]);
    root.components = [...context.components];
    root.imports = context.imports;
    root.cached = context.cached;
    return context;
}
exports.transform = transform;
function findRootNode(root, context) {
    const children = root.children.filter((node) => node.type === 1 /* NodeTypes.ELEMENT */ && node.tag !== 'template');
    if (children.length === 1) {
        context.rootNode = children[0];
    }
}
function traverseNode(node, context) {
    context.currentNode = node;
    // apply transform plugins
    const { nodeTransforms } = context;
    const exitFns = [];
    for (let i = 0; i < nodeTransforms.length; i++) {
        const onExit = nodeTransforms[i](node, context);
        if (onExit) {
            if ((0, shared_1.isArray)(onExit)) {
                exitFns.push(...onExit);
            }
            else {
                exitFns.push(onExit);
            }
        }
        if (!context.currentNode) {
            // node was removed
            return;
        }
        else {
            // node may have been replaced
            node = context.currentNode;
        }
    }
    switch (node.type) {
        case 3 /* NodeTypes.COMMENT */:
            // context.helper(CREATE_COMMENT)
            break;
        case 5 /* NodeTypes.INTERPOLATION */:
            context.helper(compiler_core_1.TO_DISPLAY_STRING);
            break;
        // for container types, further traverse downwards
        case 9 /* NodeTypes.IF */:
            for (let i = 0; i < node.branches.length; i++) {
                traverseNode(node.branches[i], context);
            }
            break;
        case 10 /* NodeTypes.IF_BRANCH */:
        case 11 /* NodeTypes.FOR */:
        case 1 /* NodeTypes.ELEMENT */:
        case 0 /* NodeTypes.ROOT */:
            traverseChildren(node, context);
            break;
    }
    // exit transforms
    context.currentNode = node;
    let i = exitFns.length;
    while (i--) {
        exitFns[i]();
    }
}
exports.traverseNode = traverseNode;
function traverseChildren(parent, context) {
    let i = 0;
    const nodeRemoved = () => {
        i--;
    };
    for (; i < parent.children.length; i++) {
        const child = parent.children[i];
        if ((0, shared_1.isString)(child))
            continue;
        context.parent = parent;
        context.childIndex = i;
        context.onNodeRemoved = nodeRemoved;
        traverseNode(child, context);
    }
}
exports.traverseChildren = traverseChildren;
function defaultOnError(error) {
    throw error;
}
function defaultOnWarn(msg) {
    console.warn(`[Vue warn] ${msg.message}`);
}
function createTransformContext(rootNode, { root = '', filename = '', isTS = false, inline = false, hashId = null, scopeId = null, filters = [], bindingCssVars = [], bindingMetadata = shared_1.EMPTY_OBJ, cacheHandlers = false, prefixIdentifiers = false, skipTransformIdentifier = false, renderDataSpread = false, nodeTransforms = [], directiveTransforms = {}, miniProgram = {
    class: {
        array: true,
    },
    slot: {
        fallbackContent: false,
        dynamicSlotNames: true,
    },
    directive: '',
}, isBuiltInComponent = shared_1.NOOP, isCustomElement = shared_1.NOOP, expressionPlugins = [], onError = defaultOnError, onWarn = defaultOnWarn, }) {
    const rootScope = {
        id: new identifier_1.default(),
        identifiers: [],
        properties: [],
        parent: null,
    };
    function findVIfParentScope() {
        for (let i = scopes.length - 1; i >= 0; i--) {
            const scope = scopes[i];
            if (isVForScope(scope) || isRootScope(scope)) {
                return scope;
            }
        }
        return rootScope;
    }
    function createScope(id, initScope) {
        return (0, shared_1.extend)({
            id,
            properties: [],
            parent: scopes[scopes.length - 1],
            get identifiers() {
                return Object.keys(identifiers);
            },
        }, initScope);
    }
    const vueIds = [];
    const identifiers = Object.create(null);
    const scopes = [rootScope];
    const miniProgramComponents = (0, uni_cli_shared_1.findMiniProgramUsingComponents)({
        filename,
        componentsDir: miniProgram.component?.dir,
        inputDir: root,
    });
    // const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
    const context = {
        // options
        // 暂不提供根据文件名生成递归组件
        selfName: '',
        miniProgram,
        isTS,
        inline,
        hashId,
        scopeId,
        filters,
        bindingCssVars,
        bindingMetadata,
        cacheHandlers,
        prefixIdentifiers,
        nodeTransforms,
        directiveTransforms,
        expressionPlugins,
        skipTransformIdentifier,
        renderDataSpread,
        isBuiltInComponent,
        isCustomElement,
        onError,
        onWarn,
        // state
        parent: null,
        childIndex: 0,
        helpers: new Map(),
        components: new Set(),
        imports: [],
        bindingComponents: Object.create(null),
        cached: 0,
        identifiers,
        scope: rootScope,
        scopes: {
            vFor: 0,
            vueId: 0,
        },
        get currentScope() {
            return scopes[scopes.length - 1];
        },
        currentNode: rootNode,
        vueIds,
        get currentVueId() {
            return vueIds[vueIds.length - 1];
        },
        inVOnce: false,
        get inVFor() {
            let parent = scopes[scopes.length - 1];
            while (parent) {
                if (isVForScope(parent) && !isScopedSlotVFor(parent)) {
                    return true;
                }
                parent = parent.parent;
            }
            return false;
        },
        // methods
        getScopeIndex(scope) {
            return scopes.indexOf(scope);
        },
        popScope() {
            return scopes.pop();
        },
        addVIfScope(initScope) {
            const vIfScope = createScope(scopes[scopes.length - 1].id, (0, shared_1.extend)(initScope, { parentScope: findVIfParentScope() }));
            scopes.push(vIfScope);
            return vIfScope;
        },
        addVForScope(initScope) {
            const vForScope = createScope(new identifier_1.default(), initScope);
            scopes.push(vForScope);
            return vForScope;
        },
        helper(name) {
            const count = context.helpers.get(name) || 0;
            context.helpers.set(name, count + 1);
            return name;
        },
        removeHelper(name) {
            const count = context.helpers.get(name);
            if (count) {
                const currentCount = count - 1;
                if (!currentCount) {
                    context.helpers.delete(name);
                }
                else {
                    context.helpers.set(name, currentCount);
                }
            }
        },
        helperString(name) {
            return `_${compiler_core_1.helperNameMap[context.helper(name)]}`;
        },
        replaceNode(node) {
            context.parent.children[context.childIndex] = context.currentNode = node;
        },
        removeNode(node) {
            if (!context.parent) {
                throw new Error(`Cannot remove root node.`);
            }
            const list = context.parent.children;
            const removalIndex = node
                ? list.indexOf(node)
                : context.currentNode
                    ? context.childIndex
                    : -1;
            /* istanbul ignore if */
            if (removalIndex < 0) {
                throw new Error(`node being removed is not a child of current parent`);
            }
            if (!node || node === context.currentNode) {
                // current node removed
                context.currentNode = null;
                context.onNodeRemoved();
            }
            else {
                // sibling node removed
                if (context.childIndex > removalIndex) {
                    context.childIndex--;
                    context.onNodeRemoved();
                }
            }
            context.parent.children.splice(removalIndex, 1);
        },
        onNodeRemoved: () => { },
        addIdentifiers(exp) {
            if ((0, shared_1.isString)(exp)) {
                addId(exp);
            }
            else if (exp.identifiers) {
                exp.identifiers.forEach(addId);
            }
            else if (exp.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */) {
                addId(exp.content);
            }
        },
        removeIdentifiers(exp) {
            if ((0, shared_1.isString)(exp)) {
                removeId(exp);
            }
            else if (exp.identifiers) {
                exp.identifiers.forEach(removeId);
            }
            else if (exp.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */) {
                removeId(exp.content);
            }
        },
        cache(exp, isVNode = false) {
            return createCacheExpression(context.cached++, exp, isVNode);
        },
        isMiniProgramComponent(name) {
            return miniProgramComponents[name];
        },
        rootNode: null,
    };
    function addId(id) {
        const { identifiers } = context;
        if (identifiers[id] === undefined) {
            identifiers[id] = 0;
        }
        identifiers[id]++;
    }
    function removeId(id) {
        context.identifiers[id]--;
    }
    return context;
}
exports.createTransformContext = createTransformContext;
function createCacheExpression(index, value, isVNode = false) {
    return {
        type: 20 /* NodeTypes.JS_CACHE_EXPRESSION */,
        index,
        value,
        isVNode,
        loc: compiler_core_1.locStub,
    };
}
function createStructuralDirectiveTransform(name, fn) {
    const matches = (0, shared_1.isString)(name)
        ? (n) => n === name
        : (n) => name.test(n);
    return (node, context) => {
        if (node.type === 1 /* NodeTypes.ELEMENT */) {
            const { props } = node;
            // structural directive transforms are not concerned with slots
            // as they are handled separately in vSlot.ts
            // if (node.tagType === ElementTypes.TEMPLATE && props.some(isVSlot)) {
            //   return
            // }
            const exitFns = [];
            for (let i = 0; i < props.length; i++) {
                const prop = props[i];
                if (prop.type === 7 /* NodeTypes.DIRECTIVE */ && matches(prop.name)) {
                    // structural directives are removed to avoid infinite recursion
                    // also we remove them *before* applying so that it can further
                    // traverse itself in case it moves the node around
                    props.splice(i, 1);
                    i--;
                    const onExit = fn(node, prop, context);
                    if (onExit)
                        exitFns.push(onExit);
                }
            }
            return exitFns;
        }
    };
}
exports.createStructuralDirectiveTransform = createStructuralDirectiveTransform;
function createRenderDataExpr(properties, context) {
    const objExpr = (0, ast_1.createObjectExpression)(properties);
    if (!hasSpreadElement(objExpr)) {
        return objExpr;
    }
    // filters: ['test']
    // v-if="text.aa()"
    if (context.filters.length) {
        transformFilterObjectSpreadExpr(objExpr, context);
    }
    if (context.renderDataSpread) {
        return objExpr;
    }
    return transformObjectSpreadExpr(objExpr, context);
}
function hasSpreadElement(expr) {
    return expr.properties.some((prop) => {
        if ((0, types_1.isSpreadElement)(prop)) {
            return true;
        }
        else {
            const returnStatement = parseReturnStatement(prop);
            if (returnStatement) {
                return hasSpreadElement(returnStatement.argument);
            }
        }
    });
}
// 目前硬编码识别 _f,应该读取 context.helperString
const returnObjExprMap = {
    _f: 1,
    _w: 0, // _w(()=>{return {}})
};
function parseReturnStatement(prop) {
    if ((0, types_1.isObjectProperty)(prop) &&
        (0, types_1.isCallExpression)(prop.value) &&
        (0, types_1.isIdentifier)(prop.value.callee)) {
        const { name } = prop.value.callee;
        if ((0, shared_1.hasOwn)(returnObjExprMap, name)) {
            return prop.value.arguments[returnObjExprMap[name]].body.body[0];
        }
    }
}
function transformObjectPropertyExpr(prop, context) {
    // vFor,withScopedSlot
    const returnStatement = parseReturnStatement(prop);
    if (returnStatement) {
        const objExpr = returnStatement.argument;
        if (hasSpreadElement(objExpr)) {
            returnStatement.argument = transformObjectSpreadExpr(objExpr, context);
        }
    }
    return prop;
}
function transformObjectSpreadExpr(objExpr, context) {
    const properties = objExpr.properties;
    const args = [];
    let objExprProperties = [];
    properties.forEach((prop) => {
        if ((0, types_1.isObjectProperty)(prop)) {
            objExprProperties.push(transformObjectPropertyExpr(prop, context));
        }
        else {
            if (objExprProperties.length) {
                args.push((0, types_1.objectExpression)(objExprProperties));
            }
            args.push(transformConditionalExpression(prop.argument, context));
            objExprProperties = [];
        }
    });
    if (objExprProperties.length) {
        args.push((0, types_1.objectExpression)(objExprProperties));
    }
    if (args.length === 1) {
        return args[0];
    }
    return (0, types_1.callExpression)((0, types_1.identifier)(context.helperString(runtimeHelpers_1.EXTEND)), args);
}
function transformConditionalExpression(expr, context) {
    const { consequent, alternate } = expr;
    if ((0, types_1.isObjectExpression)(consequent) && hasSpreadElement(consequent)) {
        expr.consequent = transformObjectSpreadExpr(consequent, context);
    }
    if ((0, types_1.isObjectExpression)(alternate)) {
        if (hasSpreadElement(alternate)) {
            expr.alternate = transformObjectSpreadExpr(alternate, context);
        }
    }
    else if ((0, types_1.isConditionalExpression)(alternate)) {
        transformConditionalExpression(alternate, context);
    }
    return expr;
}
function transformFilterObjectSpreadExpr(objExpr, context) {
    const properties = objExpr.properties;
    properties.forEach((prop) => {
        if ((0, types_1.isObjectProperty)(prop)) {
            transformFilterObjectPropertyExpr(prop, context);
        }
        else {
            prop.argument = transformFilterConditionalExpression(prop.argument, context);
        }
    });
}
function transformFilterObjectPropertyExpr(prop, context) {
    // vFor, withScopedSlot
    const returnStatement = parseReturnStatement(prop);
    if (returnStatement) {
        const objExpr = returnStatement.argument;
        if (hasSpreadElement(objExpr)) {
            transformFilterObjectSpreadExpr(objExpr, context);
        }
    }
}
function transformFilterConditionalExpression(expr, context) {
    const { test, consequent, alternate } = expr;
    if ((0, types_1.isObjectExpression)(consequent) && hasSpreadElement(consequent)) {
        transformFilterObjectSpreadExpr(consequent, context);
    }
    if ((0, types_1.isObjectExpression)(alternate)) {
        if (hasSpreadElement(alternate)) {
            transformFilterObjectSpreadExpr(alternate, context);
        }
    }
    else if ((0, types_1.isConditionalExpression)(alternate)) {
        expr.alternate = transformFilterConditionalExpression(alternate, context);
    }
    const testCode = (0, codegen_1.genBabelExpr)(test);
    // filter test
    if (context.filters.find((filter) => testCode.includes(filter + '.'))) {
        // test.aa() ? {a:1} : {b:2} => {...{a:1},...{b:2}}
        const properties = [];
        if (!(0, types_1.isObjectExpression)(consequent) || consequent.properties.length) {
            properties.push((0, types_1.spreadElement)(consequent));
        }
        if (!(0, types_1.isObjectExpression)(expr.alternate) ||
            expr.alternate.properties.length) {
            properties.push((0, types_1.spreadElement)(expr.alternate));
        }
        return (0, types_1.objectExpression)(properties);
    }
    return expr;
}
