"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.genElementProps = exports.genNode = exports.generate = void 0;
const shared_1 = require("@vue/shared");
const uni_shared_1 = require("@dcloudio/uni-shared");
const uni_cli_shared_1 = require("@dcloudio/uni-cli-shared");
const compiler_core_1 = require("@vue/compiler-core");
const codegen_1 = require("../codegen");
const vFor_1 = require("../transforms/vFor");
const vIf_1 = require("../transforms/vIf");
const vSlot_1 = require("../transforms/vSlot");
const utils_1 = require("../transforms/utils");
function generate({ children }, { slot, event, scopeId, emitFile, filename, directive, lazyElement, isBuiltInComponent, isMiniProgramComponent, component, }) {
    const context = {
        slot,
        event,
        code: '',
        scopeId,
        directive,
        lazyElement,
        component,
        isBuiltInComponent,
        isMiniProgramComponent,
        push(code) {
            context.code += code;
        },
    };
    children.forEach((node) => {
        genNode(node, context);
    });
    emitFile({ type: 'asset', fileName: filename, source: context.code });
}
exports.generate = generate;
function genNode(node, context) {
    switch (node.type) {
        case 9 /* NodeTypes.IF */:
            return node.branches.forEach((node) => {
                genNode(node, context);
            });
        case 2 /* NodeTypes.TEXT */:
            return genText(node, context);
        case 5 /* NodeTypes.INTERPOLATION */:
            return genExpression(node.content, context);
        case 1 /* NodeTypes.ELEMENT */:
            if (node.tagType === 2 /* ElementTypes.SLOT */) {
                return genSlot(node, context);
            }
            else if (node.tagType === 1 /* ElementTypes.COMPONENT */) {
                return genComponent(node, context);
            }
            else if (node.tagType === 3 /* ElementTypes.TEMPLATE */) {
                return genTemplate(node, context);
            }
            else if (isLazyElement(node, context)) {
                return genLazyElement(node, context);
            }
            return genElement(node, context);
    }
}
exports.genNode = genNode;
function genText(node, { push }) {
    push(node.content);
}
function genExpression(node, { push }) {
    push(`{{${(0, codegen_1.genExpr)(node)}}}`);
}
function genVIf(exp, { push, directive }) {
    push(` ${directive}if="{{${exp}}}"`);
}
function genVElseIf(exp, { push, directive }) {
    push(` ${directive}elif="{{${exp}}}"`);
}
function genVElse({ push, directive }) {
    push(` ${directive}else`);
}
function genVFor(node, { push, directive }) {
    const { sourceCode, valueAlias, indexAlias } = node.vFor;
    push(` ${directive}for="${sourceCode}"`);
    if (valueAlias) {
        push(` ${directive}for-item="${valueAlias}"`);
    }
    if (valueAlias === 'index') {
        push(` ${directive}for-index="${indexAlias}"`);
    }
    const keyProp = (0, compiler_core_1.findProp)(node, 'key', true);
    if (keyProp) {
        const key = keyProp.exp.content;
        push(` ${directive}key="${key.includes('.') ? key.split('.')[1] : key}"`);
        node.props.splice(node.props.indexOf(keyProp), 1);
    }
}
function genSlot(node, context) {
    // 移除掉所有非name属性，即移除作用域插槽的绑定指令
    node.props = node.props.filter((prop) => {
        if (prop.type === 6 /* NodeTypes.ATTRIBUTE */) {
            return prop.name === 'name';
        }
        else if (prop.arg?.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */) {
            return prop.arg.content === 'name';
        }
    });
    if (!node.children.length || context.slot.fallbackContent) {
        // 无后备内容或支持后备内容
        return genElement(node, context);
    }
    const { push } = context;
    const isVIfSlot = (0, vIf_1.isIfElementNode)(node);
    if (isVIfSlot) {
        push(`<block`);
        genVIfCode(node, context);
        push(`>`);
        delete node.vIf;
    }
    const children = node.children.slice();
    node.children.length = 0;
    push(`<block`);
    const nameProp = (0, compiler_core_1.findProp)(node, 'name');
    let name = uni_shared_1.SLOT_DEFAULT_NAME;
    if (nameProp) {
        if ((0, uni_cli_shared_1.isAttributeNode)(nameProp)) {
            if (nameProp.value?.content) {
                name = nameProp.value.content;
            }
        }
        else {
            if (nameProp.slotName) {
                name = nameProp.slotName;
            }
        }
    }
    genVIf(`$slots.${name}`, context);
    push(`>`);
    genElement(node, context);
    push(`</block>`);
    push(`<block`);
    genVElse(context);
    push(`>`);
    children.forEach((node) => {
        genNode(node, context);
    });
    push(`</block>`);
    if (isVIfSlot) {
        push(`</block>`);
    }
}
function genTemplate(node, context) {
    const slotProp = node.props.find((prop) => prop.type === 7 /* NodeTypes.DIRECTIVE */ &&
        (prop.name === 'slot' ||
            (prop.name === 'bind' &&
                prop.arg?.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ &&
                prop.arg.content === 'slot')));
    // 为 bind 时，通常是作用域插槽生成的 vSlot.ts:197 createBindDirectiveNode('slot',...)
    if (slotProp && (slotProp.name === 'bind' || (0, vSlot_1.findSlotName)(slotProp))) {
        /**
         * 仅百度、字节支持使用 block 作为命名插槽根节点
         * 此处为了统一仅默认替换为view
         * <template v-slot/> => <view slot="">
         */
        node.tag = 'view';
    }
    else {
        // <template/> => <block/>
        node.tag = 'block';
    }
    // @ts-ignore
    node.tagType = 0 /* ElementTypes.ELEMENT */;
    // 仅单个子节点的命名插槽(非作用域)，直接使用子节点作为插槽使用，避免多增加的 view 节点影响 flex 排版
    if (slotProp &&
        node.tag === 'view' &&
        !(0, vFor_1.isForElementNode)(node) &&
        node.children.length === 1) {
        const child = node.children[0];
        if ((0, uni_cli_shared_1.isElementNode)(child) &&
            !(0, vFor_1.isForElementNode)(child) &&
            !(0, compiler_core_1.isSlotOutlet)(child)) {
            child.props.push(slotProp);
            return genElement(child, context);
        }
    }
    return genElement(node, context);
}
function genComponent(node, context) {
    if (context.component?.getPropertySync) {
        return genElement(node, context);
    }
    if ((0, vIf_1.isIfElementNode)(node) || (0, vFor_1.isForElementNode)(node)) {
        return genElement(node, context);
    }
    // 小程序原生组件，补充 if(r0)
    if (context.isMiniProgramComponent(node.tag)) {
        ;
        node.vIf = {
            name: 'if',
            condition: 'r0',
        };
        return genElement(node, context);
    }
    const prop = (0, compiler_core_1.findProp)(node, utils_1.ATTR_VUE_PROPS);
    if (!prop) {
        return genElement(node, context);
    }
    ;
    node.vIf = {
        name: 'if',
        condition: prop.exp.content,
    };
    return genElement(node, context);
}
function isLazyElement(node, context) {
    if (!context.lazyElement) {
        return false;
    }
    let lazyProps;
    if ((0, shared_1.isFunction)(context.lazyElement)) {
        const res = context.lazyElement(node, context);
        if (!(0, shared_1.isPlainObject)(res)) {
            return res;
        }
        lazyProps = res[node.tag];
    }
    else {
        lazyProps = context.lazyElement[node.tag];
    }
    if (lazyProps === true) {
        return true;
    }
    if (!lazyProps) {
        return;
    }
    return node.props.some((prop) => prop.type === 7 /* NodeTypes.DIRECTIVE */ &&
        lazyProps.find((lazyProp) => {
            return (prop.name === lazyProp.name &&
                prop.arg?.type === 4 /* NodeTypes.SIMPLE_EXPRESSION */ &&
                lazyProp.arg.includes(prop.arg.content));
        }));
}
/**
 * 部分内置组件的部分事件在初始化时会立刻触发，但标准事件需要等首次渲染才能确认事件函数，故增加wx:if="{{r0}}"
 * @param node
 * @param context
 */
