var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import QueryHelper from "./QueryHelper";
import EqualToOrNull from "../../../Types/BaseDatabase/EqualToOrNull";
import GreaterThan from "../../../Types/BaseDatabase/GreaterThan";
import GreaterThanOrEqual from "../../../Types/BaseDatabase/GreaterThanOrEqual";
import InBetween from "../../../Types/BaseDatabase/InBetween";
import Includes from "../../../Types/BaseDatabase/Includes";
import IncludesAll from "../../../Types/BaseDatabase/IncludesAll";
import IncludesNone from "../../../Types/BaseDatabase/IncludesNone";
import StartsWith from "../../../Types/BaseDatabase/StartsWith";
import EndsWith from "../../../Types/BaseDatabase/EndsWith";
import NotContains from "../../../Types/BaseDatabase/NotContains";
import IsNull from "../../../Types/BaseDatabase/IsNull";
import LessThan from "../../../Types/BaseDatabase/LessThan";
import LessThanOrEqual from "../../../Types/BaseDatabase/LessThanOrEqual";
import NotEqual from "../../../Types/BaseDatabase/NotEqual";
import NotNull from "../../../Types/BaseDatabase/NotNull";
import Search from "../../../Types/BaseDatabase/Search";
import MultiSearch from "../../../Types/BaseDatabase/MultiSearch";
import TableColumnType from "../../../Types/Database/TableColumnType";
import ObjectID from "../../../Types/ObjectID";
import Typeof from "../../../Types/Typeof";
import { And } from "typeorm";
import { FindOperator } from "typeorm/find-options/FindOperator";
import CaptureSpan from "../../Utils/Telemetry/CaptureSpan";
import LessThanOrNull from "../../../Types/BaseDatabase/LessThanOrNull";
import GreaterThanOrNull from "../../../Types/BaseDatabase/GreaterThanOrNull";
import EqualTo from "../../../Types/BaseDatabase/EqualTo";
import PostgresAppInstance from "../../Infrastructure/PostgresDatabase";
export default class QueryUtil {
    static serializeQuery(modelType, query) {
        const model = new modelType();
        query = query;
        /*
         * Multi-field text search:
         * A MultiSearch operator on any key fans out into an ILIKE OR across the
         * listed entity fields. We hang the Raw expression off `_id` so it lands
         * in the WHERE clause without TypeORM treating the synthetic key as a
         * real column. Falls through silently if metadata is unavailable or no
         * fields resolve (e.g. property name typo).
         */
        for (const key in query) {
            const value = query[key];
            if (!(value instanceof MultiSearch)) {
                continue;
            }
            delete query[key];
            const ms = value;
            if (!ms.value || ms.fields.length === 0) {
                continue;
            }
            /*
             * Validate the requested fields against entity metadata so we only
             * emit identifiers TypeORM's post-processor can map back to real
             * columns (otherwise the alias.field token survives unescaped and
             * Postgres lowercases the table name → "missing FROM-clause entry").
             */
            const validPropertyNames = [];
            if (PostgresAppInstance.isConnected()) {
                const dataSource = PostgresAppInstance.getDataSource();
                if (dataSource) {
                    let entityMetadata;
                    try {
                        entityMetadata = dataSource.getMetadata(modelType);
                    }
                    catch (_a) {
                        entityMetadata = undefined;
                    }
                    if (entityMetadata) {
                        for (const fieldName of ms.fields) {
                            const column = entityMetadata.columns.find((c) => {
                                return c.propertyName === fieldName;
                            });
                            if (column) {
                                validPropertyNames.push(fieldName);
                            }
                        }
                    }
                }
            }
            if (validPropertyNames.length === 0) {
                continue;
            }
            const rawFilter = QueryHelper.multiSearch(validPropertyNames, ms.value);
            const existingIdFilter = query._id;
            if (existingIdFilter instanceof FindOperator) {
                query._id = And(existingIdFilter, rawFilter);
            }
            else if (existingIdFilter &&
                typeof existingIdFilter === Typeof.String) {
                query._id = And(QueryHelper.equalTo(existingIdFilter), rawFilter);
            }
            else {
                query._id = rawFilter;
            }
        }
        for (const key in query) {
            const tableColumnMetadata = model.getTableColumnMetadata(key);
            if (tableColumnMetadata && query[key] === null) {
                query[key] = QueryHelper.isNull();
            }
            else if (query[key] &&
                query[key] instanceof NotNull &&
                tableColumnMetadata) {
                if (tableColumnMetadata.type === TableColumnType.EntityArray) {
                    const manyToManyMeta = QueryUtil.getManyToManyRelationMetadata(modelType, key);
                    if (manyToManyMeta) {
                        const subqueryFilter = QueryHelper.anyEntitiesInManyToMany({
                            joinTableName: manyToManyMeta.joinTableName,
                            ownerColumnName: manyToManyMeta.ownerColumnName,
                        });
                        delete query[key];
                        const existingIdFilter = query._id;
                        if (existingIdFilter instanceof FindOperator) {
                            query._id = And(existingIdFilter, subqueryFilter);
                        }
                        else if (existingIdFilter &&
                            typeof existingIdFilter === Typeof.String) {
                            query._id = And(QueryHelper.equalTo(existingIdFilter), subqueryFilter);
                        }
                        else {
                            query._id = subqueryFilter;
                        }
                    }
                    else {
                        /*
                         * Metadata unavailable — drop the filter rather than emit
                         * invalid SQL against a relation that isn't a real column.
                         */
                        delete query[key];
                    }
                }
                else {
                    query[key] = QueryHelper.notNull();
                }
            }
            else if (query[key] &&
                query[key] instanceof EqualToOrNull &&
                tableColumnMetadata) {
                query[key] = QueryHelper.equalToOrNull(query[key]);
            }
            else if (query[key] &&
                query[key] instanceof EqualTo &&
                tableColumnMetadata) {
                query[key] = QueryHelper.equalTo(query[key]);
            }
            else if (query[key] &&
                tableColumnMetadata &&
                tableColumnMetadata.type === TableColumnType.JSON) {
                query[key] = QueryHelper.queryJson(query[key]);
            }
            else if (query[key] &&
                query[key] instanceof NotEqual &&
                tableColumnMetadata) {
                query[key] = QueryHelper.notEquals(query[key]);
            }
            else if (query[key] &&
                query[key]._value &&
                Array.isArray(query[key]._value) &&
                query[key]._value.length > 0 &&
                tableColumnMetadata) {
                let counter = 0;
                for (const item of query[key]._value) {
                    if (item instanceof ObjectID) {
                        query[key]._value[counter] = query[key]._value[counter].toString();
                    }
                    counter++;
                }
            }
            else if (query[key] &&
                query[key] instanceof ObjectID &&
                tableColumnMetadata &&
                tableColumnMetadata.type !== TableColumnType.EntityArray) {
                query[key] = QueryHelper.equalTo(query[key].toString());
            }
            else if (query[key] &&
                query[key] instanceof Search &&
                tableColumnMetadata) {
                query[key] = QueryHelper.search(query[key].toString());
            }
            else if (query[key] &&
                query[key] instanceof NotContains &&
                tableColumnMetadata) {
                query[key] = QueryHelper.notContains(query[key].toString());
            }
            else if (query[key] &&
                query[key] instanceof StartsWith &&
                tableColumnMetadata) {
                query[key] = QueryHelper.startsWith(query[key].toString());
            }
            else if (query[key] &&
                query[key] instanceof EndsWith &&
                tableColumnMetadata) {
                query[key] = QueryHelper.endsWith(query[key].toString());
            }
            else if (query[key] &&
                query[key] instanceof LessThan &&
                tableColumnMetadata) {
                query[key] = QueryHelper.lessThan(query[key].toString());
            }
            else if (query[key] &&
                query[key] instanceof IsNull &&
                tableColumnMetadata) {
                if (tableColumnMetadata.type === TableColumnType.EntityArray) {
                    const manyToManyMeta = QueryUtil.getManyToManyRelationMetadata(modelType, key);
                    if (manyToManyMeta) {
                        const subqueryFilter = QueryHelper.noEntitiesInManyToMany({
                            joinTableName: manyToManyMeta.joinTableName,
                            ownerColumnName: manyToManyMeta.ownerColumnName,
                        });
                        delete query[key];
                        const existingIdFilter = query._id;
                        if (existingIdFilter instanceof FindOperator) {
                            query._id = And(existingIdFilter, subqueryFilter);
                        }
                        else if (existingIdFilter &&
                            typeof existingIdFilter === Typeof.String) {
                            query._id = And(QueryHelper.equalTo(existingIdFilter), subqueryFilter);
                        }
                        else {
                            query._id = subqueryFilter;
                        }
                    }
                    else {
                        delete query[key];
                    }
                }
                else {
                    query[key] = QueryHelper.isNull();
                }
            }
            else if (query[key] &&
                query[key] instanceof InBetween &&
                tableColumnMetadata) {
                query[key] = QueryHelper.inBetween(query[key].startValue, query[key].endValue);
            }
            else if (query[key] &&
                query[key] instanceof GreaterThan &&
                tableColumnMetadata) {
                query[key] = QueryHelper.greaterThan(query[key].toString());
            }
            else if (query[key] &&
                query[key] instanceof IncludesAll &&
                tableColumnMetadata) {
                if (tableColumnMetadata.type === TableColumnType.EntityArray) {
                    const includesAll = query[key];
                    const values = includesAll.values.map((item) => {
                        if (item !== null &&
                            typeof item === Typeof.Object &&
                            !(item instanceof ObjectID)) {
                            const itemRecord = item;
                            if (itemRecord["_id"]) {
                                return itemRecord["_id"];
                            }
                        }
                        return item.toString();
                    });
                    const manyToManyMeta = QueryUtil.getManyToManyRelationMetadata(modelType, key);
                    if (manyToManyMeta && values.length > 0) {
                        const subqueryFilter = QueryHelper.allEntitiesInManyToMany({
                            values,
                            joinTableName: manyToManyMeta.joinTableName,
                            ownerColumnName: manyToManyMeta.ownerColumnName,
                            relationColumnName: manyToManyMeta.relationColumnName,
                        });
                        /*
                         * Remove the relation-based filter so TypeORM does not create a
                         * JOIN that would yield OR semantics.
                         */
                        delete query[key];
                        const existingIdFilter = query._id;
                        if (existingIdFilter instanceof FindOperator) {
                            query._id = And(existingIdFilter, subqueryFilter);
                        }
                        else if (existingIdFilter &&
                            typeof existingIdFilter === Typeof.String) {
                            query._id = And(QueryHelper.equalTo(existingIdFilter), subqueryFilter);
                        }
                        else {
                            query._id = subqueryFilter;
                        }
                    }
                    else {
                        // Fall back to OR behavior when metadata cannot be resolved.
                        query[key] = values;
                    }
                }
                else if (tableColumnMetadata.type === TableColumnType.Entity) {
                    // Entity (single) columns treat AND as a single match — same as OR.
                    query[key] = query[key].values;
                }
                else {
                    query[key] = QueryHelper.any(query[key].values);
                }
            }
            else if (query[key] &&
                query[key] instanceof IncludesNone &&
                tableColumnMetadata) {
                if (tableColumnMetadata.type === TableColumnType.EntityArray) {
                    const includesNone = query[key];
                    const values = includesNone.values.map((item) => {
                        if (item !== null &&
                            typeof item === Typeof.Object &&
                            !(item instanceof ObjectID)) {
                            const itemRecord = item;
                            if (itemRecord["_id"]) {
                                return itemRecord["_id"];
                            }
                        }
                        return item.toString();
                    });
                    const manyToManyMeta = QueryUtil.getManyToManyRelationMetadata(modelType, key);
                    if (manyToManyMeta && values.length > 0) {
                        const subqueryFilter = QueryHelper.noneEntitiesInManyToMany({
                            values,
                            joinTableName: manyToManyMeta.joinTableName,
                            ownerColumnName: manyToManyMeta.ownerColumnName,
                            relationColumnName: manyToManyMeta.relationColumnName,
                        });
                        delete query[key];
                        const existingIdFilter = query._id;
                        if (existingIdFilter instanceof FindOperator) {
                            query._id = And(existingIdFilter, subqueryFilter);
                        }
                        else if (existingIdFilter &&
                            typeof existingIdFilter === Typeof.String) {
                            query._id = And(QueryHelper.equalTo(existingIdFilter), subqueryFilter);
                        }
                        else {
                            query._id = subqueryFilter;
                        }
                    }
                    else {
                        delete query[key];
                    }
                }
                else if (tableColumnMetadata.type === TableColumnType.Entity) {
                    query[key] = QueryHelper.notIn(query[key].values);
                }
                else {
                    query[key] = QueryHelper.notIn(query[key].values);
                }
            }
            else if (query[key] &&
                query[key] instanceof Includes &&
                tableColumnMetadata) {
                if (tableColumnMetadata.type === TableColumnType.EntityArray ||
                    tableColumnMetadata.type === TableColumnType.Entity) {
                    query[key] = query[key].values;
                }
                else {
                    query[key] = QueryHelper.any(query[key].values);
                }
            }
            else if (query[key] &&
                query[key] instanceof GreaterThanOrEqual &&
                tableColumnMetadata) {
                query[key] = QueryHelper.greaterThanEqualTo(query[key].toString());
            }
            else if (query[key] &&
                query[key] instanceof LessThanOrEqual &&
                tableColumnMetadata) {
                query[key] = QueryHelper.lessThanEqualTo(query[key].toString());
            }
            else if (query[key] &&
                query[key] instanceof LessThanOrNull &&
                tableColumnMetadata) {
                query[key] = QueryHelper.lessThanOrNull(query[key].toString());
            }
            else if (query[key] &&
                query[key] instanceof GreaterThanOrNull &&
                tableColumnMetadata) {
                query[key] = QueryHelper.greaterThanOrNull(query[key].toString());
            }
            else if (query[key] &&
                Array.isArray(query[key]) &&
                tableColumnMetadata &&
                tableColumnMetadata.type !== TableColumnType.EntityArray) {
                query[key] = QueryHelper.any(query[key]);
            }
            if (tableColumnMetadata &&
                tableColumnMetadata.manyToOneRelationColumn &&
                typeof query[key] === Typeof.String) {
                query[tableColumnMetadata.manyToOneRelationColumn] = query[key];
                delete query[key];
            }
            if (tableColumnMetadata &&
                tableColumnMetadata.modelType &&
                tableColumnMetadata.type === TableColumnType.EntityArray &&
                Array.isArray(query[key])) {
                query[key] = query[key].map((item) => {
                    if (typeof item === Typeof.String) {
                        return item;
                    }
                    if (item && item["_id"]) {
                        return item["_id"];
                    }
                    return item;
                });
                query[key] = {
                    _id: QueryHelper.any(query[key]),
                };
            }
        }
        return query;
    }
    /**
     * Resolves the join-table metadata for a many-to-many relation declared on
     * the provided model. Returns null when the column is not a many-to-many
     * relation, the database connection is not yet ready, or required metadata
     * is missing.
     */
    static getManyToManyRelationMetadata(modelType, propertyPath) {
        var _a, _b, _c, _d;
        if (!PostgresAppInstance.isConnected()) {
            return null;
        }
        const dataSource = PostgresAppInstance.getDataSource();
        if (!dataSource) {
            return null;
        }
        let entityMetadata;
        try {
            entityMetadata = dataSource.getMetadata(modelType);
        }
        catch (_e) {
            return null;
        }
        if (!entityMetadata) {
            return null;
        }
        const relation = entityMetadata.findRelationWithPropertyPath(propertyPath);
        if (!relation || !relation.isManyToMany) {
            return null;
        }
        /*
         * Only the owning side of a many-to-many has join/inverse columns. Follow
         * the inverse relation when needed.
         */
        const owningRelation = relation.isOwning
            ? relation
            : (_a = relation.inverseRelation) !== null && _a !== void 0 ? _a : relation;
        const joinTableName = (_b = owningRelation.junctionEntityMetadata) === null || _b === void 0 ? void 0 : _b.tableName;
        if (!joinTableName) {
            return null;
        }
        /*
         * When `modelType` is the owning side, its id lives on joinColumns. When
         * it is the inverse side, its id lives on inverseJoinColumns.
         */
        const ownerColumns = relation.isOwning
            ? owningRelation.joinColumns
            : owningRelation.inverseJoinColumns;
        const relationColumns = relation.isOwning
            ? owningRelation.inverseJoinColumns
            : owningRelation.joinColumns;
        const ownerColumnName = (_c = ownerColumns[0]) === null || _c === void 0 ? void 0 : _c.databaseName;
        const relationColumnName = (_d = relationColumns[0]) === null || _d === void 0 ? void 0 : _d.databaseName;
        if (!ownerColumnName || !relationColumnName) {
            return null;
        }
        return {
            joinTableName,
            ownerColumnName,
            relationColumnName,
        };
    }
}
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object, Object]),
    __metadata("design:returntype", Object)
], QueryUtil, "serializeQuery", null);
//# sourceMappingURL=QueryUtil.js.map