"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.McxCompileData = exports.CompileData = exports.DefaultCache = void 0;
const context_js_1 = __importDefault(require("./context.js"));
const parser_1 = __importDefault(require("@babel/parser"));
const t = __importStar(require("@babel/types"));
const index_js_1 = __importDefault(require("./../ast/index.js"));
const lib_js_1 = require("./../compile-component/lib.js");
const utils_node_js_1 = __importDefault(require("./utils.node.js"));
const utils_js_1 = __importDefault(require("./../utils.js"));
const node_path_1 = __importDefault(require("node:path"));
const index_js_2 = __importDefault(require("../../loger/index.js"));
const promises_1 = require("node:fs/promises");
const node_crypto_1 = require("node:crypto");
const McxConfig = {
    allowTag: {
        script: null,
        Event: null,
        Component: {
            items: {
                item: null
            }
        }
    }
};
const conBuild = require("./../../build/build-g-config.json");
exports.DefaultCache = {
    import: [],
    export: [],
    call: []
};
class CompileData {
    AST;
    Cache;
    type = "JsCompileData";
    context;
    filePath = "";
    constructor(AST, cache = exports.DefaultCache) {
        this.AST = AST;
        this.Cache = cache;
        this.context = new context_js_1.default();
    }
}
exports.CompileData = CompileData;
class McxCompileData {
    AST;
    JSContext = new context_js_1.default();
    type = "McxCompileData";
    filePath = "";
    setFilePath(filePath) {
        this.filePath = filePath;
    }
    constructor(AST) {
        this.AST = AST;
    }
}
exports.McxCompileData = McxCompileData;
class Utils {
    static async FileAST(fileDir, parserOpt) {
        if (typeof fileDir !== "string")
            throw new TypeError("[read file]: compile utils was passed a non-string value");
        const file = await utils_js_1.default.readFile(fileDir);
        if (typeof file !== "string")
            throw new Error("[read file]: not found file " + fileDir);
        try {
            return parser_1.default.parse(file).program;
        }
        catch (err) {
            throw new Error("[compile ast]: " + err.stack);
        }
    }
    static async FileContent(fileDir) {
        const file = await utils_js_1.default.readFile(fileDir);
        if (typeof file === "string")
            return file;
        throw new Error("[read file]: not found file " + fileDir);
    }
    static nodeStringValue(node) {
        if (node.type == "StringLiteral") {
            return node.value;
        }
        else if (node.type == "Identifier") {
            return node.name;
        }
        throw new TypeError("[read id error]: no way to read string id");
    }
    static CheckImportNode(node, ir) {
        const newList = Utils.ImportToCache(node);
        // Eliminate common differences
        if (newList.source !== ir.source)
            return false;
        if (newList.imported.length !== ir.imported.length)
            return false;
        // in this for, newList.imported and ir.imported is same length
        for (let irIndex = 0; irIndex < newList.imported.length; irIndex++) {
            const newItem = newList.imported[irIndex];
            const oldItem = ir.imported[irIndex];
            if (newItem?.import !== oldItem?.import || newItem?.as !== oldItem?.as || newItem?.isAll !== oldItem?.isAll)
                return false;
        }
        return true;
    }
    static CacheToImportNode(ir) {
        if (!ir)
            throw new TypeError("plase call use right ImportList");
        // first verify ir.raw
        if (ir?.raw && Utils.CheckImportNode(ir?.raw, ir))
            return ir.raw;
        let result = [];
        for (let ImportIt of ir.imported) {
            if (!ImportIt)
                continue;
            if (ImportIt.isAll) {
                result.push(t.importNamespaceSpecifier(t.identifier(ImportIt.as)));
                continue;
            }
            if (ImportIt.import == "default") {
                result.push(t.importDefaultSpecifier(t.identifier(ImportIt.as)));
                continue;
            }
            if (!ImportIt.import)
                throw new TypeError("[compile node]: not found imported");
            result.push(t.importSpecifier(t.identifier(ImportIt.as), t.identifier(ImportIt.import)));
        }
        return t.importDeclaration(result, t.stringLiteral(ir.source));
    }
    static ImportToCache(node) {
        const result = [];
        for (let item of node.specifiers) {
            const thisName = item.local.name;
            if (item.type == "ImportNamespaceSpecifier") {
                result.push({
                    isAll: true,
                    as: thisName
                });
            }
            else if (item.type == "ImportDefaultSpecifier") {
                result.push({
                    isAll: false,
                    import: "default",
                    as: thisName
                });
            }
            else if (item.type == "ImportSpecifier") {
                result.push({
                    isAll: false,
                    as: thisName,
                    import: Utils.nodeStringValue(item.imported)
                });
            }
        }
        return {
            source: Utils.nodeStringValue(node.source),
            imported: result,
            raw: node
        };
    }
}
class ComponentExport {
    rule;
    ast;
    TopConext;
    // export: fileid
    constructor(code, rule, BabelConfig) {
        this.rule = rule;
        this.ast = parser_1.default.parse(code).program;
        this.TopConext = {};
        this.run();
    }
    run() {
        const spData = {};
        this.atre(this.ast, spData);
        return spData;
    }
    FindVar(name, ThisContext) {
        if (typeof name == "string") {
            if (this.TopConext[name])
                return this.TopConext[name];
            if (ThisContext[name])
                return ThisContext[name];
        }
        throw new Error("[compile error]: can't find var : " + name);
    }
    writeResult(spData, declaration, id) {
        // @ts-ignore
        if (this.rule.getExport && this.rule.getExport.includes(id)) {
            return;
        }
        const init = declaration.init;
        if (init?.type !== "NewExpression")
            return;
        const className = init.callee;
        if (className.type !== "MemberExpression")
            return;
        const callee = utils_node_js_1.default.memberExpressionToStringArray(className, 2);
        if (callee[0] !== "__MCX__")
            return;
        const name = callee[1];
        if (name == "ItemComponent")
            spData[id] = new lib_js_1.ItemComponent();
    }
    atre(block, spData) {
        const isTop = block.type == "Program";
        const ThisContext = {};
        for (const _node of block.body) {
            if (_node.type == "BlockStatement") {
                this.atre(_node, spData);
            }
            else if (_node.type == "BreakStatement" || _node.type == "ContinueStatement" || _node.type == "EmptyStatement" || _node.type == "WithStatement")
                continue;
            let node = _node;
            // 基础模块报错
            let isExport = isTop ? Boolean(this.rule.setup) : false;
            if (isTop) {
                if (_node.type == "ExportDefaultDeclaration") {
                    isExport = true;
                    node = _node.declaration;
                }
                else if (_node.type == "ExportNamedDeclaration") {
                    if (_node.specifiers.length > 0 || !_node.declaration)
                        throw new Error("[compile component]: you shouldn't use 'export xxx from xxx' in component mcx");
                    isExport = true;
                    node = _node.declaration;
                }
                else if (_node.type == "ExportAllDeclaration" || _node.type == "ImportDeclaration") {
                    throw new Error("[compile component]: 'export * from ' or 'import'(you should use __MCX__ to visit mcx component class) can't use in component mcx");
                }
            }
            if (node.type == "VariableDeclaration") {
                if (node.kind !== "const")
                    continue;
                for (const declaration of node.declarations) {
                    if (!declaration || !declaration.init)
                        continue;
                    // 理想模式 
                    if (declaration.id.type == "Identifier") {
                        const id = declaration.id.name;
                        if (!id)
                            continue;
                        if (isTop) {
                            this.TopConext[id] = declaration.init;
                        }
                        else {
                            ThisContext[id] = declaration.init;
                        }
                    }
                }
            }
        }
    }
}
class CompileMain {
    compileOpt;
    main;
    babelConfig;
    constructor(compileOpt) {
        this.compileOpt = compileOpt;
        this.main = utils_js_1.default.AbsoluteJoin(this.compileOpt.ProjectDir, this.compileOpt.main);
        let babelConfig = this.compileOpt.config?.babelParser;
        if (!babelConfig)
            babelConfig = {};
        this.babelConfig = {
            sourceType: "module",
            allowImportExportEverywhere: false,
            createParenthesizedExpressions: false,
            attachComment: false,
            ...babelConfig
        };
    }
    async compile() {
        if (!await utils_js_1.default.FileExsit(this.main)) {
            throw new Error("[compile error]: cannot find ");
        }
        let moduleCache = new Map();
        const main__code = await Utils.FileAST(this.main, this.babelConfig);
        const mainCompileData = this.traverse(main__code, true);
        mainCompileData.filePath = this.main;
        // for in import
        const importIrs = mainCompileData.Cache.import;
        let newBody = main__code.body;
        if (importIrs.length > 0) {
            // go in fn
            this.trImport(importIrs, moduleCache, this.main);
        }
        if (mainCompileData.Cache.export.length > 0) {
            newBody.push(...this.MergeExport(mainCompileData.Cache, mainCompileData.Cache.export));
        }
    }
    RandomUniqueValue() {
        return "__mbler__" + (0, node_crypto_1.randomUUID)().replace("-", "");
    }
    ensureExpression(node) {
        if (node.type == "ClassDeclaration")
            return t.classExpression(node.id, node.superClass, node.body);
        if (node.type == "FunctionDeclaration")
            return t.functionExpression(node.id, node.params, node.body, node.generator, node.async);
        if (t.isExpression(node))
            return node;
        throw new Error("[compile error]: cannot ensure");
    }
    MergeExport(ir, exp) {
        let result = [];
        let resObj = t.objectExpression([]);
        for (const item of exp) {
            if (item.type == "ExportAllDeclaration") {
                // ExportAllDeclaration
                resObj.properties.push(t.spreadElement(t.callExpression(t.identifier("__import"), [
                    t.identifier(item.source.value)
                ])));
            }
            else if (item.type == "ExportDefaultDeclaration") {
                // ExportDefaultDclaration
                resObj.properties.push(t.objectProperty(t.identifier("default"), this.ensureExpression(item.declaration)));
            }
            else {
                // ExportNamedDeclaration
            }
        }
        result.push(t.returnStatement(resObj));
        return result;
    }
    genaterateModuleFunction(body) {
        return t.functionDeclaration(t.identifier(this.RandomUniqueValue()), [
            t.identifier("__import")
        ], body, false, true);
    }
    async trImport(importList, cacheMap, filePath) {
        for (const moduleData of importList) {
            const source = moduleData.source;
            // 判断源的类型
            const PasreDir = node_path_1.default.parse(source);
            // if root = /, we should warn user because compiling can leave the computer 
            if (PasreDir.root == "/") {
                index_js_2.default.w("mcx compile", "you shouldn't use Absolute path");
                continue;
            }
            let modulePath = "";
            // Relative Path
            if (PasreDir.dir == "./") {
                modulePath = node_path_1.default.join(filePath, source);
            }
            else if (source == "mcx") {
                // visit 'mcx'
                //  mcx 提供的导出被计划为大多都是宏，不需要处理，后续补上.d.ts即可
                continue;
            }
            else {
                // find module
                if (this.compileOpt.moduleList.includes(source)) {
                    // is module
                    const moduleDir = node_path_1.default.join(this.compileOpt.moduleDir, source);
                    let moduleMainfest;
                    try {
                        moduleMainfest = JSON.parse(await (0, promises_1.readFile)(node_path_1.default.join(moduleDir, conBuild.packageFile), "utf-8"));
                    }
                    catch {
                        moduleMainfest = {
                            name: "",
                            mcVersion: "1.21.00",
                            description: "",
                            version: "0.0.1",
                            script: {
                                main: "./index.js"
                            }
                        };
                    }
                    const TempStr = moduleMainfest.script?.main;
                    if (typeof TempStr == "string") {
                        modulePath = TempStr;
                    }
                    modulePath = node_path_1.default.join(moduleDir, modulePath);
                }
            }
            let compileData;
            const moduleCode = await (0, promises_1.readFile)(modulePath, "utf-8");
            const moduleExt = node_path_1.default.extname(modulePath).slice(1);
            if (moduleExt == "js") {
                const AST = parser_1.default.parse(moduleCode).program;
                compileData = this.traverse(AST, true);
            }
            else if (moduleExt == "mcx") {
                compileData = this.MCXCompile(moduleCode);
            }
            else {
                throw new Error("[load module] load module '" + modulePath + "' : ext is not useful");
            }
            compileData.filePath = modulePath;
            cacheMap.set(modulePath, compileData);
        }
    }
    Tag(code) {
        return (new index_js_1.default.tag(code)).parseAST();
    }
    MCXCompile(code) {
        const parser = this.Tag(code);
        let result = {
            Component: {
                item: [],
                isLoad: false
            },
            Event: {
                on: "after",
                data: []
            }
        };
        let allowTag = McxConfig.allowTag;
        const FindTag = (node, lastTag) => {
            const allowTagNames = Object.keys(allowTag);
            const isTop = lastTag.length == 0;
            for (let index = 0; index < node.length; index++) {
                const item = node[index];
                const hasContent = !!(typeof item?.content?.data == "string" && item.content.data.trim().length > 1);
                if (typeof item?.name !== "string")
                    throw new Error("[compile error]: mcx: Tag name is not string");
                if (!allowTagNames.includes(item?.name))
                    throw new Error("[compile error]: mcx: visit not allow tag: " + item?.name);
                if (isTop) {
                    if (item.name == "script") {
                        // 验证内容
                        if (!hasContent || !item.content)
                            throw new Error("[compile error]: mcx: script tag shouldn't null");
                        // 验证是否已写
                        if (result.script)
                            throw new Error("[compile error]: mcx: a mcx cannot have two script tag");
                        result.script = this.traverse(parser_1.default.parse(item.content.data).program, true);
                        continue;
                    }
                    if (item.name == "Event") {
                        // 验证内容
                        if (!hasContent || !item.content)
                            throw new Error("[compile error]: mcx: Event shouldn't null");
                        // 验证属性是否可写
                        if (result.Event.data.length > 0)
                            throw new Error("[compile error]: mcx: a mcx can't have two event tag");
                        // 验证是否冲突
                        if (result.Component.isLoad)
                            throw new Error("[compile error]: mcx: a mcx can't have comonent and event");
                        const prop = index_js_1.default.prop(item.content.data);
                        if (prop.length < 1)
                            throw new Error("[compile error]: mcx: event tag should't have null data");
                        const TempObj = item.arr;
                        let on;
                        // 判断事件类型
                        const isAfter = Boolean(TempObj["@after"]);
                        const isBefore = Boolean(TempObj["@before"]);
                        if (isAfter && isBefore)
                            throw new Error("[compile error]: mcx: Event can't have @after and @before");
                        on = isAfter ? "after" : (isBefore ? "before" : "after");
                        result.Event = {
                            on,
                            data: prop
                        };
                    }
                    if (item.name == "Component") {
                        if (!hasContent || !item.content)
                            throw new Error("[compile error]: mcx: Component should't null");
                        // 是否冲突
                        if (result.Event.data.length > 0)
                            throw new Error("[compile error]: mcx: compoent tag can't use with event tag");
                        // 是否已写
                        if (result.Component.isLoad)
                            throw new Error("[compile error]: mcx: a mcx shouldn't use more component");
                        allowTag = McxConfig.allowTag.Component;
                        FindTag(this.Tag(item.content.data), [item.name]);
                        return;
                    }
                }
                const TopTag = lastTag[0];
                // 不需要判断 script和Event，因为其只有一层
                if (TopTag == "Component") {
                    // 遍历到第二层的时候
                    if (lastTag.length == 1) {
                        if (allowTag[item.name]) {
                            if (!hasContent || !item.content)
                                throw new Error("[compile mcx]: can't get component data");
                            FindTag(this.Tag(item.content?.data), [...lastTag, item.name]);
                            return;
                        }
                    }
                    // 遍历内部
                    if (lastTag.length > 1) {
                        if (typeof item.arr.id !== "string" || !hasContent || !item.content)
                            throw new Error("[compile mcx]: a component have to have a file id as arr");
                        if (item.name == "item") {
                            result.Component.item.push({
                                Fileid: item.arr.id.replace("../", ""),
                                UseExport: item.content.data.trim()
                            });
                        }
                    }
                }
                else {
                    throw new Error("[compile mcx]: TopTag is not right");
                }
            }
        };
        FindTag(parser, []);
        const res = new McxCompileData(result);
        return res;
    }
    traverse(node, isTop, data) {
        if (isTop && node.type == "Program")
            data = new CompileData(node);
        if (!data)
            throw new Error("[traverse error]: cannot init CompileData");
        const buildCache = data.Cache;
        // 用于
        const tempImport;
        for (let nodeIndex = 0; nodeIndex < node.body.length; nodeIndex++) {
            const nodeItem = node.body[nodeIndex];
            if (!nodeItem)
                continue;
            // must in global
            if (isTop) {
                switch (nodeItem.type) {
                    case "ImportDeclaration":
                        const newsImport = Utils.ImportToCache(nodeItem);
                        buildCache.import.push(newsImport);
                        node.body.splice(nodeIndex, 1);
                        nodeIndex -= 1;
                        break;
                    case "ExportDefaultDeclaration":
                    case "ExportAllDeclaration":
                    case "ExportNamedDeclaration":
                        buildCache.export.push(nodeItem);
                        node.body.splice(nodeIndex, 1);
                        nodeIndex -= 1;
                        break;
                }
            }
            // 都有的
            switch (nodeItem.type) {
                case "ExpressionStatement":
                    const Exp = nodeItem.expression;
                    if (Exp.type == "CallExpression") {
                        const callee = Exp.callee;
                        let name = [];
                        if (callee.type == "Identifier")
                            name.push(callee.name);
                        if (callee.type == "MemberExpression")
                            name = utils_node_js_1.default.memberExpressionToStringArray(callee, 3);
                        if (name.length >= 1)
                            buildCache.call.push({
                                arguments: Exp.arguments,
                                remove: () => {
                                    node.body.splice(nodeIndex, 1);
                                },
                                source: name
                            });
                    }
                    break;
                case "IfStatement":
                    // handler node
                    if (nodeItem.consequent)
                        this.traverse(t.blockStatement([nodeItem.consequent]), false, data);
                    if (nodeItem.alternate)
                        this.traverse(t.blockStatement([
                            nodeItem.alternate
                        ]), false, data);
                    break;
            }
        }
        return data;
    }
}
exports.default = CompileMain;
