#!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __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 });
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const child_process = __importStar(require("child_process"));
const typescript_1 = __importDefault(require("typescript"));
const commander_1 = __importDefault(require("commander"));
const utils_1 = require("./utils");
const make_fake_expression_1 = require("./make_fake_expression");
const directiveParser_1 = require("./directiveParser");
const default_cost_pattern_source = utils_1.default_cost_pattern.source;
const default_tags_string = Object.entries(utils_1.default_tags)
    .map((it) => it.join("="))
    .join(",");
commander_1.default
    .option("-p, --project <string>", "The project path or tsconfig.json, defaults to: ./ .", "./")
    .option("-e, --exclude <regexp>", "The regexp to exclude files, defaults to: node_modules .", "node_modules")
    .option("-t, --tags <string>", `The tags you used in you ts file, defaults to: ${default_tags_string} .`, default_tags_string)
    .option("-m, --error-cost <int>", "Throw error if explain cost exceeds treshold.")
    .option("--emit-out-dir <string>", "Path, where sqls will be emitted", "./emit-sql")
    .option("--warn-cost <int>", "Log warning if explain cost exceeds treshold.")
    .option("--info-cost <int>", "Log info if explain cost exceeds treshold.")
    .option("--cost-pattern <regexp>", `The regexp used to extract cost from command stdout, defaults to: ${default_cost_pattern_source} .`, default_cost_pattern_source)
    .option("-w, --watch", `Watch mode of cli`, false)
    .arguments("[command...]")
    .description("Explain all your sqls in your code to test them. Eg: ts-sql-plugin -p ./my_ts_projet psql -c", {
    command: "The command to be run to explain the faked sql, like: psql.",
    args: "The arguments passed to the command, like: -c. The faked sql will be added as the last argument.",
})
    .action((_command) => {
    if (_command.length === 0) {
        _command = utils_1.default_command;
    }
    else {
        _command = commander_1.default.rawArgs.slice(utils_1.index_of_array(commander_1.default.rawArgs, _command));
    }
    const config = commander_1.default.opts();
    config.error_cost = config.errorCost;
    config.warn_cost = config.warnCost;
    config.info_cost = config.infoCost;
    config.cost_pattern = config.costPattern;
    const exclude = new RegExp(config.exclude);
    const tags = Object.assign({}, utils_1.default_tags, config.tags
        .split(",")
        .map((s) => s.split("="))
        .reduce((acc, [k, v]) => {
        acc[k] = v;
        return acc;
    }, {}));
    const cost_pattern = new RegExp(config.cost_pattern);
    const project_path = path.dirname(config.project);
    const tsconfig_path = path.join(project_path, "tsconfig.json");
    const { config: tsconfig } = typescript_1.default.parseConfigFileTextToJson(tsconfig_path, fs.readFileSync(tsconfig_path, { encoding: "utf8" }));
    const watchHost = config.watch
        ? typescript_1.default.createWatchCompilerHost(utils_1.get_all_ts_files(project_path), tsconfig.compilerOptions, typescript_1.default.sys)
        : null;
    const watchProgram = config.watch ? typescript_1.default.createWatchProgram(watchHost) : null;
    const initProgram = config.watch
        ? watchProgram.getProgram().getProgram()
        : typescript_1.default.createProgram(utils_1.get_all_ts_files(project_path), tsconfig.compilerOptions);
    let fake_expression = make_fake_expression_1.make_fake_expression(initProgram.getTypeChecker(), tags);
    if (config.watch) {
        const onChangeFile = (fileName) => {
            const file = watchProgram.getProgram().getSourceFile(fileName);
            if (file && !exclude.test(file.fileName)) {
                fake_expression = make_fake_expression_1.make_fake_expression(watchProgram.getProgram().getProgram().getTypeChecker(), tags);
                delint(file);
            }
        };
        const watchFile = (sourceFile) => {
            watchHost.watchFile(sourceFile.fileName, (fileName, event) => {
                if (event !== typescript_1.default.FileWatcherEventKind.Deleted) {
                    onChangeFile(fileName);
                }
            });
        };
        watchProgram.getProgram().getSourceFiles().forEach(watchFile);
    }
    let has_error = false;
    initProgram.getSourceFiles().forEach((f) => {
        if (!exclude.test(f.fileName)) {
            delint(f);
        }
    });
    if (has_error) {
        console.error("Your code can not pass all sql test!!!");
        process.exit(1);
    }
    function delint(sourceFile) {
        delintNode(sourceFile);
        function delintNode(node) {
            var _a;
            if (node.kind === typescript_1.default.SyntaxKind.TaggedTemplateExpression) {
                let n = node;
                if (n.tag.getText() === tags.sql) {
                    let query_configs = fake_expression(n);
                    for (const qc of query_configs) {
                        let s = qc.text.replace(/\?\?/gm, "null");
                        const directives = directiveParser_1.parseDirectives(s);
                        const emitDir = directives.find((d) => d.directive === "emit");
                        if (emitDir) {
                            const fileName = (_a = emitDir.arg) !== null && _a !== void 0 ? _a : crypto.createHash("sha1").update(s).digest("hex");
                            const filePath = `${config.emitOutDir}/${fileName}.sql`;
                            fs.writeFile(filePath, s, (err) => {
                                if (err) {
                                    console.error(`Error occured, when emitting file "${filePath}"`);
                                }
                            });
                        }
                        let p = child_process.spawnSync(_command[0], _command.slice(1).concat(`EXPLAIN ${s}`));
                        if (p.status) {
                            const stderr_str = p.stderr.toString("utf8");
                            has_error = true;
                            utils_1.report(sourceFile, node, stderr_str);
                            break;
                        }
                        if ((config.error_cost || config.warn_cost || config.info_cost) &&
                            !directives.some((d) => d.directive === "ignore-cost")) {
                            const stdout_str = p.stdout.toString("utf8");
                            const match = stdout_str.match(cost_pattern);
                            if (match) {
                                const [_, cost_str] = match;
                                const cost = Number(cost_str);
                                if (cost > config.error_cost) {
                                    has_error = true;
                                    utils_1.report(sourceFile, node, `Error: explain cost is too high: ${cost}\n${s}`, 3);
                                    break;
                                }
                                else if (cost > config.warn_cost) {
                                    utils_1.report(sourceFile, node, `Warn: explain cost is at warning: ${cost}\n${s}`, 2);
                                }
                                else if (cost > config.info_cost) {
                                    utils_1.report(sourceFile, node, `Info: explain cost is ok: ${cost}\n${s}`, 1);
                                }
                            }
                        }
                    }
                }
            }
            typescript_1.default.forEachChild(node, delintNode);
        }
    }
});
commander_1.default.parse(process.argv);
//# sourceMappingURL=cli.js.map