"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getUsedComponents = exports.getMpEventHanlderName = exports.generateWxml = void 0;
const chalk_1 = __importDefault(require("chalk"));
const weapps_core_1 = require("@cloudbase/lowcode-generator/lib/weapps-core");
const xml_js_1 = require("xml-js");
const mp_1 = require("@cloudbase/lowcode-generator/lib/generator/config/mp");
const materials_1 = require("./materials");
const weapp_1 = require("@cloudbase/lowcode-generator/lib/generator/util/weapp");
const name_mangler_1 = __importDefault(require("@cloudbase/lowcode-generator/lib/generator/util/name-mangler"));
const config_1 = require("../config");
const util_1 = require("./util");
const error = chalk_1.default.redBright;
function generateWxml(ctx, widgets, docTag, wxmlDataPrefix, usingComponents, componentGenerics, nodeTransform) {
    const nameMangler = ctx.isProduction ? new name_mangler_1.default({ blackList: mp_1.builtinMpTags }) : undefined;
    const xmlJson = {
        elements: [
            {
                type: 'element',
                name: 'wxs',
                attributes: {
                    src: '/common/utils.wxs',
                    module: 'wxsUtils',
                },
                elements: [],
                _order: -1,
                _parent: null,
            },
        ].concat(createXml(widgets)),
    };
    if (ctx.isPage) {
        const originElements = xmlJson.elements;
        // 登录校验: 向其最外层包裹一层block
        xmlJson.elements = [
            {
                type: 'element',
                name: 'block',
                attributes: {
                    ['wx:if']: '{{weDaHasLogin}}',
                    ['data-weui-theme']: 'light',
                },
                elements: originElements,
                _order: -1,
                _parent: null,
            },
        ];
    }
    function createXml(widgets, parent = null, parentForNodes = []) {
        var _a, _b, _c, _d, _e, _f;
        const elements = [];
        // eslint-disable-next-line no-restricted-syntax
        for (const id in widgets) {
            const { xComponent, xProps, properties, xIndex, genericComp } = widgets[id];
            const { data: _data = {}, listeners = [], directives = {}, staticResourceAttribute = ['src'] } = xProps || {};
            const data = { ..._data };
            if (directives.waIf && directives.waIf.value === false) {
                continue;
            }
            if (!xComponent) {
                // slot prop
                const slotNodes = createXml(properties, parent, parentForNodes);
                slotNodes.forEach((node) => {
                    if (node.name == 'block') {
                        /**
                         * block 组件上的 slot 属性不生效，只能透传到下面的组件
                         */
                        (node.elements || []).forEach((node) => {
                            node.attributes.slot = id;
                        });
                    }
                    else {
                        node.attributes.slot = id;
                    }
                    elements === null || elements === void 0 ? void 0 : elements.push(node);
                });
                if (parent) {
                    delete parent.attributes[id];
                }
                continue;
            }
            const componentKey = `${xComponent.moduleName}:${xComponent.name}`;
            const helpMsg = `Please check component(${id}) in component tree of ${docTag}.`;
            let tagName, path, componentProto;
            switch (componentKey) {
                case `${config_1.REPEATER.MODULE_NAME}:${config_1.REPEATER.REPEATER_NAME}`: {
                    const children = createXml(properties, parent, parentForNodes);
                    for (let item of children) {
                        item._order = xIndex || 0;
                    }
                    elements.push(...children);
                    continue;
                    break;
                }
                case `${config_1.REPEATER.MODULE_NAME}:${config_1.REPEATER.REPEATER_ITEM_NAME}`: {
                    tagName = 'block';
                    path = '';
                    break;
                }
                default: {
                    componentProto = (0, util_1.findComponentInfo)(xComponent, {
                        materialLibs: ctx.materialLibs,
                        miniprogramPlugins: ctx.miniprogramPlugins,
                    });
                    if (!componentProto) {
                        console.error(error(`Component(${xComponent.name}) not found in lib(${xComponent.moduleName}). ${helpMsg}`));
                        continue;
                    }
                    const meta = (0, materials_1.getWxmlTag)(ctx, xComponent, nameMangler);
                    tagName = meta.tagName;
                    path = meta.path;
                }
            }
            if (genericComp === null || genericComp === void 0 ? void 0 : genericComp.propName) {
                tagName = getGenericCompTagName(genericComp.propName);
                componentGenerics[tagName] = { default: path || tagName };
            }
            else if (path) {
                usingComponents[tagName] = path;
            }
            let curForNodes = parentForNodes;
            if ((_a = directives.waFor) === null || _a === void 0 ? void 0 : _a.value) {
                curForNodes = [...curForNodes, id];
            }
            const attrPrefix = `${wxmlDataPrefix.widgetProp}${id}${curForNodes
                .map((forNodeId) => `[${wxmlDataPrefix.forIndex}${forNodeId}]`)
                .join('')}`;
            if (tagName === 'slot') {
                const slotNode = {
                    type: 'element',
                    name: tagName,
                    attributes: {
                        name: data.name.type && data.name.type !== 'static' ? `{{${id}.name}}` : data.name.value,
                    },
                    elements: [],
                    _order: xIndex || 0,
                    _parent: null,
                };
                if ((_b = directives.waIf) === null || _b === void 0 ? void 0 : _b.value) {
                    slotNode.attributes['wx:if'] = getAttrBind(directives.waIf, `${attrPrefix}._waIf`);
                }
                elements.push(slotNode);
                continue;
            }
            const rawTag = ['slot', 'block'].includes(tagName);
            const node = {
                type: 'element',
                name: tagName,
                attributes: {
                    id: curForNodes.length < 1
                        ? id
                        : `{{'${id}'${curForNodes
                            .map((forNodeId) => `+ '-' + ${wxmlDataPrefix.forIndex}${forNodeId}`)
                            .join('')}}}`,
                    style: rawTag || ((_c = componentProto === null || componentProto === void 0 ? void 0 : componentProto.meta) === null || _c === void 0 ? void 0 : _c.mustEmptyStyle) ? undefined : `{{${attrPrefix}.style}}`,
                    [(0, mp_1.getClassAttrName)(tagName)]: rawTag ? undefined : `{{${attrPrefix}.className}}`,
                },
                elements: [],
                _order: xIndex || 0,
                _parent: parent,
            };
            if ((_d = directives.waIf) === null || _d === void 0 ? void 0 : _d.value) {
                node.attributes['wx:if'] = getAttrBind(directives.waIf, `${attrPrefix}._waIf`);
            }
            if ((_e = directives.waFor) === null || _e === void 0 ? void 0 : _e.value) {
                node.attributes['wx:for'] = getAttrBind(directives.waFor, `${wxmlDataPrefix.widgetProp}${id}${parentForNodes
                    .map((forNodeId) => `[${wxmlDataPrefix.forIndex}${forNodeId}]`)
                    .join('')}`);
                node.attributes['wx:for-index'] = wxmlDataPrefix.forIndex + id;
                node.attributes['wx:key'] = ((_f = directives.waForKey) === null || _f === void 0 ? void 0 : _f.value) ? '_key' : '$$i';
            }
            const compSchema = componentProto === null || componentProto === void 0 ? void 0 : componentProto.dataForm;
            for (const prop of Object.keys(data || {})) {
                if (compSchema) {
                    const fieldDef = compSchema[prop];
                    if (!fieldDef && !isGlobalAttr(prop)) {
                        // console.log(error(`Prop(${prop}) does not exist on ${componentKey}. ${helpMsg}`));
                        continue;
                    }
                    if (fieldDef === null || fieldDef === void 0 ? void 0 : fieldDef.readOnly) {
                        if (Object.prototype.hasOwnProperty.call(fieldDef, 'default')) {
                            node.attributes[prop] = fieldDef.default;
                        }
                        else {
                            console.error(error(`Readonly property(${prop}) of ${componentKey} must have a default value. ${helpMsg}`));
                        }
                        continue;
                    }
                    if ((fieldDef === null || fieldDef === void 0 ? void 0 : fieldDef['x-component']) === mp_1.WeappsCompSelect) {
                        // handle generic component, https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/generics.html
                        const passedComp = data[prop].value;
                        const [libName, compName] = passedComp.split(':');
                        const { tagName, path } = (0, materials_1.getWxmlTag)(ctx, { moduleName: libName, name: compName }, nameMangler);
                        node.attributes[`generic:${getGenericCompTagName(prop)}`] = tagName;
                        usingComponents[tagName] = path;
                        continue;
                    }
                }
                const widgetBind = /^[a-zA-Z]\w*$/.test(prop) // is js identifier char
                    ? `${attrPrefix}.${prop}`
                    : `${attrPrefix}['${prop}']`;
                xmlJsonSetCustomAttr(node, prop, getAttrBind(data[prop], widgetBind, staticResourceAttribute.includes(prop)), xComponent);
            }
            /**
             * 来自外部引用的组件，添加系统系别的事件绑定
             */
            if (componentGenerics[tagName] || usingComponents[tagName]) {
                node.attributes['bind:attached'] = '__mnt__';
                node.attributes['bind:detached'] = '__unmnt__';
            }
            // Event binding
            const { inputProps, syncProps } = (componentProto === null || componentProto === void 0 ? void 0 : componentProto.meta) || {};
            const syncConfigs = syncProps || inputProps || {};
            const processedListeners = [...(0, util_1.generateSyncListeners)(syncConfigs), ...listeners];
            // 扩展组件配置
            const { compConfig } = componentProto || {};
            // 如果是数据容器，则增加一个onDataChange事件 bind:onDataChange="onid1$onDataChange"
            if (compConfig === null || compConfig === void 0 ? void 0 : compConfig.isDataContainer) {
                processedListeners.push(...(0, util_1.generateDataContainerListeners)());
            }
            processedListeners.forEach((l) => {
                const evtName = getMpEventName(l.trigger);
                const modifiers = l;
                node.attributes[getMpEventAttr(evtName, modifiers, tagName)] = getMpEventHanlderName(id, evtName, modifiers);
            });
            if (compConfig === null || compConfig === void 0 ? void 0 : compConfig.pluginConfig) {
                if (compConfig.pluginConfig.attributes) {
                    Object.assign(node.attributes, compConfig.pluginConfig.attributes);
                }
                if (compConfig.pluginConfig.componentPath) {
                    usingComponents[tagName] = compConfig.pluginConfig.componentPath;
                }
            }
            // find ancestor nodes with for lists to mount data-for-indexes
            /* let curNode = node
            const nodeIdsWithFor: string[] = []
            while (curNode) {
              if (curNode.attributes['wx:for']) {
                nodeIdsWithFor.unshift(curNode.attributes.id)
              }
              curNode = curNode._parent as any
            }
            if (nodeIdsWithFor.length) {
              node.attributes['data-for-ids'] = nodeIdsWithFor.join(varSeparator)
              node.attributes['data-for-indexes'] = nodeIdsWithFor
                .map((id) => `{{${wxmlDataPrefix.forIndex}${id}}}`)
                .join(varSeparator)
            }*/
            node.elements = node.elements.concat(createXml(properties, node, curForNodes));
            // 特殊处理 swiper，对swiper 子节点包裹议程 swiperItem
            if (tagName === 'swiper' || componentKey === 'weda:Swiper' || componentKey === `${config_1.OFFICIAL_LIB_KEY}:Swiper`) {
                node.elements = node.elements.map((item, index) => {
                    var _a, _b;
                    let { ['wx:for']: wxFor, ['wx:for-index']: wxForIndex, ['wx:key']: wxKey, ['wx:if']: wxIf, ...itemRestKey } = item.attributes || {};
                    if (item.name !== 'swiper-item') {
                        let SwiperItem = {
                            type: 'element',
                            name: 'swiper-item',
                            attributes: {
                                id: `${((_a = item.attributes) === null || _a === void 0 ? void 0 : _a.id) || ((_b = node === null || node === void 0 ? void 0 : node.attributes) === null || _b === void 0 ? void 0 : _b.id) + index}-item`,
                                'wx:for': wxFor,
                                'wx:for-index': wxForIndex,
                                'wx:key': wxKey,
                                'wx:if': wxIf,
                            },
                            elements: [],
                            _order: index || 0,
                            _parent: node,
                        };
                        SwiperItem.elements = [
                            {
                                ...item,
                                attributes: {
                                    ...itemRestKey,
                                },
                                _parent: SwiperItem,
                            },
                        ];
                        return SwiperItem;
                    }
                    else {
                        return item;
                    }
                });
            }
            nodeTransform === null || nodeTransform === void 0 ? void 0 : nodeTransform(widgets[id], node);
            elements.push(node);
        }
        return elements.sort((a, b) => a._order - b._order);
    }
    return (0, xml_js_1.js2xml)(xmlJson, {
        spaces: '\t',
        /* textFn: text => {
          return text
            .replace(/&gt;/g, '>')
            .replace(/&lt;/g, '<')
            .replace(/&amp;/g, '&')
        }, */
    });
}
exports.generateWxml = generateWxml;
function xmlJsonSetCustomAttr(node, prop, value, comp) {
    if (mp_1.builtinWigetProps.indexOf(prop) > -1) {
        console.error(error(`Builtin prop(${prop}) is not allowed for custom component`), comp);
        return;
    }
    node.attributes[prop] = value;
    // FIXME Attention, handling innerText
    if (prop === mp_1.textContentPropName) {
        node.elements.push({ type: 'text', text: value });
    }
}
const evtNameMap = {
    __weapps_action_trigger_click: 'tap',
};
function getMpEventName(originalName) {
    // 模板中也有部分依赖保持相同的连字符(_)，更改时注意同步修改
    return evtNameMap[originalName] || originalName.replace(/\./g, '_');
}
function getMpEventHanlderName(widgetId, evtName, modifier = {}) {
    // Only builtin events have will bubble
    if (mp_1.builtinMpEvents.indexOf(evtName) === -1) {
        modifier = {};
    }
    return `on${widgetId}$${getMpEventName(evtName)}${modifier.isCapturePhase ? '$cap' : ''}${modifier.noPropagation ? '$cat' : ''}`;
}
exports.getMpEventHanlderName = getMpEventHanlderName;
/* onid3click,  */
function getMpEventAttr(evtName, modifier, tagName) {
    // Only builtin events have will bubble
    if (mp_1.builtinMpEvents.indexOf(evtName) === -1) {
        modifier = {};
    }
    let prefix = modifier.isCapturePhase ? 'capture-' : '';
    prefix += modifier.noPropagation ? 'catch' : 'bind';
    const sep = getEventBindSep(tagName);
    return prefix + sep + evtName;
}
function getEventBindSep(tagName) {
    // bind:input => bindinput 兼容微信 7.0.13 安卓版本 textarea 组件，bind:input 写法会导致事件失效
    return mp_1.nativeCompWhiteList.includes(tagName.toLowerCase()) ? '' : ':';
}
function getUsedComponents(widgets, usedCmps = {}) {
    (0, weapp_1.walkThroughWidgets)(widgets, (id, widget) => {
        const { xComponent, xProps = { data: null } } = widget;
        if (!xComponent)
            return;
        const { moduleName, name } = xComponent;
        if (!usedCmps[moduleName]) {
            usedCmps[moduleName] = new Set();
        }
        usedCmps[moduleName].add(name);
        // Handle generic component props
        Object.keys(xProps.data || {}).map((p) => {
            var _a;
            const { value } = (_a = xProps.data) === null || _a === void 0 ? void 0 : _a[p];
            if (/^\w+:\w+$/.test(value)) {
                const [libName, name] = value.split(':');
                if (!usedCmps[libName]) {
                    usedCmps[libName] = new Set();
                }
                usedCmps[libName].add(name);
            }
        });
    });
    return usedCmps;
}
exports.getUsedComponents = getUsedComponents;
function getAttrBind(dVale, widgetBind, isStaticResource) {
    const { type, value } = dVale;
    const attrVal = type === weapps_core_1.PropBindType.prop ? value : widgetBind;
    return isStaticResource ? `{{wxsUtils._getStaticResourceAttribute(${attrVal})}}` : `{{${attrVal}}}`;
}
function getGenericCompTagName(propName) {
    return `g--${propName}`;
}
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#list_of_global_attributes
function isGlobalAttr(prop = '') {
    const globalAttrs = [
        'animation',
        'role',
        'accesskey',
        'autocapitalize',
        'class',
        'contenteditable',
        'contextmenu',
        // 'data-*',
        'dir',
        'draggable',
        'enterkeyhint',
        'exportparts',
        'hidden',
        'id',
        'inputmode',
        'is',
        'itemid',
        'itemprop',
        'itemref',
        'itemscope',
        'itemtype',
        'lang',
        'nonce',
        'part',
        'slot',
        'spellcheck',
        'style',
        'tabindex',
        'title',
        'translate',
    ];
    return globalAttrs.indexOf(prop) > -1 || prop.startsWith('data-') || prop.startsWith('aria-');
}
