"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const DeclarationParser_1 = require("./DeclarationParser");
const FileApi_1 = require("./FileApi");
const DefineVisitor_1 = require("./parser/DefineVisitor");
const Terser = require("terser");
const concat_with_sourcemaps_1 = require("concat-with-sourcemaps");
const PackageVersion_1 = require("./PackageVersion");
const PackedLessFile_1 = require("./core/PackedLessFile");
const PackedCssFile_1 = require("./core/PackedCssFile");
class FilePacker {
    root;
    file;
    packageConfig;
    content = [];
    header = [];
    dependencies = [];
    done = {};
    sourceNodes = [];
    lessNodes = {
        global: void 0,
        local: void 0,
        globalHigh: void 0,
        localHigh: void 0,
        globalLow: void 0,
        localLow: void 0
    };
    cssNodes = {
        global: void 0,
        local: void 0,
        globalHigh: void 0,
        localHigh: void 0,
        globalLow: void 0,
        localLow: void 0
    };
    appPath = "";
    fileApi;
    constructor(root, file, packageConfig) {
        this.root = root;
        this.file = file;
        this.packageConfig = packageConfig;
        this.fileApi = new FileApi_1.default(root);
    }
    async pack() {
        const dependentFiles = {};
        const filePath = this.fileApi.parse(this.file);
        const moduleName = `${this.packageConfig.name}/${filePath.dir}/${filePath.base}`;
        await this.writeFile(this.file, moduleName);
        const outputFile = this.file + ".pack.js";
        const outputFileMin = this.file + ".pack.min.js";
        const umdFile = PackageVersion_1.default.isV2
            ? `${this.root}/node_modules/@web-atoms/module-loader/umd.js`
            : `${this.root}/node_modules/web-atoms-amd-loader/umd.js`;
        this.sourceNodes.push(await this.jsFile(umdFile));
        // for (const iterator of this.content) {
        //     await this.fileApi.appendString(outputFile, iterator + "\r\n");
        // }
        // need to add the app...
        if (this.appPath) {
            await this.writeFile(`${this.root}/node_modules/${this.appPath}`, this.appPath);
        }
        // packages
        const packages = [];
        for (const iterator of this.header) {
            const pn = DeclarationParser_1.default.parsePackage(iterator);
            if (!packages.find((n) => n === pn.name)) {
                packages.push(pn.name);
            }
        }
        this.sourceNodes.push({ content: `
        AmdLoader.instance.register(
            [${packages.map((s) => JSON.stringify(s)).join(",")}],
            [${this.header.map((s) => JSON.stringify(s)).join(",")}]);
` });
        for (const iterator of this.content) {
            if (iterator.file) {
                dependentFiles[iterator.file] = iterator.fileMTime;
            }
            this.sourceNodes.push(iterator);
        }
        // now lets do the magic !!
        const concat = new concat_with_sourcemaps_1.default(true, outputFile, "\n");
        // concat.add("none.js", "// web-atoms-packed\n");
        const moduleRoot = this.fileApi.resolve(filePath.dir);
        const absRoot = moduleRoot;
        // let us import style sheets first...
        const wait = [];
        for (const key in this.lessNodes) {
            if (Object.hasOwn(this.lessNodes, key)) {
                const element = this.lessNodes[key];
                if (element && !element.isEmpty) {
                    wait.push(element.postSave());
                    this.sourceNodes.push({ content: `window.installStyleSheet( new URL("./${filePath.base}.pack.${key}.less.css", document.currentScript.src).toString());` });
                }
            }
        }
        for (const key in this.cssNodes) {
            if (Object.hasOwn(this.cssNodes, key)) {
                const element = this.cssNodes[key];
                if (element && !element.isEmpty) {
                    wait.push(element.postSave());
                    this.sourceNodes.push({ content: `window.installStyleSheet( new URL("./${filePath.base}.pack.${key}.css", document.currentScript.src).toString());` });
                }
            }
        }
        await Promise.all(wait);
        for (const iterator of this.sourceNodes) {
            const r = iterator.file ? this.fileApi.relative(absRoot, this.fileApi.resolve(iterator.file)) : undefined;
            const map = iterator.map;
            if (map) {
                const ss = map.sources;
                if (ss) {
                    const fileRoot = this.fileApi.parse(this.fileApi.resolve(iterator.file));
                    map.sources =
                        ss.map((s) => this.fileApi.relative(moduleRoot, this.fileApi.resolve(fileRoot.dir, s)).split("\\")
                            .join("/"));
                }
            }
            concat.add(r, iterator.content, iterator.map);
        }
        const code = `${concat.content}
        //# sourceMappingURL=${filePath.base}.pack.js.map
        `;
        await this.fileApi.writeString(outputFile, code);
        await this.fileApi.writeString(outputFile + ".map", concat.sourceMap);
        // minify...
        const result = await Terser.minify({
            [outputFile]: code
        }, {
            ecma: 2015,
            sourceMap: {
                content: JSON.parse(concat.sourceMap),
                url: filePath.base + ".pack.min.js.map"
            },
            keep_classnames: true,
            mangle: false,
            compress: false
        });
        await this.fileApi.writeString(outputFileMin, result.code);
        await this.fileApi.writeString(outputFileMin + ".map", result.map);
        return dependentFiles;
    }
    async writeFile(f, name) {
        if (this.done[f]) {
            return;
        }
        this.done[f] = true;
        if (name === "reflect-metadata") {
            f = f + "/Reflect";
        }
        f = f.split("\\").join("/");
        if (f.endsWith(".js")) {
            f = f.substring(0, f.length - 3);
        }
        name = name.split("\\").join("/");
        if (this.done[name]) {
            return;
        }
        this.done[name] = true;
        this.header.push(name);
        if (/\.(jpg|jpeg|svg|png|json|html|mp4|mp3|gif)$/i.test(name)) {
            return;
        }
        const fileContent = await this.fileApi.readString(f + ".js");
        const dependencies = DefineVisitor_1.default.parse(fileContent);
        if (!this.appPath && dependencies) {
            if (dependencies.find((v) => v.toString() === "@web-atoms/core/dist/xf/controls/AtomXFControl")) {
                this.appPath = "@web-atoms/core/dist/xf/XFApp";
            }
            if (dependencies.find((v) => v.toString() === "@web-atoms/core/dist/web/controls/AtomControl")) {
                this.appPath = "@web-atoms/core/dist/web/WebApp";
            }
        }
        if (dependencies && dependencies.length > 0) {
            const ignore = ["reflect-metadata", "global", "tslib"];
            const ds = dependencies
                .map((s) => s.startsWith(".") ? DeclarationParser_1.default.resolveRelativePath(s, name) : s)
                .filter((s) => ignore.indexOf(s) === -1)
                .map((s) => DeclarationParser_1.default.parsePackage(s))
                .map((s) => ({
                path: this.packageConfig.name === s.name ?
                    this.fileApi.join(this.root, s.path) :
                    this.fileApi.join(this.root + "//node_modules//" + s.name, s.path),
                module: s
            }));
            for (const iterator of ds) {
                const { path } = iterator;
                if (this.done[path]) {
                    continue;
                }
                if (path.endsWith(".less")) {
                    let name = "global";
                    // inject css...
                    if (path.endsWith(".global-low.less")) {
                        name = "globalLow";
                    }
                    if (path.endsWith(".global-high.less")) {
                        name = "globalHigh";
                    }
                    if (path.endsWith(".local.less")) {
                        name = "local";
                    }
                    if (path.endsWith(".local-low.less")) {
                        name = "localLow";
                    }
                    if (path.endsWith(".local-high.less")) {
                        name = "localHigh";
                    }
                    const less = (this.lessNodes[name] ??= new PackedLessFile_1.default(`${this.file}.pack.${name}.less`));
                    await less.append(path);
                    this.done[path] = true;
                    continue;
                }
                if (path.endsWith(".css")) {
                    let name = "global";
                    // inject css...
                    if (path.endsWith(".global-low.css")) {
                        name = "globalLow";
                    }
                    if (path.endsWith(".global-high.css")) {
                        name = "globalHigh";
                    }
                    if (path.endsWith(".local.css")) {
                        name = "local";
                    }
                    if (path.endsWith(".local-low.css")) {
                        name = "localLow";
                    }
                    if (path.endsWith(".local-high.css")) {
                        name = "localHigh";
                    }
                    const css = (this.cssNodes[name] ??= new PackedCssFile_1.default(`${this.file}.pack.${name}.css`));
                    await css.append(path);
                    this.done[path] = true;
                    continue;
                }
                await this.writeFile(path, iterator.module.fullPath);
            }
        }
        this.content.push({ content: `
    AmdLoader.instance.setup("${name}");
` });
        this.content.push(await this.jsFile(f + ".js", fileContent));
    }
    async jsFile(file, content) {
        let st = null;
        if (!content) {
            content = await this.fileApi.readString(file);
        }
        if (file) {
            st = this.fileApi.statSync(file);
        }
        // check last line..
        const lines = content.split("\n")
            .map((s) => s.trim());
        const srcMap = "//# sourceMappingURL=";
        let mapPath = "";
        let map;
        while (true && lines.length) {
            const last = lines.pop();
            if (last && last.startsWith(srcMap)) {
                mapPath = last.substring(srcMap.length);
                break;
            }
        }
        if (mapPath) {
            const parsedPath = this.fileApi.parse(file);
            mapPath = this.fileApi.join(parsedPath.dir, mapPath);
            if (await this.fileApi.exists(mapPath)) {
                mapPath = await this.fileApi.readString(mapPath);
                map = JSON.parse(mapPath);
            }
        }
        return {
            file,
            content,
            fileMTime: st ? st.mtimeMs : 0,
            map
        };
    }
}
exports.default = FilePacker;
//# sourceMappingURL=FilePacker.js.map