"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFiltered = getFiltered;
const client_1 = __importDefault(require("./client"));
const console_1 = require("@b/utils/console");
const blockchain_1 = require("../blockchain"); // adjust path as needed
const cassandra_driver_1 = require("cassandra-driver");
const error_1 = require("@b/utils/error");
// Supported operators for basic comparisons.
const operatorMap = {
    equal: "=",
    greaterThan: ">",
    greaterThanOrEqual: ">=",
    lessThan: "<",
    lessThanOrEqual: "<=",
    like: "LIKE", // for string columns only
    notLike: "NOT LIKE",
};
/**
 * Helper to quote a column name if it contains uppercase letters.
 */
function quoteColumn(key) {
    return /[A-Z]/.test(key) ? `"${key}"` : key;
}
/**
 * Helper to format a 32-character hex string into standard UUID format: 8-4-4-4-12.
 */
function formatUuid(hexStr) {
    return (hexStr.substr(0, 8) +
        "-" +
        hexStr.substr(8, 4) +
        "-" +
        hexStr.substr(12, 4) +
        "-" +
        hexStr.substr(16, 4) +
        "-" +
        hexStr.substr(20, 12));
}
/**
 * Builds a WHERE clause from a filter object.
 *
 * @param filter - The filter object.
 * @param nonStringLikeColumns - An array of columns (e.g. UUID columns) that should not use plain LIKE.
 *
 * For non-string columns:
 * - "startsWith" or "like": If the value is shorter than a full UUID, a range query is generated.
 * - Unsupported operators ("notEqual", "endsWith", "substring", "regexp", "notRegexp") throw an error.
 */
function buildWhereClause(filter, nonStringLikeColumns = []) {
    if (typeof filter === "string") {
        try {
            filter = JSON.parse(decodeURIComponent(filter));
        }
        catch (e) {
            filter = {};
        }
    }
    if (!filter || typeof filter !== "object") {
        return { whereClause: "", values: [] };
    }
    const clauses = [];
    const values = [];
    for (const key in filter) {
        if (Object.prototype.hasOwnProperty.call(filter, key)) {
            const columnName = quoteColumn(key);
            const condition = filter[key];
            if (typeof condition === "object" &&
                condition.operator &&
                condition.value !== undefined) {
                // Handle "startsWith" / "like" operators.
                if (condition.operator === "startsWith" ||
                    condition.operator === "like") {
                    if (nonStringLikeColumns.includes(key)) {
                        // For non-string columns (e.g. UUID), simulate "startsWith" with a range query.
                        const prefix = condition.value.toString();
                        if (prefix.length < 36) {
                            const clean = prefix.replace(/-/g, "");
                            const lowerClean = clean.padEnd(32, "0");
                            const upperClean = clean.padEnd(32, "f");
                            const lowerStr = formatUuid(lowerClean);
                            const upperStr = formatUuid(upperClean);
                            try {
                                const lowerBound = cassandra_driver_1.types.Uuid.fromString(lowerStr);
                                const upperBound = cassandra_driver_1.types.Uuid.fromString(upperStr);
                                clauses.push(`${columnName} >= ?`);
                                values.push(lowerBound);
                                clauses.push(`${columnName} <= ?`);
                                values.push(upperBound);
                            }
                            catch (e) {
                                clauses.push(`${columnName} = ?`);
                                values.push(cassandra_driver_1.types.Uuid.fromString("00000000-0000-0000-0000-000000000000"));
                            }
                        }
                        else {
                            try {
                                const uuidVal = cassandra_driver_1.types.Uuid.fromString(condition.value);
                                clauses.push(`${columnName} = ?`);
                                values.push(uuidVal);
                            }
                            catch (e) {
                                clauses.push(`${columnName} = ?`);
                                values.push(cassandra_driver_1.types.Uuid.fromString("00000000-0000-0000-0000-000000000000"));
                            }
                        }
                        continue;
                    }
                    else {
                        clauses.push(`${columnName} LIKE ?`);
                        values.push(`${condition.value}%`);
                        continue;
                    }
                }
                // For non-string columns, reject unsupported operators.
                if (nonStringLikeColumns.includes(key)) {
                    const unsupported = [
                        "notEqual",
                        "endsWith",
                        "substring",
                        "regexp",
                        "notRegexp",
                    ];
                    if (unsupported.includes(condition.operator)) {
                        throw (0, error_1.createError)({
                            statusCode: 400,
                            message: `Operator "${condition.operator}" is not supported for column "${key}" of non-string type`
                        });
                    }
                }
                const op = operatorMap[condition.operator];
                if (!op)
                    continue;
                clauses.push(`${columnName} ${op} ?`);
                values.push(condition.value);
            }
            else {
                clauses.push(`${columnName} = ?`);
                values.push(condition);
            }
        }
    }
    return { whereClause: clauses.join(" AND "), values };
}
/**
 * Fetch records from a given table with filtering, sorting, and count-based pagination.
 *
 * If Cassandra cannot perform ORDER BY server-side (because partition keys aren’t restricted
 * or because the sort field isn’t a clustering column), an in-memory sort is applied.
 *
 * @param table - Table name.
 * @param query - Query object (may contain page, perPage, etc.).
 * @param filter - Filter conditions.
 * @param sortField - Field to sort on.
 * @param sortOrder - 'ASC' or 'DESC' (default: DESC).
 * @param perPage - Number of records per page.
 * @param allowFiltering - Whether to add ALLOW FILTERING.
 * @param keyspace - Optional keyspace.
 * @param partitionKeys - Array of partition key names (used for determining ORDER BY eligibility).
 * @param transformColumns - Array of columns to transform from BigInt.
 * @param nonStringLikeColumns - Array of columns (e.g. ["userId"]) to treat as non-string for LIKE operators.
 *
 * @returns An object with the transformed items and a pagination object.
 */
