"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get BUILD_TARGETS () {
        return BUILD_TARGETS;
    },
    get BuildTarget () {
        return BuildTarget;
    },
    get fetchInfo () {
        return fetchInfo;
    },
    get getCommands () {
        return getCommands;
    },
    get getCompilerClient () {
        return getCompilerClient;
    },
    get isValidNpmPackageName () {
        return isValidNpmPackageName;
    },
    get isValidPipPackageName () {
        return isValidPipPackageName;
    },
    get loadQpcConfig () {
        return loadQpcConfig;
    },
    get tryLoadQpcConfig () {
        return tryLoadQpcConfig;
    }
});
function _commander() {
    const data = require("commander");
    _commander = function() {
        return data;
    };
    return data;
}
function _schema_grpc_pb() {
    const data = require("~/compiler/schema_grpc_pb");
    _schema_grpc_pb = function() {
        return data;
    };
    return data;
}
function _schema_pb() {
    const data = /*#__PURE__*/ _interop_require_wildcard(require("~/compiler/schema_pb"));
    _schema_pb = function() {
        return data;
    };
    return data;
}
function _util() {
    const data = require("util");
    _util = function() {
        return data;
    };
    return data;
}
function _assert() {
    const data = require("~/base/js/assert");
    _assert = function() {
        return data;
    };
    return data;
}
function _chalk() {
    const data = /*#__PURE__*/ _interop_require_default(require("chalk"));
    _chalk = function() {
        return data;
    };
    return data;
}
function _glob() {
    const data = require("glob");
    _glob = function() {
        return data;
    };
    return data;
}
function _ora() {
    const data = /*#__PURE__*/ _interop_require_default(require("ora"));
    _ora = function() {
        return data;
    };
    return data;
}
function _config() {
    const data = require("~/compiler/config");
    _config = function() {
        return data;
    };
    return data;
}
function _path() {
    const data = require("path");
    _path = function() {
        return data;
    };
    return data;
}
function _fs() {
    const data = require("~/base/node/fs");
    _fs = function() {
        return data;
    };
    return data;
}
const _exceptions = require("./exceptions");
function _promises() {
    const data = require("fs/promises");
    _promises = function() {
        return data;
    };
    return data;
}
function _os() {
    const data = require("~/base/node/os");
    _os = function() {
        return data;
    };
    return data;
}
function _fs1() {
    const data = require("fs");
    _fs1 = function() {
        return data;
    };
    return data;
}
function _network() {
    const data = require("~/base/node/network");
    _network = function() {
        return data;
    };
    return data;
}
const _profile = require("./profile");
function _axios() {
    const data = /*#__PURE__*/ _interop_require_default(require("axios"));
    _axios = function() {
        return data;
    };
    return data;
}
function _python() {
    const data = require("~/base/node/python");
    _python = function() {
        return data;
    };
    return data;
}
function _exec() {
    const data = require("~/base/node/exec");
    _exec = function() {
        return data;
    };
    return data;
}
const _utils = require("./utils");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
function _getRequireWildcardCache(nodeInterop) {
    if (typeof WeakMap !== "function") return null;
    var cacheBabelInterop = new WeakMap();
    var cacheNodeInterop = new WeakMap();
    return (_getRequireWildcardCache = function(nodeInterop) {
        return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
    })(nodeInterop);
}
function _interop_require_wildcard(obj, nodeInterop) {
    if (!nodeInterop && obj && obj.__esModule) {
        return obj;
    }
    if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
        return {
            default: obj
        };
    }
    var cache = _getRequireWildcardCache(nodeInterop);
    if (cache && cache.has(obj)) {
        return cache.get(obj);
    }
    var newObj = {
        __proto__: null
    };
    var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
    for(var key in obj){
        if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
            var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
            if (desc && (desc.get || desc.set)) {
                Object.defineProperty(newObj, key, desc);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    newObj.default = obj;
    if (cache) {
        cache.set(obj, newObj);
    }
    return newObj;
}
const BUILD_TARGETS = [
    ..._config().TARGETS,
    "python",
    "node",
    "web",
    "wasm",
    "js"
];
(function(BuildTarget) {
    BuildTarget.tryIntoTarget = (buildTarget)=>{
        if (_config().TARGETS.includes(buildTarget)) {
            return buildTarget;
        }
        const arch = process.arch;
        {
            if (buildTarget === "python" && (0, _os().isMacOs)() && arch === "x64") {
                return "python-x86_64-macos";
            }
            if (buildTarget === "python" && (0, _os().isMacOs)() && arch === "arm64") {
                return "python-arm64-macos";
            }
            if (buildTarget === "python" && (0, _os().isLinux)() && arch === "x64") {
                return "python-x86_64-linux";
            }
            if (buildTarget === "python" && (0, _os().isLinux)() && arch === "arm64") {
                return "python-arm64-linux";
            }
            if (buildTarget === "python" && (0, _os().isWindows)() && arch === "x64") {
                return "python-x86_64-windows";
            }
            //
            if (buildTarget === "node" && (0, _os().isMacOs)() && arch === "x64") {
                return "node-x86_64-macos";
            }
            if (buildTarget === "node" && (0, _os().isMacOs)() && arch === "arm64") {
                return "node-arm64-macos";
            }
            if (buildTarget === "node" && (0, _os().isLinux)() && arch === "x64") {
                return "node-x86_64-linux";
            }
            if (buildTarget === "node" && (0, _os().isLinux)() && arch === "arm64") {
                return "node-arm64-linux";
            }
            if (buildTarget === "node" && (0, _os().isWindows)() && arch === "x64") {
                return "node-x86_64-windows";
            }
            //
            if (buildTarget === "web" || buildTarget === "wasm") {
                return "wasm-unknown-unknown";
            }
            if (buildTarget === "js") {
                return "js-universal";
            }
        }
        return;
    };
})(BuildTarget || (BuildTarget = {}));
const getCompilerClient = async ()=>{
    const profile = await _profile.Profile.load();
    const client = await profile.getClient();
    return [
        new (_schema_grpc_pb()).CompilerApiClient((0, _assert().unwrap)(client.config.grpcApiBase), (0, _assert().unwrap)(client.config.grpcCredentials), (0, _assert().unwrap)(client["_grpcOptions"])),
        client["grpcMetadata"]
    ];
};
const fetchInfo = async (client)=>{
    const info = await (0, _util().promisify)(client.info.bind(client))(new (_schema_pb()).InfoRequest());
    return {
        version: info.getVersion(),
        buildDate: (0, _assert().unwrap)(info.getBuildTime()?.toDate())
    };
};
const isValidNpmPackageName = (name)=>{
    if (name == null) return false;
    name = name.trim();
    if (typeof name !== "string" || name.length === 0 || name.length > 214) return false;
    if (/[A-Z]/.test(name)) return false; // no capitals
    if (/^[._]/.test(name)) return false; // no leading . or _
    if (/\s/.test(name)) return false; // no whitespace
    // scoped package?
    if (name.startsWith("@")) {
        const [scope, pkg] = name.split("/");
        if (!scope || !pkg) return false; // need exactly @scope/pkg
        const scopeOk = /^@[a-z0-9][a-z0-9._-]*$/.test(scope);
        const pkgOk = /^[a-z0-9][a-z0-9._-]*$/.test(pkg);
        return scopeOk && pkgOk;
    }
    // unscoped
    return /^[a-z0-9][a-z0-9._-]*$/.test(name);
};
const isValidPipPackageName = (name)=>{
    if (name == null) return false;
    name = name.trim();
    if (typeof name !== "string" || name.length === 0 || name.length > 255) return false;
    // core rule: first/last char alnum; middle chars alnum, dot, dash, underscore
    return /^[A-Za-z0-9]+([A-Za-z0-9._-]*[A-Za-z0-9])?$/.test(name);
};
const tryLoadQpcConfig = async (path)=>{
    let config = (0, _config().getInitConfig)();
    if (await (0, _fs().exists)(path)) {
        config = (0, _config().mergeConfigs)(config, await (0, _fs().readJson)(path));
    }
    return config;
};
const loadQpcConfig = async (path)=>{
    return (0, _assert().unwrap)(await tryLoadQpcConfig(path), `QPC config file not found at "${_chalk().default.yellow(path)}"`);
};
const collectSrcPaths = async (config, cwd)=>{
    const excludes = [
        ...config.exclude ?? []
    ];
    const includes = [
        ...config.include ?? []
    ];
    const srcPaths = [];
    srcPaths.push(...await Promise.all(includes.map((r)=>(0, _glob().glob)(r, {
            cwd,
            ignore: excludes
        }))).then((r)=>r.flat()));
    return srcPaths;
};
const readSrcFiles = async (relPaths, dir)=>{
    return Promise.all(relPaths.map(async (path)=>{
        const file = new (_schema_pb()).File();
        const data = await (0, _promises().readFile)((0, _path().resolve)(dir, path));
        file.setPath(path);
        file.setData(data);
        return file;
    }));
};
const printOutcoming = (items)=>{
    if (items.length === 0) return;
    console.log(_chalk().default.blackBright(items.map((r)=>`← ${r}`).join("\n")));
};
const printIncoming = (items)=>{
    if (items.length === 0) return;
    console.log(_chalk().default.blackBright(items.map((r)=>`→ ${r}`).join("\n")));
};
const check = async ({ cwd, config: configPath, verbose })=>{
    const profile = await _profile.Profile.load();
    await profile.ping();
    cwd ??= process.cwd();
    verbose && console.log(_chalk().default.blackBright(`CWD: ${cwd}`));
    const [compilerClient, grpcMetadata] = await getCompilerClient();
    const qpcConfig = await loadQpcConfig(configPath ?? (0, _path().resolve)(cwd, _config().QPC_CONFIG_FILENAME));
    const srcPaths = await collectSrcPaths(qpcConfig, cwd);
    verbose && printOutcoming(srcPaths);
    const srcFiles = await readSrcFiles(srcPaths, cwd);
    const req = new (_schema_pb()).CheckRequest();
    req.setQpcConfig(JSON.stringify(qpcConfig));
    req.setFilesList(srcFiles);
    let pb = (0, _ora().default)(`Checking`).start();
    let ok = true;
    const res = await new Promise((resolve, reject)=>{
        compilerClient.check(req, grpcMetadata, (err, data)=>{
            if (err) return reject(err);
            resolve(data);
        });
    });
    ok = res.getOk();
    const logs = res.getMessage();
    pb.stop();
    if (logs?.length) {
        console.log(logs);
    }
    if (ok) {
        pb.succeed(`Ok`);
    } else {
        pb.fail(`Failed`);
        process.exitCode = 1;
    }
    if (verbose) {
        console.log(`Request ID: ${_chalk().default.magentaBright(res.getRequestId())}`);
    }
};
const writeApiFile = async (file, dir, axiosInstance)=>{
    const path = (0, _path().resolve)(dir, file.getPath());
    (0, _fs1().mkdirSync)((0, _path().dirname)(path), {
        recursive: true
    });
    if (file.hasData()) {
        const data = file.getData_asU8();
        await (0, _promises().writeFile)(path, data);
    } else if (file.hasUrl()) {
        try {
            await (0, _network().downloadFileToPath)((0, _assert().unwrap)(file.getUrl()), path, axiosInstance);
        } catch (e) {
            if (_axios().default.isAxiosError(e)) {
                throw new _exceptions.CliError(`Could not download file from ${file.getUrl()}\n${e.message}`);
            }
            throw e;
        }
    } else {
        throw new _exceptions.CliError(`File "${file.getPath()}" has no data or URL`);
    }
};
const getApiFileFlags = (file)=>{
    const flags = file.getFlags();
    return {
        pythonWheel: Boolean(flags & _schema_pb().FileFlag.FILE_FLAG_PYTHON_WHEEL),
        npmTar: Boolean(flags & _schema_pb().FileFlag.FILE_FLAG_NPM_TAR)
    };
};
const build = async ({ target: rawTarget, cwd, config: configPath, emit, outDir, skipInstall, skipTest, verbose })=>{
    if (rawTarget == null && !emit) {
        throw new _exceptions.CliError(`You must specify --target or use --emit option`);
    }
    let target;
    if (rawTarget != null) {
        target = BuildTarget.tryIntoTarget(rawTarget);
        if (target == null) throw new _exceptions.CliError(`Invalid target: ${rawTarget}`);
    }
    const profile = await _profile.Profile.load();
    await profile.ping();
    const client = await profile.getClient();
    cwd ??= process.cwd();
    verbose && console.log(_chalk().default.blackBright(`CWD: ${cwd}`));
    const pythonPath = await (0, _python().locatePython)();
    const pythonVersion = await (0, _python().getPythonVersion)(pythonPath);
    client["_clientInfo"]["python"] = pythonVersion;
    const [compilerClient, grpcMetadata] = await getCompilerClient();
    const qpcConfig = await loadQpcConfig(configPath ?? (0, _path().resolve)(cwd, _config().QPC_CONFIG_FILENAME));
    qpcConfig.py ??= {};
    qpcConfig.js ??= {};
    qpcConfig.node ??= {};
    qpcConfig.web ??= {};
    qpcConfig.emit ||= emit;
    qpcConfig.outDir = outDir ?? qpcConfig.outDir;
    if (skipInstall) qpcConfig.install = false;
    if (skipTest) qpcConfig.test = false;
    if (target?.startsWith("python") && qpcConfig.install) {
        if (pythonPath == null) throw new _exceptions.CliError(`Python not found. Install python and or it to PATH`);
        {
            const res = await (0, _exec().exec)({
                command: `${pythonPath} -m pip show qpace`
            });
            if (res.exitCode !== 0) {
                throw new _exceptions.CliError(`Python package "qpace" not found. Install it using ${_chalk().default.yellowBright(`"${pythonPath} -m pip install qpace --break-system-packages"`)}`);
            }
        }
    }
    // console.log("XD", qpcConfig);
    // process.exit();
    const srcPaths = await collectSrcPaths(qpcConfig, cwd);
    verbose && printOutcoming(srcPaths);
    const srcFiles = await readSrcFiles(srcPaths, cwd);
    const req = new (_schema_pb()).BuildRequest();
    req.setQpcConfig(JSON.stringify(qpcConfig));
    req.setFilesList(srcFiles);
    if (target != null) req.setTarget(target);
    let pb;
    pb = (0, _ora().default)(`Checking`).start();
    let ok = true;
    const stream = compilerClient.build(req, grpcMetadata);
    const fail = (msg)=>{
        pb?.fail(msg ?? `Failed`);
        ok = false;
    };
    await new Promise((_resolve, _reject)=>{
        stream.on("error", (err)=>{
            console.error(_chalk().default.redBright(err));
            fail();
            _reject(err);
        });
        stream.on("data", async (e)=>{
            if (e.hasCheckEnd()) {
                const data = e.getCheckEnd();
                const reqId = data.getRequestId();
                if (verbose) {
                    console.log(_chalk().default.blackBright(`\nRequest ID: ${_chalk().default.magentaBright(reqId)}`));
                }
                ok = data.getOk();
                pb.clear();
                const logs = data.getMessage();
                if (logs?.length) console.log(`${logs}\n`);
                if (!ok) _resolve();
                return;
            }
            if (e.hasBuildStart()) {
                pb.start(`Building ${_chalk().default.blueBright(target)}`);
                return;
            }
            if (e.hasBuildEnd()) {
                const data = e.getBuildEnd();
                const files = data.getFilesList();
                const profile = await _profile.Profile.load();
                const client = await profile.getClient();
                await Promise.all(files.map((f)=>writeApiFile(f, cwd, (0, _assert().unwrap)(client["http"]))));
                if (data.getOk()) {
                    if (target?.includes("python")) {
                        const pythonWheelFile = files.find((f)=>getApiFileFlags(f).pythonWheel);
                        if (pythonWheelFile == null) {
                            fail(`Python wheel file not produced`);
                        }
                        if (pythonWheelFile != null && qpcConfig.install) {
                            pb.text = `Installing Python wheel`;
                            const path = (0, _path().resolve)(cwd, pythonWheelFile.getPath());
                            {
                                const res = await (0, _exec().exec)({
                                    command: `${pythonPath} -m pip install "${path}" --break-system-packages`,
                                    io: verbose
                                });
                                if (res.exitCode !== 0 || res.stdout.includes("ERROR")) {
                                    fail(`Failed to install Python wheel`);
                                    process.stdout.write(res.stdout);
                                    process.stderr.write(res.stderr);
                                }
                            }
                            if (ok && qpcConfig.test) {
                                pb.text = `Testing Python wheel`;
                                const res = await (0, _exec().exec)({
                                    command: `${pythonPath} -c "import ${(0, _config().tryGetPythonPackageName)(qpcConfig.py)}"`,
                                    io: verbose
                                });
                                if (res.exitCode !== 0 || res.stdout.includes("ERROR")) {
                                    fail(`Failed Python wheel test`);
                                    process.stdout.write(res.stdout);
                                    process.stderr.write(res.stderr);
                                }
                            }
                        }
                    } else if (target?.includes("node") || target?.includes("wasm") || target === "js-universal") {
                        const npmTarFile = files.find((f)=>getApiFileFlags(f).npmTar);
                        if (npmTarFile == null) {
                            fail(`NPM tar file not produced`);
                        }
                        if (npmTarFile != null && qpcConfig.install) {
                            pb.text = `Installing NPM package`;
                            const path = (0, _path().resolve)(cwd, npmTarFile.getPath());
                            const pkgManager = await (0, _utils.detectNodePackageManager)(cwd);
                            const res = await (0, _utils.installNodePackage)(`"${path}"`, {
                                cwd,
                                manager: pkgManager,
                                verbose
                            });
                            if (res.exitCode !== 0) {
                                fail(`Failed to install NPM package`);
                                process.stdout.write(res.stdout);
                                process.stderr.write(res.stderr);
                            }
                        }
                    }
                } else {
                    fail();
                }
                if (ok) pb?.succeed(`Ok`);
                verbose && printIncoming(files.map((f)=>f.getPath()));
                const logs = data.getMessage();
                if (logs?.length) console.log(logs);
                _resolve();
                return;
            }
        });
    });
    if (ok && verbose) {
        console.log(_chalk().default.greenBright(`\nUse following:`));
        if (target?.includes("python")) {
            console.log(_chalk().default.greenBright(`import ${(0, _config().tryGetPythonPackageName)(qpcConfig.py)} as pine;`));
        } else if (target?.includes("node")) {
            console.log(_chalk().default.greenBright(`import * as pine from "${(0, _config().tryGetJsPackageName)(qpcConfig.js)}/node";`));
        } else if (target?.includes("node")) {
            console.log(_chalk().default.greenBright(`import * as pine from "${(0, _config().tryGetJsPackageName)(qpcConfig.js)}/web";`));
        } else if (target === "js-universal") {
            console.log(_chalk().default.greenBright(`import * as pine from "${(0, _config().tryGetJsPackageName)(qpcConfig.js)}/node|web";`));
        }
        console.log("");
    }
    if (!ok) {
        process.exitCode = 1;
    }
    pb?.stop();
};
const getCommands = ()=>{
    return [
        new (_commander()).Command("check").description("Analyzes the current project and reports errors, but doesn't build").option("--cwd <path>", `Project root directory`).option("--config <path>", `Path to the QPC config file`).option("--verbose", `Prints verbose output`, false).action(check),
        new (_commander()).Command("build").description("Compiles the project, produces artifacts like Python wheel, NPM package and optionally installs + tests them").addOption(new (_commander()).Option("--target <target>", `Target platform`).choices(BUILD_TARGETS)).option("--emit", `Emits compiled files. Default: "config.emit"`).option("--out-dir <path>", `Directory to emit compiled files and artifacts. Default: "config.outDir"`).option("--cwd <path>", `Project root directory`).option("--config <path>", `Path to the QPC config file`).option("--skip-install", `Skips installing artifact. "config.python|node.install"`).option("--skip-test", `Skips testing artifact. "config.python|node.test"`, false).option("--verbose", `Prints verbose output`, false).action(build)
    ];
};
var BuildTarget;
