import TableColumnType from "../../../Types/AnalyticsDatabase/TableColumnType";
import GreaterThan from "../../../Types/BaseDatabase/GreaterThan";
import GreaterThanOrEqual from "../../../Types/BaseDatabase/GreaterThanOrEqual";
import Includes from "../../../Types/BaseDatabase/Includes";
import LessThan from "../../../Types/BaseDatabase/LessThan";
import LessThanOrEqual from "../../../Types/BaseDatabase/LessThanOrEqual";
import LessThanOrNull from "../../../Types/BaseDatabase/LessThanOrNull";
import GreaterThanOrNull from "../../../Types/BaseDatabase/GreaterThanOrNull";
import NotEqual from "../../../Types/BaseDatabase/NotEqual";
import Search from "../../../Types/BaseDatabase/Search";
import OneUptimeDate from "../../../Types/Date";
import ObjectID from "../../../Types/ObjectID";
import { inspect } from "util";
export class Statement {
    constructor(strings = [""], values = []) {
        this.strings = strings;
        this.values = values;
    }
    get query() {
        var _a, _b;
        let query = this.strings.reduce((prev, curr, i) => {
            const param = this.values[i - 1];
            const dataType = typeof param === "string"
                ? "Identifier"
                : Statement.toColumnType(param);
            return prev + `{p${i - 1}:${dataType}}` + curr;
        });
        // dedent lines
        query = query.trimEnd();
        const minIndent = (_b = (_a = query.match(/\n( *)/g)) === null || _a === void 0 ? void 0 : _a.reduce((minIndent, indent) => {
            return Math.min(minIndent, indent.length - 1);
        }, Infinity)) !== null && _b !== void 0 ? _b : 0;
        query = query.replace(new RegExp(`\n {${minIndent}}`, "g"), "\n");
        query = query.trimStart();
        return query;
    }
    get query_params() {
        let index = 0;
        const returnObject = {};
        for (const v of this.values) {
            const finalValue = this.serializseValue(v);
            returnObject[`p${index}`] = finalValue;
            index++;
        }
        return returnObject;
    }
    serializseValue(v) {
        let finalValue = v;
        // if of type date, convert to database date
        if (typeof v === "string") {
            finalValue = v;
        }
        else if (Array.isArray(v.value)) {
            const tempArr = [];
            for (const val of v.value) {
                tempArr.push(this.serializseValue({
                    type: v.type,
                    value: val,
                }));
            }
            finalValue = tempArr;
        }
        else if (v.value instanceof ObjectID) {
            finalValue = v.value.toString();
        }
        else if (v.value instanceof Search) {
            finalValue = `%${v.value.toString()}%`;
        }
        else if (v.value instanceof LessThan ||
            v.value instanceof LessThanOrEqual ||
            v.value instanceof GreaterThan ||
            v.value instanceof GreaterThanOrEqual ||
            v.value instanceof LessThanOrNull ||
            v.value instanceof GreaterThanOrNull ||
            v.value instanceof NotEqual) {
            finalValue = v.value.value;
        }
        else if (v.value instanceof Includes) {
            if (v.type === TableColumnType.Text ||
                v.type === TableColumnType.ObjectID) {
                finalValue = v.value.values.map((val) => {
                    return `${val.toString()}`;
                });
            }
            else {
                finalValue = v.value.values;
            }
        }
        else if (v.value instanceof Date) {
            if (typeof v !== "string" && v.type === TableColumnType.DateTime64) {
                finalValue = OneUptimeDate.toClickhouseDateTime64(v.value);
            }
            else {
                finalValue = OneUptimeDate.toClickhouseDateTime(v.value);
            }
        }
        else {
            finalValue = v.value;
        }
        // serialize to date (only for string values that need parsing, not already processed Date objects)
        if (typeof v !== "string" &&
            v.type === TableColumnType.Date &&
            !(v.value instanceof Date)) {
            finalValue = OneUptimeDate.fromString(finalValue);
            finalValue = OneUptimeDate.toClickhouseDateTime(finalValue);
        }
        if (typeof v !== "string" &&
            v.type === TableColumnType.DateTime64 &&
            !(v.value instanceof Date)) {
            finalValue = OneUptimeDate.fromString(finalValue);
            finalValue = OneUptimeDate.toClickhouseDateTime64(finalValue);
        }
        return finalValue;
    }
    /**
     * Append an escaped SQL fragment. If you pass a raw String
     * it is appended as trusted SQL!
     */
    append(statement) {
        if (statement instanceof Statement) {
            this.strings[this.strings.length - 1] += statement.strings[0];
            this.strings.push(...statement.strings.slice(1));
            this.values.push(...statement.values);
        }
        else {
            this.strings[this.strings.length - 1] += statement;
        }
        return this;
    }
    // custom inspect for logging
    [Symbol.for("nodejs.util.inspect.custom")]() {
        return `Statement ${inspect({
            query: this.query,
            query_params: this.query_params,
        })}`;
    }
    static toColumnType(statementParam) {
        /*
         * ensure we have a mapping for all types (a missing mapping will
         * be a compile error)
         */
        const columnTypes = {
            [TableColumnType.Text]: "String",
            [TableColumnType.ObjectID]: "String",
            [TableColumnType.Boolean]: "Bool",
            [TableColumnType.Number]: "Int32",
            [TableColumnType.Decimal]: "Double",
            [TableColumnType.Date]: "DateTime",
            [TableColumnType.DateTime64]: "DateTime64(9)",
            [TableColumnType.JSON]: "JSON",
            [TableColumnType.ArrayNumber]: "Array(Int32)",
            [TableColumnType.ArrayText]: "Array(String)",
            [TableColumnType.LongNumber]: "Int128",
        };
        if (statementParam.value instanceof Includes) {
            const includesValues = statementParam.value.values;
            const isNumberArray = includesValues.every((v) => {
                return typeof v === "number";
            });
            if (isNumberArray) {
                return "Array(Int32)";
            }
            return "Array(String)";
        }
        return columnTypes[statementParam.type] || "String";
    }
}
/**
 * ClickHouse SQL template literal tag.
 *
 * String substitution values are interpereted as Identifiers (e.g. table or column names),
 * other substitution values must be StatementParameters.
 */
export function SQL(strings, ...values) {
    return new Statement(strings.slice(0), values.slice(0));
}
//# sourceMappingURL=Statement.js.map