function genLazyElement(node, context) {
    const { push } = context;
    if (!(0, vIf_1.isIfElementNode)(node)) {
        push(`<block`);
        // r0 => ready 首次渲染
        genVIf(`r0`, context);
        push(`>`);
        genElement(node, context);
        push(`</block>`);
        return;
    }
    // v-if,v-else-if 无需处理
    if (node.vIf.name !== 'else') {
        return genElement(node, context);
    }
    push(`<block`);
    genVElse(context);
    push(`>`);
    node.vIf.name = 'if';
    node.vIf.condition = 'r0';
    genElement(node, context);
    push(`</block>`);
}
function genVIfCode(node, context) {
    const { name, condition } = node.vIf;
    if (name === 'if') {
        genVIf(condition, context);
    }
    else if (name === 'else-if') {
        genVElseIf(condition, context);
    }
    else if (name === 'else') {
        genVElse(context);
    }
}
function genElement(node, context) {
    const { children, isSelfClosing, props } = node;
    let tag = node.tag;
    // <template slot="left"/> => <block slot="left"/>
    if (tag === 'template') {
        if ((0, compiler_core_1.findProp)(node, 'slot')) {
            tag = 'view';
        }
        else {
            tag = 'block';
        }
    }
    // 无用的 block
    if (tag === 'block' &&
        props.length === 0 &&
        !(0, vIf_1.isIfElementNode)(node) &&
        !(0, vFor_1.isForElementNode)(node)) {
        return children.forEach((node) => {
            genNode(node, context);
        });
    }
    let virtualHost = false;
    if ((0, uni_cli_shared_1.isUserComponent)(node, context)) {
        tag = (0, shared_1.hyphenate)(tag);
        if (context.component?.normalizeName) {
            tag = context.component?.normalizeName(tag);
        }
        if (context.component?.mergeVirtualHostAttributes) {
            virtualHost = true;
        }
    }
    const { push } = context;
    const hasVIf = (0, vIf_1.isIfElementNode)(node);
    const hasVFor = (0, vFor_1.isForElementNode)(node);
    const hasVIfAndVFor = hasVIf && hasVFor;
    // 小程序中 wx:else wx:elif 不支持与 wx:for 同时使用
    // 故 if 需要补充一层 block
    if (hasVIfAndVFor) {
        push(`<block`);
        genVIfCode(node, context);
        push(`>`);
    }
    push(`<${tag}`);
    if (!hasVIfAndVFor && hasVIf) {
        genVIfCode(node, context);
    }
    if (hasVFor) {
        genVFor(node, context);
    }
    if (props.length) {
        genElementProps(node, virtualHost, context);
    }
    if (isSelfClosing) {
        push(`/>`);
    }
    else {
        push(`>`);
        children.forEach((node) => {
            genNode(node, context);
        });
        push(`</${tag}>`);
    }
    if (hasVIfAndVFor) {
        push(`</block>`);
    }
}
function checkVirtualHostProps(name, virtualHost) {
    const names = [name];
    if (virtualHost) {
        const obj = {
            style: utils_1.VIRTUAL_HOST_STYLE,
            class: utils_1.VIRTUAL_HOST_CLASS,
        };
        if (name in obj) {
            // TODO 支付宝平台移除原有属性（支付宝小程序自定义组件外部属性始终无效）
            names.push(obj[name]);
        }
        return names;
    }
    return names;
}
function genElementProps(node, virtualHost, context) {
    node.props.forEach((prop) => {
        if (prop.type === 6 /* NodeTypes.ATTRIBUTE */) {
            const { value } = prop;
            if (value) {
                checkVirtualHostProps(prop.name, virtualHost).forEach((name) => {
                    context.push(` ${name}="${value.content}"`);
                });
            }
            else {
                context.push(` ${prop.name}`);
            }
        }
        else {
            const { name } = prop;
            if (name === 'on') {
                genOn(prop, node, context);
            }
            else {
                genDirectiveNode(prop, node, virtualHost, context);
            }
        }
    });
}
exports.genElementProps = genElementProps;
function genOn(prop, node, { push, event, isBuiltInComponent }) {
    const arg = prop.arg.content;
    const exp = prop.exp;
    const modifiers = prop.modifiers;
    const name = (event?.format || uni_cli_shared_1.formatMiniProgramEvent)(arg, {
        isCatch: modifiers.includes('stop') || modifiers.includes('prevent'),
        isCapture: modifiers.includes('capture'),
        isComponent: (0, uni_cli_shared_1.isUserComponent)(node, { isBuiltInComponent }),
    });
    if (exp.isStatic) {
        push(` ${name}="${exp.content}"`);
    }
    else {
        push(` ${name}="{{${exp.content}}}"`);
    }
}
function genDirectiveNode(prop, node, virtualHost, context) {
    const { push, component } = context;
    if (prop.name === 'slot') {
        if (prop.arg) {
            const arg = prop.arg;
            if (arg.isStatic) {
                const slotName = (0, uni_shared_1.dynamicSlotName)(arg.content);
                // 非作用域默认插槽不生成 slot 属性
                if (slotName !== uni_shared_1.SLOT_DEFAULT_NAME) {
                    push(` slot="${slotName}"`);
                }
            }
            else {
                push(` slot="{{${arg.content}}}"`);
            }
        }
    }
    else if (prop.name === 'show') {
        let hiddenPropName = 'hidden';
        if ((0, uni_cli_shared_1.isUserComponent)(node, context) && component && component.vShow) {
            hiddenPropName = component.vShow;
        }
        push(` ${hiddenPropName}="{{!${prop.exp.content}}}"`);
    }
    else if (prop.arg && prop.exp) {
        const arg = prop.arg.content;
        const exp = prop.exp.content;
        checkVirtualHostProps(arg, virtualHost).forEach((arg) => {
            push(` ${arg}="{{${exp}}}"`);
        });
    }
    else {
        if (prop.name !== 'bind') {
            throw new Error(`unknown directive ` + JSON.stringify(prop));
        }
    }
}