async function getFiltered({ table, query, filter, sortField = "createdAt", sortOrder = "DESC", perPage = 10, allowFiltering = true, keyspace, partitionKeys, transformColumns, nonStringLikeColumns = [], }) {
    const fullTableName = keyspace ? `${keyspace}.${table}` : table;
    const { whereClause, values } = buildWhereClause(filter, nonStringLikeColumns);
    const params = [...values];
    // Build count query.
    let countCql = `SELECT count(*) FROM ${fullTableName}`;
    if (whereClause) {
        countCql += ` WHERE ${whereClause}`;
    }
    if (allowFiltering) {
        countCql += ` ALLOW FILTERING`;
    }
    let totalItems = 0;
    try {
        const countResult = await client_1.default.execute(countCql, params, {
            prepare: true,
        });
        totalItems = Number(countResult.rows[0]["count"]) || 0;
    }
    catch (error) {
        console_1.logger.error("SCYLLA", "Error executing count query", error);
        throw (0, error_1.createError)({ statusCode: 500, message: "Error executing count query: " + error.message });
    }
    const currentPage = query.page ? Number(query.page) : 1;
    const offset = (currentPage - 1) * perPage;
    // Build main data query.
    let dataCql = `SELECT * FROM ${fullTableName}`;
    if (whereClause) {
        dataCql += ` WHERE ${whereClause}`;
    }
    let addOrderBy = false;
    if (sortField && partitionKeys && partitionKeys.length > 0) {
        addOrderBy = partitionKeys.every((pk) => {
            const filterValue = filter && filter[pk];
            return (filterValue &&
                (filterValue.operator === "equal" || filterValue.operator === "in"));
        });
    }
    if (addOrderBy) {
        dataCql += ` ORDER BY ${quoteColumn(sortField)} ${sortOrder.toUpperCase()}`;
    }
    if (allowFiltering) {
        dataCql += ` ALLOW FILTERING`;
    }
    let allRows = [];
    try {
        const dataResult = await client_1.default.execute(dataCql, params, { prepare: true });
        allRows = dataResult.rows;
    }
    catch (error) {
        console_1.logger.error("SCYLLA", "Error executing data query", error);
        throw (0, error_1.createError)({ statusCode: 500, message: "Error executing data query: " + error.message });
    }
    // If server-side sorting wasn't applied, do an in-memory sort.
    if (!addOrderBy && sortField) {
        allRows.sort((a, b) => {
            const aRaw = a[sortField];
            const bRaw = b[sortField];
            // Attempt to coerce to number.
            const aNum = Number(aRaw);
            const bNum = Number(bRaw);
            if (!isNaN(aNum) && !isNaN(bNum)) {
                return aNum - bNum;
            }
            return String(aRaw).localeCompare(String(bRaw));
        });
        if (sortOrder.toUpperCase() === "DESC") {
            allRows.reverse();
        }
    }
    let items = allRows.slice(offset, offset + perPage);
    const totalPages = Math.ceil(totalItems / perPage);
    if (transformColumns && transformColumns.length > 0) {
        items = items.map((row) => {
            transformColumns.forEach((col) => {
                if (row[col] !== undefined && row[col] !== null) {
                    try {
                        row[col] =
                            typeof row[col] === "bigint"
                                ? (0, blockchain_1.fromBigInt)(row[col])
                                : (0, blockchain_1.fromBigInt)(BigInt(row[col]));
                    }
                    catch (error) {
                        // Leave value as is on conversion failure.
                    }
                }
            });
            return row;
        });
    }
    return {
        items,
        pagination: { totalItems, currentPage, perPage, totalPages },
    };
}
