"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createVForArrowFunctionExpression = exports.createForLoopParams = exports.parseForExpression = exports.parseVForScope = exports.transformFor = exports.isForElementNode = void 0;
const shared_1 = require("@vue/shared");
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
const compiler_core_1 = require("@vue/compiler-core");
const ast_1 = require("../ast");
const transform_1 = require("../transform");
const transformExpression_1 = require("./transformExpression");
const codegen_1 = require("../codegen");
const types_1 = require("@babel/types");
const utils_1 = require("./utils");
const runtimeHelpers_1 = require("../runtimeHelpers");
const vSlot_1 = require("./vSlot");
function isForElementNode(node) {
    return !!node.vFor;
}
exports.isForElementNode = isForElementNode;
exports.transformFor = (0, transform_1.createStructuralDirectiveTransform)('for', (node, dir, context) => {
    if (!dir.exp) {
        context.onError((0, compiler_core_1.createCompilerError)(31 /* ErrorCodes.X_V_FOR_NO_EXPRESSION */, dir.loc));
        return;
    }
    const parseResult = parseForExpression(dir.exp, context);
    if (!parseResult) {
        context.onError((0, compiler_core_1.createCompilerError)(32 /* ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION */, dir.loc));
        return;
    }
    parseResult.tagType = node.tagType;
    const { addIdentifiers, removeIdentifiers } = context;
    const { source, value, key, index } = parseResult;
    if (context.prefixIdentifiers) {
        addIdentifiers(value);
        addIdentifiers(key);
        addIdentifiers(index);
    }
    const { currentScope: parentScope, scopes, popScope } = context;
    const sourceExpr = (0, ast_1.parseExpr)(source, context);
    const valueCode = (0, codegen_1.genExpr)(value);
    const valueExpr = (0, ast_1.parseParam)(valueCode, context, value);
    const valueAlias = parseAlias(valueExpr, valueCode, 'v' + scopes.vFor);
    const keyCode = (0, codegen_1.genExpr)(key);
    const keyExpr = (0, ast_1.parseParam)(keyCode, context, key);
    const keyAlias = parseAlias(keyExpr, keyCode, 'k' + scopes.vFor);
    const indexCode = (0, codegen_1.genExpr)(index);
    const indexExpr = (0, ast_1.parseParam)(indexCode, context, index);
    const indexAlias = parseAlias(indexExpr, indexCode, 'i' + scopes.vFor);
    // 先占位 vFor，后续更新 cloneSourceExpr 为 CallExpression
    const cloneSourceExpr = (0, types_1.cloneNode)(sourceExpr, false);
    const sourceAliasReferencedScope = (0, utils_1.findReferencedScope)(cloneSourceExpr, context.currentScope, 
    // vFor 嵌套时，始终保持嵌套关系，issues/3263
    false);
    // 寻找子节点中 if 指令作用域
    const vIfReferencedScope = findVIfReferencedScope(node, context.currentScope, context);
    // 取最近的作用域
    const referencedScope = vIfReferencedScope &&
        context.getScopeIndex(vIfReferencedScope) >
            context.getScopeIndex(sourceAliasReferencedScope)
        ? vIfReferencedScope
        : sourceAliasReferencedScope;
    const sourceAlias = (0, utils_1.rewriteExpression)(source, context, cloneSourceExpr, parentScope, 
    // 强制 rewrite，因为即使是字符串，数字，也要走 vFor 函数
    {
        property: true,
        ignoreLiteral: true,
        referencedScope,
    }).content;
    const sourceCode = `{{${sourceAlias}}}`;
    const vForData = {
        source,
        sourceExpr,
        sourceAlias,
        sourceCode,
        value,
        valueCode,
        valueExpr,
        valueAlias,
        key,
        keyCode,
        keyExpr,
        keyAlias,
        index,
        indexCode,
        indexExpr,
        indexAlias,
        node,
    };
    const vForScope = context.addVForScope({
        ...vForData,
        locals: findVForLocals(parseResult),
    });
    const vFor = {
        ...vForData,
    };
    const isScopedSlot = (0, transform_1.isScopedSlotVFor)(vForScope);
    node.vFor = vFor;
    scopes.vFor++;
    return () => {
        scopes.vFor--;
        if ((0, compiler_core_1.isTemplateNode)(node)) {
            node.children.some((c) => {
                if (c.type === 1 /* NodeTypes.ELEMENT */ && !isForElementNode(c)) {
                    const key = (0, compiler_core_1.findProp)(c, 'key');
                    if (key) {
                        context.onError((0, compiler_core_1.createCompilerError)(33 /* ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT */, key.loc));
                        return true;
                    }
                }
            });
        }
        if (context.prefixIdentifiers) {
            value && removeIdentifiers(value);
            key && removeIdentifiers(key);
            index && removeIdentifiers(index);
        }
        (0, shared_1.extend)(clearExpr(cloneSourceExpr), isScopedSlot
            ? (0, vSlot_1.createVSlotCallExpression)(node
                .slotComponent, vForScope, context)
            : createVForCallExpression(vForScope, context));
        popScope();
    };
});
function clearExpr(expr) {
    Object.keys(expr).forEach((key) => {
        delete expr[key];
    });
    return expr;
}
function parseAlias(babelExpr, exprCode, fallback) {
    if ((0, types_1.isIdentifier)(babelExpr)) {
        return exprCode;
    }
    return fallback;
}
function parseVForScope(currentScope) {
    while (currentScope) {
        if ((0, transform_1.isVForScope)(currentScope) && !(0, transform_1.isScopedSlotVFor)(currentScope)) {
            return currentScope;
        }
        currentScope = currentScope.parent;
    }
}
exports.parseVForScope = parseVForScope;
function findVIfReferencedScope(node, currentScope, context) {
    if (!currentScope) {
        return;
    }
    const vForScope = parseVForScope(currentScope);
    if (!vForScope) {
        return;
    }
    if (!node.children.find((item) => checkVIfReferenced(item, vForScope, context))) {
        return findVIfReferencedScope(node, currentScope.parent, context);
    }
    return vForScope;
}
function checkVIfReferenced(node, vForScope, context) {
    if (!(0, uni_cli_shared_1.isElementNode)(node)) {
        return false;
    }
    // 嵌套 for 不查找
    if ((0, compiler_core_1.findDir)(node, 'for')) {
        return false;
    }
    const ifDir = (0, compiler_core_1.findDir)(node, 'if');
    if (ifDir) {
        return checkDirReferenced(ifDir.exp, vForScope, context);
    }
    const elseIfDir = (0, compiler_core_1.findDir)(node, 'else-if');
    if (elseIfDir) {
        return checkDirReferenced(elseIfDir.exp, vForScope, context);
    }
    return !!node.children.find((item) => checkVIfReferenced(item, vForScope, context));
}
function checkDirReferenced(node, vForScope, context) {
    if (node) {
        const babelNode = (0, ast_1.parseExpr)(node, context);
        if (babelNode && (0, utils_1.isReferencedByIds)(babelNode, vForScope.locals)) {
            return true;
        }
    }
    return false;
}
function findVForLocals({ value, key, index }) {
    const ids = [];
    if (value) {
        findIds(value, ids);
    }
    if (key) {
        findIds(key, ids);
    }
    if (index) {
        findIds(index, ids);
    }
    return ids;
}
function findIds(exp, ids) {
    if ((0, shared_1.isString)(exp)) {
        ids.push(exp);
    }
    else if (exp.identifiers) {
        exp.identifiers.forEach((id) => ids.push(id));
    }
    else if (exp.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */) {
        ids.push(exp.content);
    }
}
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/;
const stripParensRE = /^\(|\)$/g;
function parseForExpression(input, context) {
    const loc = input.loc;
    const exp = input.content;
    const inMatch = exp.match(forAliasRE);
    if (!inMatch)
        return;
    const [, LHS, RHS] = inMatch;
    const result = {
        source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),
        value: (0, compiler_core_1.createSimpleExpression)('v' + context.scopes.vFor),
        key: (0, compiler_core_1.createSimpleExpression)('k' + context.scopes.vFor),
        index: (0, compiler_core_1.createSimpleExpression)('i' + context.scopes.vFor),
        tagType: 0 /* ElementTypes.ELEMENT */,
    };
    if (context.prefixIdentifiers) {
        result.source = (0, transformExpression_1.processExpression)(result.source, context);
    }
    let valueContent = LHS.trim().replace(stripParensRE, '').trim();
    const trimmedOffset = LHS.indexOf(valueContent);
    const iteratorMatch = valueContent.match(forIteratorRE);
    if (iteratorMatch) {
        valueContent = valueContent.replace(forIteratorRE, '').trim();
        const keyContent = iteratorMatch[1].trim();
        let keyOffset;
        if (keyContent) {
            keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);
            result.key = createAliasExpression(loc, keyContent, keyOffset);
            if (context.prefixIdentifiers) {
                result.key = (0, transformExpression_1.processExpression)(result.key, context, true);
            }
        }
        if (iteratorMatch[2]) {
            const indexContent = iteratorMatch[2].trim();
            if (indexContent) {
                result.index = createAliasExpression(loc, indexContent, exp.indexOf(indexContent, result.key
                    ? keyOffset + keyContent.length
                    : trimmedOffset + valueContent.length));
                if (context.prefixIdentifiers) {
                    result.index = (0, transformExpression_1.processExpression)(result.index, context, true);
                }
            }
        }
    }
    if (valueContent) {
        result.value = createAliasExpression(loc, valueContent, trimmedOffset);
        if (context.prefixIdentifiers) {
            result.value = (0, transformExpression_1.processExpression)(result.value, context, true);
        }
    }
    return result;
}
exports.parseForExpression = parseForExpression;
function createAliasExpression(range, content, offset) {
    return (0, compiler_core_1.createSimpleExpression)(content, false, (0, compiler_core_1.getInnerRange)(range, offset, content.length));
}
function createForLoopParams({ value, key, index }, memoArgs = []) {
    return createParamsList([value, key, index, ...memoArgs]);
}
exports.createForLoopParams = createForLoopParams;
function createParamsList(args) {
    let i = args.length;
    while (i--) {
        if (args[i])
            break;
    }
    return args
        .slice(0, i + 1)
        .map((arg, i) => arg || (0, compiler_core_1.createSimpleExpression)(`_`.repeat(i + 1), false));
}
function createVForCallExpression(vForScope, context) {
    // let sourceExpr: Expression = vForScope.sourceExpr!
    // if (isNumericLiteral(sourceExpr)) {
    //   sourceExpr = numericLiteralToArrayExpr((sourceExpr as NumericLiteral).value)
    // }
    return (0, types_1.callExpression)((0, types_1.identifier)(context.helperString(runtimeHelpers_1.V_FOR)), [
        vForScope.sourceExpr,
        createVForArrowFunctionExpression(vForScope),
    ]);
}
function createVForArrowFunctionExpression({ valueExpr, keyExpr, indexExpr, properties, }) {
    const params = [];
    if (valueExpr) {
        params.push(valueExpr);
    }
    if (keyExpr) {
        params.push(keyExpr);
    }
    if (indexExpr) {
        params.push(indexExpr);
    }
    return (0, types_1.arrowFunctionExpression)(params, (0, types_1.blockStatement)([(0, types_1.returnStatement)((0, types_1.objectExpression)(properties))]));
}
exports.createVForArrowFunctionExpression = createVForArrowFunctionExpression;
