"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.genExpr = exports.genBabelExpr = exports.generate = void 0;
const shared_1 = require("@vue/shared");
const compiler_core_1 = require("@vue/compiler-core");
const generator_1 = __importDefault(require("@babel/generator"));
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
function generate(ast, options) {
    const context = createCodegenContext(ast, options);
    const { mode, push, indent, deindent, newline, prefixIdentifiers } = context;
    const helpers = Array.from(ast.helpers);
    const hasHelpers = helpers.length > 0;
    const useWithBlock = !prefixIdentifiers && mode !== 'module';
    const isSetupInlined = !!options.inline;
    // preambles
    // in setup() inline mode, the preamble is generated in a sub context
    // and returned separately.
    const preambleContext = isSetupInlined
        ? createCodegenContext(ast, options)
        : context;
    if (mode === 'module') {
        genModulePreamble(ast, preambleContext, isSetupInlined);
    }
    else {
        genFunctionPreamble(ast, preambleContext);
    }
    // enter render function
    const functionName = `render`;
    const args = ['_ctx', '_cache'];
    if (options.bindingMetadata && !options.inline) {
        // binding optimization args
        args.push('$props', '$setup', '$data', '$options');
    }
    const signature = options.isTS
        ? args.map((arg) => `${arg}: any`).join(',')
        : args.join(', ');
    if (isSetupInlined) {
        push(`(${signature}) => {`);
    }
    else {
        push(`function ${functionName}(${signature}) {`);
    }
    indent();
    if (useWithBlock) {
        push(`with (_ctx) {`);
        indent();
        if (hasHelpers) {
            push(`const { ${helpers
                .map((s) => `${compiler_core_1.helperNameMap[s]}: _${compiler_core_1.helperNameMap[s]}`)
                .join(', ')} } = _Vue`);
            push(`\n`);
            newline();
        }
    }
    push(`return `);
    push(genBabelExpr(ast.renderData, options.generatorOpts));
    if (useWithBlock) {
        deindent();
        push(`}`);
    }
    deindent();
    push(`}`);
    return {
        code: context.code,
        preamble: isSetupInlined ? preambleContext.code : ``,
        // SourceMapGenerator does have toJSON() method but it's not in the types
        map: context.map ? context.map.toJSON() : undefined,
    };
}
exports.generate = generate;
function createCodegenContext(ast, { mode = 'function', prefixIdentifiers = mode === 'module', filename = `template.vue.html`, scopeId = null, runtimeGlobalName = `Vue`, runtimeModuleName = `vue`, isTS = false, sourceMap = false, }) {
    const context = {
        mode,
        prefixIdentifiers,
        filename,
        scopeId,
        runtimeGlobalName,
        runtimeModuleName,
        bindingComponents: ast.bindingComponents,
        isTS,
        source: ast.loc.source,
        code: ``,
        column: 1,
        line: 1,
        offset: 0,
        indentLevel: 0,
        push(code, node) {
            context.code += code;
            if (context.map) {
                if (node) {
                    let name;
                    if (node.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ && !node.isStatic) {
                        const content = node.content.replace(/^_ctx\./, '');
                        if (content !== node.content && (0, compiler_core_1.isSimpleIdentifier)(content)) {
                            name = content;
                        }
                    }
                    addMapping(node.loc.start, name);
                }
                (0, compiler_core_1.advancePositionWithMutation)(context, code);
                if (node && node.loc !== compiler_core_1.locStub) {
                    addMapping(node.loc.end);
                }
            }
        },
        indent() {
            newline(++context.indentLevel);
        },
        deindent(withoutNewLine = false) {
            if (withoutNewLine) {
                --context.indentLevel;
            }
            else {
                newline(--context.indentLevel);
            }
        },
        newline() {
            newline(context.indentLevel);
        },
    };
    function newline(n) {
        context.push('\n' + `  `.repeat(n));
    }
    function addMapping(loc, name) {
        context.map.addMapping({
            name,
            source: context.filename || '',
            original: {
                line: loc.line,
                column: loc.column - 1, // source-map column is 0 based
            },
            generated: {
                line: context.line,
                column: context.column - 1,
            },
        });
    }
    // 暂时无需提供 sourcemap 支持
    // if (sourceMap) {
    //   // lazy require source-map implementation
    //   context.map = new SourceMapGenerator()
    //   context.map!.setSourceContent(filename, context.source)
    // }
    return context;
}
function genComponentImports(bindingComponents, { push, newline }) {
    const tags = Object.keys(bindingComponents);
    const importDeclarations = [];
    // 仅记录easycom和setup组件
    const components = [];
    tags.forEach((tag) => {
        const { name, type } = bindingComponents[tag];
        if (type === "unknown" /* BindingComponentTypes.UNKNOWN */) {
            const source = (0, uni_cli_shared_1.matchEasycom)(tag);
            if (source) {
                // 调整为easycom命名
                const easycomName = name.replace('component', 'easycom');
                bindingComponents[tag].name = easycomName;
                components.push(easycomName);
                (0, uni_cli_shared_1.addImportDeclaration)(importDeclarations, easycomName, source);
            }
        }
        else if (type === "setup" /* BindingComponentTypes.SETUP */) {
            components.push(name);
        }
    });
    if (tags.length) {
        push(`const __BINDING_COMPONENTS__ = '` +
            JSON.stringify(bindingComponents) +
            `'`);
        const resolveComponents = [];
        const names = [];
        Object.keys(bindingComponents).forEach((id) => {
            const { type, name } = bindingComponents[id];
            if (type === "unknown" /* BindingComponentTypes.UNKNOWN */) {
                resolveComponents.push(`const ${name} = _${compiler_core_1.helperNameMap[compiler_core_1.RESOLVE_COMPONENT]}("${id}");`);
                names.push(name);
            }
        });
        if (resolveComponents.length) {
            newline();
            push(`if (!Array) {`);
            resolveComponents.forEach((code) => {
                push(code);
            });
            push(`(${names.join('+')})()`);
            push(`}`);
        }
        newline();
        importDeclarations.forEach((str) => push(str));
        if (importDeclarations.length) {
            newline();
        }
        if (components.length) {
            push(`if (!Math) {`);
            push(` (${components.map((name) => name).join('+')})() `);
            push(`}`);
            newline();
        }
    }
}
function genFunctionPreamble(ast, context) {
    const { prefixIdentifiers, push, newline, runtimeGlobalName, bindingComponents, } = context;
    const VueBinding = runtimeGlobalName;
    const aliasHelper = (s) => `${compiler_core_1.helperNameMap[s]}: _${compiler_core_1.helperNameMap[s]}`;
    const helpers = Array.from(ast.helpers);
    if (helpers.length > 0) {
        if (prefixIdentifiers) {
            push(`const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`);
        }
        else {
            push(`const _Vue = ${VueBinding}\n`);
        }
    }
    genComponentImports(bindingComponents, context);
    newline();
    push(`return `);
}
function genModulePreamble(ast, context, inline) {
    const { push, newline, runtimeModuleName, bindingComponents } = context;
    const helpers = Array.from(ast.helpers);
    if (helpers.length) {
        push(`import { ${helpers
            .map((s) => `${compiler_core_1.helperNameMap[s]} as _${compiler_core_1.helperNameMap[s]}`)
            .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`);
    }
    if (ast.imports.length) {
        genImports(ast.imports, context);
    }
    genComponentImports(bindingComponents, context);
    newline();
    if (!inline) {
        push(`export `);
    }
}
function genImports(importsOptions, { push, newline }) {
    if (!importsOptions.length) {
        return;
    }
    importsOptions.forEach((imports) => {
        push(`import `);
        push(genExpr(imports.exp));
        push(` from '${imports.path}'`);
        newline();
    });
}
function createGenNodeContext() {
    const context = {
        code: '',
        helper(key) {
            return `_${compiler_core_1.helperNameMap[key]}`;
        },
        push(code) {
            context.code += code;
        },
    };
    return context;
}
function genBabelExpr(expr, opts = {}) {
    if (!(0, shared_1.hasOwn)(opts, 'jsescOption')) {
        opts.jsescOption = {};
    }
    opts.jsescOption.quotes = 'single';
    return (0, generator_1.default)(expr, opts).code;
}
exports.genBabelExpr = genBabelExpr;
function genExpr(node, context) {
    return genNode(node, context).code;
}
exports.genExpr = genExpr;
function genNode(node, context) {
    if (!context) {
        context = createGenNodeContext();
    }
    if ((0, shared_1.isString)(node)) {
        context.push(node);
        return context;
    }
    if ((0, shared_1.isSymbol)(node)) {
        context.push(context.helper(node));
        return context;
    }
    switch (node.type) {
        case 2 /* NodeTypes.TEXT */:
            genText(node, context);
            break;
        case 4 /* NodeTypes.SIMPLE_EXPRESSION */:
            genExpression(node, context);
            break;
        case 5 /* NodeTypes.INTERPOLATION */:
            genInterpolation(node, context);
            break;
        case 8 /* NodeTypes.COMPOUND_EXPRESSION */:
            genCompoundExpression(node, context);
            break;
    }
    return context;
}
function genText(node, context) {
    context.push(JSON.stringify(node.content), node);
}
function genExpression(node, context) {
    const { content, isStatic } = node;
    context.push(isStatic ? JSON.stringify(content) : content, node);
}
function genInterpolation(node, context) {
    const { push, helper } = context;
    push(`${helper(compiler_core_1.TO_DISPLAY_STRING)}(`);
    genExpr(node.content, context);
    push(`)`);
}
function genCompoundExpression(node, context) {
    for (let i = 0; i < node.children.length; i++) {
        const child = node.children[i];
        if ((0, shared_1.isString)(child)) {
            context.push(child);
        }
        else {
            genExpr(child, context);
        }
    }
}
