"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CrudHelperService = exports.UserIdFields = exports.FilterCombinator = void 0;
const typeorm_1 = require("typeorm");
const string_helper_1 = require("../helpers/string.helper");
const solid_registry_1 = require("../helpers/solid-registry");
const common_1 = require("@nestjs/common");
const error_messages_1 = require("../constants/error-messages");
var FilterCombinator;
(function (FilterCombinator) {
    FilterCombinator["AND"] = "$and";
    FilterCombinator["OR"] = "$or";
})(FilterCombinator || (exports.FilterCombinator = FilterCombinator = {}));
var UserIdFields;
(function (UserIdFields) {
    UserIdFields["CREATED_BY"] = "createdBy";
    UserIdFields["UPDATED_BY"] = "updatedBy";
})(UserIdFields || (exports.UserIdFields = UserIdFields = {}));
class CrudHelperService {
    constructor() {
        this.logger = new common_1.Logger(CrudHelperService.name);
        this.hasReadPermissionOnModel = (activeUser, modelName) => {
            const permissionNames = [`${(0, string_helper_1.classify)(modelName)}Controller.findOne`, `${(0, string_helper_1.classify)(modelName)}Controller.findMany`];
            const matchingPermssions = activeUser.permissions.filter((p) => permissionNames.includes(p));
            return matchingPermssions.length > 0;
        };
        this.hasWritePermissionOnModel = (activeUser, modelName) => {
            const permissionNames = [`${(0, string_helper_1.classify)(modelName)}Controller.create`, `${(0, string_helper_1.classify)(modelName)}Controller.insertMany`, `${(0, string_helper_1.classify)(modelName)}Controller.update`];
            const matchingPermssions = activeUser.permissions.filter((p) => permissionNames.includes(p));
            return matchingPermssions.length > 0;
        };
        this.hasUpdatePermissionOnModel = (activeUser, modelName) => {
            const permissionNames = [`${(0, string_helper_1.classify)(modelName)}Controller.update`];
            const matchingPermssions = activeUser.permissions.filter((p) => permissionNames.includes(p));
            return matchingPermssions.length > 0;
        };
        this.hasPublishPermissionOnModel = (activeUser, modelName) => {
            const permissionNames = [`${(0, string_helper_1.classify)(modelName)}Controller.publish`];
            const matchingPermssions = activeUser.permissions.filter((p) => permissionNames.includes(p));
            return matchingPermssions.length > 0;
        };
        this.hasUnpublishPermissionOnModel = (activeUser, modelName) => {
            const permissionNames = [`${(0, string_helper_1.classify)(modelName)}Controller.publish`];
            const matchingPermssions = activeUser.permissions.filter((p) => permissionNames.includes(p));
            return matchingPermssions.length > 0;
        };
        this.hasDeletePermissionOnModel = (activeUser, modelName) => {
            const permissionNames = [`${(0, string_helper_1.classify)(modelName)}Controller.delete`, `${(0, string_helper_1.classify)(modelName)}Controller.deleteMany`];
            const matchingPermssions = activeUser.permissions.filter((p) => permissionNames.includes(p));
            return matchingPermssions.length > 0;
        };
        this.hasCreatePermissionOnModel = (activeUser, modelName) => {
            const permissionNames = [`${(0, string_helper_1.classify)(modelName)}Controller.create`];
            const matchingPermssions = activeUser.permissions.filter((p) => permissionNames.includes(p));
            return matchingPermssions.length > 0;
        };
        this.hasRecoverPermissionOnModel = (activeUser, modelName) => {
            const permissionNames = [`${(0, string_helper_1.classify)(modelName)}Controller.recover`, `${(0, string_helper_1.classify)(modelName)}Controller.recoverMany`];
            const matchingPermssions = activeUser.permissions.filter((p) => permissionNames.includes(p));
            return matchingPermssions.length > 0;
        };
    }
    orderOptions(sort = []) {
        const orderOptions = {};
        sort.forEach((s) => {
            const parts = s.split(':');
            let order;
            let field;
            if (parts.length > 1) {
                order = parts.pop();
                field = parts.join(':');
            }
            else {
                field = parts[0];
            }
            const normalizedOrder = order ? order.toUpperCase() : 'ASC';
            if (!['ASC', 'DESC'].includes(normalizedOrder)) {
                throw new Error(`Invalid sort order provided:  ${order}`);
            }
            orderOptions[field] = normalizedOrder;
        });
        return orderOptions;
    }
    applyFilters(qb, filters, alias = 'entity', selectQb) {
        const normalizedFilters = this.normalizeObjectKeys(filters);
        if (normalizedFilters.$and) {
            normalizedFilters.$and.forEach((andFilter) => {
                qb.andWhere(new typeorm_1.Brackets(subQb => {
                    this.applyFilters(subQb, andFilter, alias, selectQb);
                }));
            });
        }
        else if (normalizedFilters.$or) {
            normalizedFilters.$or.forEach((orFilter) => {
                qb.orWhere(new typeorm_1.Brackets(subQb => {
                    this.applyFilters(subQb, orFilter, alias, selectQb);
                }));
            });
        }
        else {
            Object.keys(normalizedFilters).forEach(key => {
                const primaryFilterObj = normalizedFilters[key];
                const normalizedPrimaryFilterObj = this.normalizeObjectKeys(primaryFilterObj);
                const [rawField, funcAlias] = key.split(':');
                const operatorOrField = Object.keys(normalizedPrimaryFilterObj)[0];
                if (operatorOrField.startsWith('$')) {
                    const operator = operatorOrField;
                    let columnExpression;
                    if (funcAlias) {
                        try {
                            columnExpression = this.buildDateGranularityExpression(this.getDriver(selectQb), `${alias}.${rawField}`, funcAlias);
                        }
                        catch {
                            throw new common_1.BadRequestException(`Unsupported field function '${funcAlias}'. Supported functions are: day, week, month, year.`);
                        }
                    }
                    this.buildOperatorQuery(qb, alias, rawField, normalizedPrimaryFilterObj, operator, columnExpression);
                    return;
                }
                else {
                    if (funcAlias) {
                        throw new common_1.BadRequestException(`Function alias ':${funcAlias}' is not valid on relation field '${rawField}'. It can only be applied to scalar fields.`);
                    }
                    const joinField = `${alias}.${rawField}`;
                    if (!this.isRelationJoined(selectQb, joinField))
                        selectQb.leftJoin(joinField, rawField);
                    this.applyFilters(qb, primaryFilterObj, rawField, selectQb);
                }
            });
        }
    }
    buildOperatorQuery(qb, alias, field, normalizedPrimaryOperatorObj, operator, columnExpression) {
        const uniqueFieldAlias = `${alias}_${field}_${Math.floor(Math.random() * 1000)}`;
        const colExpr = columnExpression ?? `${alias}.${field}`;
        switch (operator) {
            case '$eq':
                qb.andWhere(`${colExpr} = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eq });
                break;
            case '$eqi':
                qb.andWhere(`LOWER(${colExpr}) = :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$eqi.toLowerCase() });
                break;
            case '$ne':
                qb.andWhere(`${colExpr} != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$ne });
                break;
            case '$nei':
                qb.andWhere(`LOWER(${colExpr}) != :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$nei.toLowerCase() });
                break;
            case '$gt':
                qb.andWhere(`${colExpr} > :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gt });
                break;
            case '$gte':
                qb.andWhere(`${colExpr} >= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$gte });
                break;
            case '$lt':
                qb.andWhere(`${colExpr} < :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lt });
                break;
            case '$lte':
                qb.andWhere(`${colExpr} <= :${uniqueFieldAlias}`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$lte });
                break;
            case '$in':
                qb.andWhere(`${colExpr} IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$in });
                break;
            case '$notIn':
                qb.andWhere(`${colExpr} NOT IN (:...${uniqueFieldAlias})`, { [uniqueFieldAlias]: normalizedPrimaryOperatorObj.$notIn });
                break;
            case '$contains':
                qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$contains}%` });
                break;
            case '$notContains':
                qb.andWhere(`${colExpr} NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContains}%` });
                break;
            case '$containsi':
                qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$containsi.toLowerCase()}%` });
                break;
            case '$notContainsi':
                qb.andWhere(`LOWER(${colExpr}) NOT LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$notContainsi.toLowerCase()}%` });
                break;
            case '$null':
                qb.andWhere(`${colExpr} IS NULL`);
                break;
            case '$notNull':
                qb.andWhere(`${colExpr} IS NOT NULL`);
                break;
            case '$between':
                qb.andWhere(`${colExpr} BETWEEN :${uniqueFieldAlias}0 AND :${uniqueFieldAlias}1`, { [`${uniqueFieldAlias}0`]: normalizedPrimaryOperatorObj.$between[0], [`${uniqueFieldAlias}1`]: normalizedPrimaryOperatorObj.$between[1] });
                break;
            case '$startsWith':
                qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWith}%` });
                break;
            case '$startsWithi':
                qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `${normalizedPrimaryOperatorObj.$startsWithi.toLowerCase()}%` });
                break;
            case '$endsWith':
                qb.andWhere(`${colExpr} LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWith}` });
                break;
            case '$endsWithi':
                qb.andWhere(`LOWER(${colExpr}) LIKE :${uniqueFieldAlias}`, { [uniqueFieldAlias]: `%${normalizedPrimaryOperatorObj.$endsWithi.toLowerCase()}` });
                break;
            default:
                throw new Error(`Operator ${operator} is not supported`);
        }
    }
    normalizeObjectKeys(obj) {
        return Object.keys(obj).reduce((acc, key) => {
            const newKey = key.replace(/^\[(.*)\]$/, '$1');
            acc[newKey] = obj[key];
            return acc;
        }, {});
    }
    normalize(value) {
        if (!value)
            return [];
        return Array.isArray(value) ? value : [value];
    }
    normalizeAndFilterPopulateAttributes(value) {
        const normalized = this.normalize(value);
        return normalized.filter(item => item !== UserIdFields.CREATED_BY && item !== UserIdFields.UPDATED_BY);
    }
    extractUserIdFieldsFromPopulate(value) {
        const normalized = this.normalize(value);
        return normalized.filter(item => item === UserIdFields.CREATED_BY || item === UserIdFields.UPDATED_BY);
    }
    isRelationJoined(queryBuilder, joinProperty) {
        return queryBuilder.expressionMap.joinAttributes.some(join => join.entityOrProperty === joinProperty);
    }
    hasJoins(queryBuilder) {
        return queryBuilder.expressionMap.joinAttributes.length > 0;
    }
    buildFilterQuery(qb, basicFilterDto, entityAlias, internationalisation, draftPublishWorkflow, moduleRef, filterCombinator = FilterCombinator.AND, applyPagination = true, applySorting = true) {
        let { limit, offset, showSoftDeleted, filters } = basicFilterDto;
        const { fields, sort, populate = [], populateMedia = [], locale, status } = basicFilterDto;
        const normalizedFields = this.normalize(fields);
        const normalizedAndFilteredPopulateAttributes = this.normalizeAndFilterPopulateAttributes(populate);
        const normalizedPopulateMedia = this.normalize(populateMedia);
        const additionalPopulate = this.additionalRelationsRequiredForMediaPopulation(normalizedPopulateMedia);
        normalizedAndFilteredPopulateAttributes.push(...additionalPopulate.filter((relation) => !normalizedAndFilteredPopulateAttributes.includes(relation)));
        const normalizedSort = this.normalize(sort);
        if (normalizedAndFilteredPopulateAttributes && normalizedAndFilteredPopulateAttributes.length) {
            this.buildPopulateQuery(normalizedAndFilteredPopulateAttributes, entityAlias, qb);
        }
        if (filters) {
            if (filterCombinator === FilterCombinator.AND) {
                qb.andWhere(new typeorm_1.Brackets(whereQb => {
                    this.applyFilters(whereQb, filters, entityAlias, qb);
                }));
            }
            else if (filterCombinator === FilterCombinator.OR) {
                qb.orWhere(new typeorm_1.Brackets(whereQb => {
                    this.applyFilters(whereQb, filters, entityAlias, qb);
                }));
            }
        }
        let finalLocale = locale;
        if (internationalisation) {
            if (!finalLocale) {
                const solidRegistry = moduleRef.get(solid_registry_1.SolidRegistry, { strict: false });
                const defaultLocale = solidRegistry.getDefaultLocale();
                if (defaultLocale) {
                    finalLocale = defaultLocale.locale;
                }
                else {
                    finalLocale = 'en';
                }
            }
            qb.andWhere(`${entityAlias}.localeName = :locale`, { locale: finalLocale });
        }
        if (draftPublishWorkflow && status) {
            if (basicFilterDto.status === 'published') {
                qb.andWhere(`${entityAlias}.publishedAt IS NOT NULL`);
            }
            else if (basicFilterDto.status === 'draft') {
                qb.andWhere(`${entityAlias}.publishedAt IS NULL`);
            }
        }
        if (normalizedFields && normalizedFields.length) {
            qb.select(normalizedFields.map(field => {
                return this.wrapFieldWithAlias(field, entityAlias);
            }));
        }
        if (applySorting && normalizedSort && normalizedSort.length) {
            const orderOptions = this.orderOptions(normalizedSort);
            if (orderOptions) {
                const orderOptionKeys = Object.keys(orderOptions);
                orderOptionKeys.forEach((key) => {
                    const value = orderOptions[key];
                    qb.addOrderBy(`${entityAlias}.${key}`, value);
                });
            }
        }
        if (showSoftDeleted === 'inclusive') {
            qb.withDeleted();
        }
        if (showSoftDeleted === 'exclusive') {
            qb.withDeleted();
            qb.where(`${entityAlias}.deletedAt IS NOT NULL`);
        }
        if (applyPagination) {
            if (limit)
                this.hasJoins(qb) ? qb.take(limit) : qb.limit(limit);
            if (offset)
                this.hasJoins(qb) ? qb.skip(offset) : qb.offset(offset);
        }
        return qb;
    }
    additionalRelationsRequiredForMediaPopulation(normalizedPopulateMedia) {
        return normalizedPopulateMedia
            .filter(pm => pm.includes("."))
            .map((pm) => {
            const mediaPathParts = pm.split('.');
            if (mediaPathParts.length <= 1)
                return pm;
            return mediaPathParts.slice(0, -1).join('.');
        });
    }
    buildPopulateQuery(normalizedPopulate, entityAlias, qb) {
        normalizedPopulate.forEach((relation) => {
            this.buildJoinQueryForRelation(qb, entityAlias, relation);
        });
        return qb;
    }
    sanitizeAlias(alias) {
        return alias.replace(/[^a-zA-Z0-9_]/g, '_');
    }
    isAliasJoined(queryBuilder, alias) {
        return queryBuilder.expressionMap.joinAttributes.some(join => join.alias?.name === alias);
    }
    getExistingJoinAlias(qb, joinProperty) {
        const existingJoin = qb.expressionMap.joinAttributes.find(join => join.entityOrProperty === joinProperty);
        return existingJoin?.alias?.name;
    }
    ensureRelationPathJoined(qb, rootAlias, pathParts) {
        const mainAlias = qb.expressionMap?.mainAlias?.name ||
            qb.expressionMap?.aliases?.find(a => a.metadata)?.name ||
            qb.expressionMap?.aliases?.[0]?.name;
        let parentAlias = mainAlias || rootAlias;
        for (let i = 0; i < pathParts.length - 1; i++) {
            const part = pathParts[i];
            const joinProperty = `${parentAlias}.${part}`;
            const existingAlias = this.getExistingJoinAlias(qb, joinProperty);
            const joinAlias = existingAlias ?? this.sanitizeAlias(`${parentAlias}_${part}`);
            if (!existingAlias && !this.isRelationJoined(qb, joinProperty) && !this.isAliasJoined(qb, joinAlias)) {
                qb.leftJoin(joinProperty, joinAlias);
            }
            parentAlias = joinAlias;
        }
        return { alias: parentAlias, property: pathParts[pathParts.length - 1] };
    }
    getDriver(qb) {
        return qb.connection.options.type;
    }
    buildDateGranularityExpression(driver, columnExpr, granularity) {
        switch (driver) {
            case 'postgres':
            case 'cockroachdb':
                return `DATE_TRUNC('${granularity}', ${columnExpr})`;
            case 'mysql':
            case 'mariadb':
                switch (granularity) {
                    case 'day': return `DATE(${columnExpr})`;
                    case 'week': return `STR_TO_DATE(DATE_FORMAT(${columnExpr}, '%x-%v-1'), '%x-%v-%w')`;
                    case 'month': return `DATE_FORMAT(${columnExpr}, '%Y-%m-01')`;
                    case 'year': return `DATE_FORMAT(${columnExpr}, '%Y-01-01')`;
                    default: throw new Error(`Unsupported granularity ${granularity} for driver ${driver}`);
                }
            case 'mssql':
            case 'sqlserver':
                switch (granularity) {
                    case 'day': return `CONVERT(date, ${columnExpr})`;
                    case 'week': return `DATEADD(week, DATEDIFF(week, 0, ${columnExpr}), 0)`;
                    case 'month': return `DATEFROMPARTS(YEAR(${columnExpr}), MONTH(${columnExpr}), 1)`;
                    case 'year': return `DATEFROMPARTS(YEAR(${columnExpr}), 1, 1)`;
                    default: throw new Error(`Unsupported granularity ${granularity} for driver ${driver}`);
                }
            default:
                throw new Error(`Granularity not supported for driver ${driver}`);
        }
    }
    buildGroupByExpression(qb, rootAlias, field) {
        const parts = field.split(':');
        const rawField = parts[0];
        const granularity = parts[1];
        const format = parts[2];
        const pathParts = rawField.split('.');
        const { alias, property } = this.ensureRelationPathJoined(qb, rootAlias, pathParts);
        const columnExpr = `${alias}.${property}`;
        const groupExpr = granularity ? this.buildDateGranularityExpression(this.getDriver(qb), columnExpr, granularity) : columnExpr;
        const selectAlias = this.sanitizeAlias(`${rawField.replace(/\./g, '_')}${granularity ? '_' + granularity : ''}`);
        return { groupExpr, selectAlias, sourceKey: field, format };
    }
    applyGroupBySelections(qb, groupBy, entityAlias) {
        const aliasMap = {};
        const formatMap = {};
        const expressionMap = {};
        qb.select([]);
        groupBy.forEach((field) => {
            const { groupExpr, selectAlias, sourceKey, format } = this.buildGroupByExpression(qb, entityAlias, field);
            qb.addSelect(groupExpr, selectAlias);
            qb.addGroupBy(groupExpr);
            aliasMap[sourceKey] = selectAlias;
            formatMap[selectAlias] = format;
            expressionMap[selectAlias] = groupExpr;
        });
        return { aliasMap, formatMap, expressionMap };
    }
    buildAggregateExpression(qb, rootAlias, aggregate) {
        const [rawField, rawFn] = aggregate.split(':');
        const fn = (rawFn || 'count').toLowerCase();
        if ((!rawField || rawField.toLowerCase() === 'count') && fn === 'count') {
            return { expression: 'COUNT(*)', selectAlias: 'count' };
        }
        if (!rawField)
            throw new Error(`Invalid aggregate specification: ${aggregate}`);
        const pathParts = rawField.split('.');
        const { alias, property } = this.ensureRelationPathJoined(qb, rootAlias, pathParts);
        const columnExpr = `${alias}.${property}`;
        const selectAlias = this.sanitizeAlias(`${rawField.replace(/\./g, '_')}_${fn}`);
        let expression = '';
        switch (fn) {
            case 'count':
                expression = `COUNT(${columnExpr})`;
                break;
            case 'count_distinct':
                expression = `COUNT(DISTINCT ${columnExpr})`;
                break;
            case 'sum':
                expression = `SUM(${columnExpr})`;
                break;
            case 'avg':
                expression = `AVG(${columnExpr})`;
                break;
            case 'min':
                expression = `MIN(${columnExpr})`;
                break;
            case 'max':
                expression = `MAX(${columnExpr})`;
                break;
            default: throw new Error(`Unsupported aggregate function ${fn}`);
        }
        return { expression, selectAlias, sourceKey: aggregate };
    }
    applyAggregates(qb, aggregates, entityAlias) {
        const aggregateList = this.normalize(aggregates);
        const aggregateAliasMap = {};
        if (!aggregateList.length) {
            qb.addSelect('COUNT(*)', 'count');
            aggregateAliasMap['count'] = 'count';
            return aggregateAliasMap;
        }
        aggregateList.forEach((agg) => {
            const { expression, selectAlias, sourceKey } = this.buildAggregateExpression(qb, entityAlias, agg);
            qb.addSelect(expression, selectAlias);
            aggregateAliasMap[sourceKey] = selectAlias;
        });
        return aggregateAliasMap;
    }
    applyGroupSortingAndPagination(qb, sort, aliasMap, limit, offset) {
        const normalizedSort = this.normalize(sort);
        if (normalizedSort.length) {
            const orderOptions = this.orderOptions(normalizedSort);
            const orderOptionKeys = Object.keys(orderOptions);
            orderOptionKeys.forEach((key) => {
                const resolvedKey = aliasMap[key] || key;
                const value = orderOptions[key];
                qb.addOrderBy(`"${resolvedKey}"`, value);
            });
        }
        const hasLimit = limit !== undefined && limit !== null;
        const hasOffset = offset !== undefined && offset !== null;
        if (hasLimit) {
            qb.take(limit);
            qb.limit(limit);
        }
        if (hasOffset) {
            qb.skip(offset);
            qb.offset(offset);
        }
    }
    async countGroups(qb) {
        const clone = qb.clone();
        clone.limit(undefined).offset(undefined).take(undefined).skip(undefined);
        const rows = await clone.getRawMany();
        return rows.length;
    }
    buildJoinQueryForRelation(qb, entityAlias, relation) {
        const relationParts = relation.split('.');
        let parentAlias = entityAlias;
        relationParts.forEach((part, i) => {
            const joinProperty = `${parentAlias}.${part}`;
            if (!this.isRelationJoined(qb, joinProperty)) {
                const joinAlias = relationParts.slice(0, i + 1).join('_');
                qb.leftJoinAndSelect(joinProperty, joinAlias);
            }
            else {
                qb.addSelect(`${part}`);
            }
            parentAlias = part;
        });
        return qb;
    }
    wrapFieldWithAlias(field, entityAlias) {
        if (!this.isAggregateField(field))
            return `${entityAlias}.${field}`;
        const fieldParts = field.split('(');
        const aggregateFunction = fieldParts[0];
        const fieldName = fieldParts[1].replace(')', '');
        return `${aggregateFunction}(${entityAlias}.${fieldName})`;
    }
    isAggregateField(field) {
        return field.includes('(');
    }
    isAggregateFieldKey(key, aggregateAliases) {
        return aggregateAliases.has(key);
    }
    getFieldFromQueryFieldKey(queryFieldKey, alias) {
        return queryFieldKey.replace(`${alias}_`, '');
    }
    buildGroupByRecordsQuery(qb, group, alias, groupAliasMap = {}, aggregateAliasMap = {}, groupExpressionMap = {}) {
        const rootAlias = qb.expressionMap?.mainAlias?.name
            ?? qb.expressionMap?.aliases?.find(a => a.metadata)?.name
            ?? qb.expressionMap?.aliases?.[0]?.name
            ?? qb.alias
            ?? alias;
        qb.andWhere(new typeorm_1.Brackets(qb => {
            const aggregateAliasSet = new Set(Object.values(aggregateAliasMap));
            const reverseGroupAliasMap = Object.entries(groupAliasMap).reduce((acc, [sourceKey, aliasKey]) => {
                acc[aliasKey] = sourceKey;
                return acc;
            }, {});
            for (const key in group) {
                if (group.hasOwnProperty(key) && !this.isAggregateFieldKey(key, aggregateAliasSet)) {
                    const value = group[key];
                    const sourceField = reverseGroupAliasMap[key] || key;
                    const cleanedField = sourceField.split(':')[0];
                    const pathParts = cleanedField.split('.');
                    const { alias: resolvedAlias, property } = this.ensureRelationPathJoined(qb, rootAlias, pathParts);
                    const paramKey = this.sanitizeAlias(`${resolvedAlias}_${property}_${key}`);
                    const expr = (sourceField.includes(':') && groupExpressionMap[key])
                        ? groupExpressionMap[key]
                        : `${resolvedAlias}.${property}`;
                    qb.andWhere(`${expr} = :${paramKey}`, { [paramKey]: value });
                }
            }
        }));
        return qb;
    }
    formatGroupValue(value, format) {
        if (!format)
            return value;
        if (value === null || value === undefined)
            return value;
        const dateVal = value instanceof Date ? value : new Date(value);
        if (isNaN(dateVal.getTime()))
            return value;
        switch (format) {
            case 'MMM':
                return dateVal.toLocaleString('en', { month: 'short' });
            case 'MMMM':
                return dateVal.toLocaleString('en', { month: 'long' });
            case 'YYYY':
                return dateVal.getFullYear();
            case 'YYYY-MM':
                return `${dateVal.getFullYear()}-${String(dateVal.getMonth() + 1).padStart(2, '0')}`;
            case 'YYYY-MM-DD':
                return `${dateVal.getFullYear()}-${String(dateVal.getMonth() + 1).padStart(2, '0')}-${String(dateVal.getDate()).padStart(2, '0')}`;
            default:
                return value;
        }
    }
    getGroupFieldValues(group, groupByFields, groupAliasMap) {
        return groupByFields
            .map(field => {
            const parts = field.split(':');
            const granularity = parts[1];
            const alias = groupAliasMap[field] ?? this.sanitizeAlias(field.replace(/\./g, '_'));
            const rawVal = group[alias] ?? group[field] ?? group[field.replace(/\./g, '_')];
            return { rawVal, alias, granularity };
        })
            .filter(({ rawVal }) => rawVal !== undefined && rawVal !== null);
    }
    normalizeGroupValue(value, granularity) {
        if (!granularity)
            return value;
        return this.formatGroupValue(value, 'YYYY-MM-DD');
    }
    getGroupName(group, aggregateAliases, groupByFields, groupAliasMap, groupFormatMap) {
        const fieldValues = this.getGroupFieldValues(group, groupByFields, groupAliasMap);
        if (fieldValues.length === 0) {
            return Object.keys(group)
                .filter(key => !this.isAggregateFieldKey(key, aggregateAliases))
                .map(key => group[key])
                .join('_');
        }
        return fieldValues
            .map(({ rawVal, alias }) => this.formatGroupValue(rawVal, groupFormatMap[alias]))
            .join('_');
    }
    getGroupValue(group, groupByFields, groupAliasMap) {
        const fieldValues = this.getGroupFieldValues(group, groupByFields, groupAliasMap);
        if (fieldValues.length === 1)
            return this.normalizeGroupValue(fieldValues[0].rawVal, fieldValues[0].granularity);
        return fieldValues.map(({ rawVal, granularity }) => this.normalizeGroupValue(rawVal, granularity)).join('_');
    }
    createGroupRecords(group, aggregateAliases, groupData, groupByFields, groupAliasMap, groupFormatMap) {
        const groupName = this.getGroupName(group, aggregateAliases, groupByFields, groupAliasMap, groupFormatMap);
        return {
            groupName,
            groupData
        };
    }
    createGroupMeta(group, aggregateAliases, groupByFields, groupAliasMap, groupFormatMap) {
        const groupName = this.getGroupName(group, aggregateAliases, groupByFields, groupAliasMap, groupFormatMap);
        const groupValue = this.getGroupValue(group, groupByFields, groupAliasMap);
        const groupAggregateValues = {};
        for (const key in group) {
            if (group.hasOwnProperty(key) && this.isAggregateFieldKey(key, aggregateAliases)) {
                const value = group[key];
                groupAggregateValues[key] = value;
            }
        }
        return {
            groupName,
            groupValue,
            ...groupAggregateValues
        };
    }
    async countGroupedRecords(qb, basicFilterDto, entityAlias) {
        const { limit, offset, ...rest } = basicFilterDto;
        const filteredDto = { ...rest, limit: undefined, offset: undefined };
        const filteredQB = this.buildFilterQuery(qb, filteredDto, entityAlias, undefined, undefined, undefined, FilterCombinator.AND, false, false);
        const groupByFields = this.normalize(filteredDto.groupBy);
        if (!groupByFields || groupByFields.length === 0) {
            throw new Error(error_messages_1.ERROR_MESSAGES.INVALID_GROUP_BY_COUNT);
        }
        this.applyGroupBySelections(filteredQB, groupByFields, entityAlias);
        this.applyAggregates(filteredQB, ['count'], entityAlias);
        filteredQB.limit(undefined).offset(undefined).take(undefined).skip(undefined);
        const rawResults = await filteredQB.getRawMany();
        return rawResults.length;
    }
}
exports.CrudHelperService = CrudHelperService;
//# sourceMappingURL=crud-helper.service.js.map