"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeCreate = void 0;
const tslib_1 = require("tslib");
const child_process_1 = tslib_1.__importDefault(require("child_process"));
const tsserverlibrary_1 = tslib_1.__importDefault(require("typescript/lib/tsserverlibrary"));
const typescript_template_language_service_decorator_1 = require("typescript-template-language-service-decorator");
const shell_quote_1 = tslib_1.__importDefault(require("shell-quote"));
const lunr_1 = tslib_1.__importDefault(require("lunr"));
const utils_1 = require("./utils");
const make_fake_expression_1 = require("./make_fake_expression");
const autocomplete_1 = require("./autocomplete");
const directiveParser_1 = require("./directiveParser");
function makeCreate(mod) {
    return function create(info) {
        const logger = (msg) => info.project.projectService.logger.info(`[ts-sql-plugin] ${msg}`);
        const config = utils_1.merge_defaults(info.config);
        const cost_pattern = new RegExp(config.cost_pattern);
        const proxy = new Proxy(info.languageService, {
            get(target, p, receiver) {
                if (p === "getSemanticDiagnostics") {
                    return function getSemanticDiagnostics(fileName) {
                        let origin_diagnostics = target.getSemanticDiagnostics(fileName);
                        const program = info.languageService.getProgram();
                        const fake_expression = make_fake_expression_1.make_fake_expression(program.getTypeChecker(), config.tags);
                        const source_file = program.getSourceFile(fileName);
                        const nodes = utils_1.find_all_nodes(source_file, n => n.kind === tsserverlibrary_1.default.SyntaxKind.TaggedTemplateExpression &&
                            n.tag.getText() === config.tags.sql);
                        const explain_rss = nodes.map(n => {
                            const make_diagnostic = (code, category, messageText) => ({
                                file: source_file,
                                start: n.getStart(),
                                length: n.getEnd() - n.getStart(),
                                source: "ts-sql-plugin",
                                code,
                                category,
                                messageText,
                            });
                            // one sql`select * from person ${xxx ? sql.raw`aaa` : sql.raw`bbb`}` may generate two sqls, need to be explained one by one
                            let query_configs = fake_expression(n);
                            for (const qc of query_configs) {
                                let s = qc.text.replace(/\?\?/gm, config.mock);
                                // ! Never pass unsanitized user input to child_process.execSync.
                                // let stdout = "";
                                // try {
                                //   stdout = child_process.execSync(`${config.command} 'EXPLAIN ${s}'`, { encoding: 'utf8' });
                                // } catch (error) {
                                //   return make_diagnostic(1, tss.DiagnosticCategory.Error, error.stderr + "\n" + s);
                                // }
                                const [_command, ..._command_args] = shell_quote_1.default
                                    .parse(config.command)
                                    .concat("EXPLAIN " + s);
                                const p = child_process_1.default.spawnSync(_command, _command_args, { encoding: "utf8" });
                                if (p.status) {
                                    return make_diagnostic(1, tsserverlibrary_1.default.DiagnosticCategory.Error, p.stderr + "\n" + s);
                                }
                                const directives = directiveParser_1.parseDirectives(s);
                                if ([config.error_cost, config.warn_cost, config.info_cost].some(it => it != void 0) &&
                                    !directives.some(x => x.directive === "ignore-cost")) {
                                    const match = p.stdout.match(cost_pattern);
                                    if (match) {
                                        const [_, cost_str] = match;
                                        const cost = Number(cost_str);
                                        if (cost > config.error_cost) {
                                            return make_diagnostic(1, tsserverlibrary_1.default.DiagnosticCategory.Error, `explain cost is too high: ${cost}\n${s}`);
                                        }
                                        if (cost > config.warn_cost) {
                                            return make_diagnostic(1, tsserverlibrary_1.default.DiagnosticCategory.Warning, `explain cost is at warning: ${cost}\n${s}`);
                                        }
                                        if (cost > config.info_cost) {
                                            return make_diagnostic(1, tsserverlibrary_1.default.DiagnosticCategory.Suggestion, `explain cost is ok: ${cost}\n${s}`);
                                        }
                                    }
                                    else {
                                        return make_diagnostic(1, tsserverlibrary_1.default.DiagnosticCategory.Error, `can not extract cost with cost_pattern: ${cost_pattern.source}\n${p.stdout}\n${s}`);
                                    }
                                }
                            }
                        });
                        return [...origin_diagnostics, ...explain_rss.filter(v => !!v)];
                    };
                }
                return target[p];
            },
        });
        if (!config.schema_command) {
            return proxy;
        }
        const [_schema_command, ..._schema_command_args] = shell_quote_1.default.parse(config.schema_command);
        const schema_info_p = child_process_1.default.spawnSync(_schema_command, _schema_command_args, { encoding: "utf8" });
        if (schema_info_p.status) {
            throw Error(schema_info_p.stderr);
        }
        const schema_info = schema_info_p.stdout
            .split("\n")
            .map(it => it.match(/\w+/g))
            .filter(it => (it === null || it === void 0 ? void 0 : it.length) === 3)
            .map((raw, id) => {
            const [schema, table, column] = raw;
            return { schema, table, column, id };
        });
        return typescript_template_language_service_decorator_1.decorateWithTemplateLanguageService(mod.typescript, proxy, info.project, new autocomplete_1.SqlTemplateAutocomplete(lunr_1.default(function () {
            this.ref("id");
            this.field("schema");
            this.field("table");
            this.field("column");
            schema_info.forEach(schema_info_item => this.add(schema_info_item));
        }), schema_info), { tags: [config.tags.sql, config.tags.raw], enableForStringWithSubstitutions: true });
    };
}
exports.makeCreate = makeCreate;
//# sourceMappingURL=create.js.map