"use strict";
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);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DatabaseService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const entities_1 = require("../database/entities");
const logger_config_1 = require("../logger/logger.config");
class EntityAccessor {
    constructor(dataSource, entity) {
        this.dataSource = dataSource;
        this.entity = entity;
    }
    get repository() {
        return this.dataSource.getRepository(this.entity);
    }
    get entityName() {
        return this.repository.metadata.tableName;
    }
    translateScalar(value) {
        if (value === null || value === undefined)
            return value;
        if (value instanceof Date)
            return value;
        if (value instanceof typeorm_2.FindOperator)
            return value;
        if (Array.isArray(value))
            return value.map((item) => this.translateScalar(item));
        if (typeof value !== 'object')
            return value;
        if (value.contains !== undefined)
            return (0, typeorm_2.Like)(`%${String(value.contains)}%`);
        if (value.in !== undefined)
            return (0, typeorm_2.In)(value.in);
        if (value.gte !== undefined)
            return (0, typeorm_2.MoreThanOrEqual)(value.gte);
        if (value.lte !== undefined)
            return (0, typeorm_2.LessThanOrEqual)(value.lte);
        if (value.gt !== undefined)
            return (0, typeorm_2.MoreThan)(value.gt);
        if (value.lt !== undefined)
            return (0, typeorm_2.LessThan)(value.lt);
        if (value.not !== undefined)
            return (0, typeorm_2.Not)(this.translateScalar(value.not));
        if (value.some !== undefined)
            return this.translateScalar(value.some);
        if (value.connect !== undefined)
            return this.translateScalar(value.connect);
        if (value.set !== undefined)
            return this.translateScalar(value.set);
        if (value.create !== undefined)
            return this.translateScalar(value.create);
        const translated = {};
        for (const [key, nestedValue] of Object.entries(value)) {
            translated[key] = this.translateScalar(nestedValue);
        }
        return translated;
    }
    translateWhere(where) {
        if (!where)
            return undefined;
        if (Array.isArray(where)) {
            return where.map((item) => this.translateWhere(item));
        }
        const { OR, AND, NOT, ...rest } = where;
        const translatedRest = this.translateScalar(rest);
        if (Array.isArray(OR) && OR.length > 0) {
            return OR.map((item) => this.translateWhere({ ...rest, ...item }));
        }
        if (Array.isArray(AND) && AND.length > 0) {
            return AND.map((item) => this.translateWhere({ ...rest, ...item }));
        }
        if (NOT && typeof NOT === 'object') {
            for (const [key, value] of Object.entries(NOT)) {
                translatedRest[key] = this.translateScalar({ not: value });
            }
        }
        return translatedRest;
    }
    translateOrder(orderBy) {
        if (!orderBy)
            return undefined;
        if (Array.isArray(orderBy)) {
            const mergedOrder = {};
            for (const item of orderBy) {
                if (typeof item === 'object' && item !== null) {
                    const translatedItem = this.translateOrder(item);
                    Object.assign(mergedOrder, translatedItem);
                }
            }
            return Object.keys(mergedOrder).length > 0 ? mergedOrder : undefined;
        }
        if (typeof orderBy === 'object' && orderBy !== null) {
            const result = {};
            for (const [key, val] of Object.entries(orderBy)) {
                if (typeof val === 'string') {
                    result[key] = val.toUpperCase();
                }
                else if (typeof val === 'object' && val !== null) {
                    result[key] = this.translateOrder(val);
                }
                else {
                    result[key] = val;
                }
            }
            return result;
        }
        return orderBy;
    }
    collectRelations(shape) {
        if (!shape || typeof shape !== 'object') {
            return undefined;
        }
        const relations = {};
        for (const [key, value] of Object.entries(shape)) {
            if (['_count', 'where', 'orderBy', 'order', 'take', 'skip', 'select'].includes(key))
                continue;
            if (value === true) {
                relations[key] = true;
                continue;
            }
            if (value && typeof value === 'object') {
                const nested = value.select ??
                    value.include ??
                    value;
                const nestedRelations = this.collectRelations(nested);
                relations[key] =
                    nestedRelations && Object.keys(nestedRelations).length > 0
                        ? nestedRelations
                        : true;
            }
        }
        return Object.keys(relations).length > 0 ? relations : undefined;
    }
    normalizePersistValue(value) {
        if (value === null || value === undefined)
            return value;
        if (value instanceof Date)
            return value;
        if (Array.isArray(value))
            return value.map((item) => this.normalizePersistValue(item));
        if (typeof value !== 'object')
            return value;
        if (value.disconnect === true) {
            return null;
        }
        if (value.connect !== undefined) {
            return this.normalizePersistValue(value.connect);
        }
        if (value.set !== undefined) {
            return this.normalizePersistValue(value.set);
        }
        if (value.create !== undefined) {
            return this.normalizePersistValue(value.create);
        }
        const normalized = {};
        for (const [key, nestedValue] of Object.entries(value)) {
            normalized[key] = this.normalizePersistValue(nestedValue);
        }
        return normalized;
    }
    normalizePersistData(data) {
        const normalized = {};
        for (const [key, value] of Object.entries(data ?? {})) {
            if (this.entityName === 'events' && key === 'members') {
                continue;
            }
            normalized[key] = this.normalizePersistValue(value);
        }
        return normalized;
    }
    extractRelationIds(value) {
        if (value === null || value === undefined)
            return null;
        const relationItems = Array.isArray(value)
            ? value
            : Array.isArray(value.set)
                ? value.set
                : Array.isArray(value.connect)
                    ? value.connect
                    : null;
        if (!relationItems)
            return null;
        return relationItems
            .map((item) => {
            if (item === null || item === undefined)
                return null;
            if (typeof item === 'string' || typeof item === 'number') {
                return String(item);
            }
            if (typeof item === 'object' && 'id' in item && item.id) {
                return String(item.id);
            }
            return null;
        })
            .filter((id) => Boolean(id));
    }
    async syncManyToManyRelations(entityId, existing, payload) {
        const manyToManyRelations = this.repository.metadata.manyToManyRelations;
        for (const relation of manyToManyRelations) {
            if (!Object.prototype.hasOwnProperty.call(payload, relation.propertyName)) {
                continue;
            }
            const desiredIds = this.extractRelationIds(payload[relation.propertyName]);
            if (desiredIds === null) {
                continue;
            }
            const currentItems = Array.isArray(existing?.[relation.propertyName])
                ? existing[relation.propertyName]
                : [];
            const currentIds = currentItems
                .map((item) => item?.id)
                .filter(Boolean)
                .map(String);
            const removedIds = currentIds.filter((id) => !desiredIds.includes(id));
            const addedIds = desiredIds.filter((id) => !currentIds.includes(id));
            const relationQuery = this.dataSource
                .createQueryBuilder()
                .relation(this.entity, relation.propertyName)
                .of(entityId);
            if (removedIds.length > 0) {
                await relationQuery.remove(removedIds);
            }
            if (addedIds.length > 0) {
                await relationQuery.add(addedIds);
            }
        }
    }
    async reload(saved, args = {}) {
        const id = saved?.id;
        if (!id)
            return saved;
        const relations = this.collectRelations(args.include);
        const reloaded = await this.repository.findOne({
            where: { id },
            relations,
        });
        return reloaded ?? saved;
    }
    async syncEventMembers(eventId, payload) {
        if (this.entityName !== 'events' || !payload?.members)
            return;
        const memberRepository = this.dataSource.getRepository(entities_1.MemberEntity);
        const relationPayload = payload.members;
        let memberIds = [];
        if (Array.isArray(relationPayload.connect)) {
            memberIds = relationPayload.connect
                .map((item) => item.id)
                .filter(Boolean);
        }
        else if (Array.isArray(relationPayload.set)) {
            memberIds = relationPayload.set
                .map((item) => item.id)
                .filter(Boolean);
            const existingMembers = await memberRepository.find({
                where: { eventsId: eventId },
                select: { id: true },
            });
            const existingIds = existingMembers.map((member) => member.id);
            const removedIds = existingIds.filter((memberId) => !memberIds.includes(memberId));
            if (removedIds.length > 0) {
                await memberRepository.update({ id: (0, typeorm_2.In)(removedIds) }, { eventsId: null });
            }
        }
        if (memberIds.length > 0) {
            await memberRepository.update({ id: (0, typeorm_2.In)(memberIds) }, { eventsId: eventId });
        }
    }
    async findMany(args = {}) {
        const where = this.translateWhere(args.where);
        const order = this.translateOrder(args.orderBy);
        const relations = this.collectRelations(args.include);
        let rows = await this.repository.find({
            where,
            relations,
            order,
            skip: args.skip,
            take: args.take,
        });
        if (this.entityName === 'departments' &&
            args.include?._count?.select?.members) {
            const memberRepository = this.dataSource.getRepository(entities_1.MemberEntity);
            const departmentIds = rows.map((row) => row.id).filter(Boolean);
            const memberRows = departmentIds.length
                ? await memberRepository.find({
                    where: {
                        department_id: (0, typeorm_2.In)(departmentIds),
                        ...(args.where?.owner_id
                            ? { owner_id: args.where.owner_id }
                            : {}),
                    },
                    select: { department_id: true },
                })
                : [];
            const counts = memberRows.reduce((acc, member) => {
                if (!member.department_id)
                    return acc;
                acc[member.department_id] = (acc[member.department_id] ?? 0) + 1;
                return acc;
            }, {});
            rows = rows.map((row) => ({
                ...row,
                _count: {
                    members: counts[row.id] ?? 0,
                },
            }));
        }
        return rows;
    }
    async findFirst(args = {}) {
        const rows = await this.findMany({ ...args, take: 1 });
        return rows[0] ?? null;
    }
    async findUnique(args = {}) {
        return this.findFirst(args);
    }
    async count(args = {}) {
        const where = this.translateWhere(args.where);
        return this.repository.count({ where });
    }
    async create(args = {}) {
        const payload = this.normalizePersistData(args.data ?? {});
        const entity = this.repository.create(payload);
        const saved = await this.repository.save(entity);
        if (payload?.members && this.entityName === 'events') {
            await this.syncEventMembers(saved.id, payload);
        }
        return this.reload(saved, args);
    }
    async update(args = {}) {
        const existing = await this.findFirst({
            where: args.where,
            include: args.include,
            select: args.select,
        });
        if (!existing)
            return null;
        const payload = this.normalizePersistData(args.data ?? {});
        const merged = this.repository.merge(existing, payload);
        const saved = await this.repository.save(merged);
        if (payload?.members && this.entityName === 'events') {
            await this.syncEventMembers(saved.id, payload);
        }
        await this.syncManyToManyRelations(saved.id, existing, args.data ?? {});
        return this.reload(saved, args);
    }
    async delete(args = {}) {
        const existing = await this.findFirst({ where: args.where });
        if (!existing)
            return null;
        await this.repository.remove(existing);
        return existing;
    }
    async upsert(args = {}) {
        const existing = await this.findFirst({
            where: args.where,
            include: args.include,
            select: args.select,
        });
        if (existing) {
            return this.update({
                where: args.where,
                data: args.update,
                include: args.include,
                select: args.select,
            });
        }
        return this.create({
            data: { ...(args.create ?? {}) },
            include: args.include,
            select: args.select,
        });
    }
    async createMany(args = {}) {
        const rows = Array.isArray(args.data)
            ? args.data.map((item) => this.normalizePersistData(item))
            : [];
        if (rows.length === 0) {
            return { count: 0 };
        }
        await this.repository.insert(rows);
        return { count: rows.length };
    }
    async updateMany(args = {}) {
        const payload = this.normalizePersistData(args.data ?? {});
        const result = await this.repository.update(this.translateWhere(args.where), payload);
        return { count: result.affected ?? 0 };
    }
    async deleteMany(args = {}) {
        const result = await this.repository.delete(this.translateWhere(args.where));
        return { count: result.affected ?? 0 };
    }
    async groupBy(args = {}) {
        const groupFields = Array.isArray(args.by) ? args.by : [];
        if (groupFields.length === 0)
            return [];
        const rows = await this.findMany({ where: args.where });
        const groups = new Map();
        for (const row of rows) {
            const groupKey = groupFields
                .map((field) => String(row[field]))
                .join('::');
            const existing = groups.get(groupKey) ?? {
                ...groupFields.reduce((acc, field) => {
                    acc[field] = row[field];
                    return acc;
                }, {}),
                _count: { _all: 0 },
            };
            existing._count._all += 1;
            groups.set(groupKey, existing);
        }
        return Array.from(groups.values());
    }
    async aggregate(args = {}) {
        const rows = await this.findMany({ where: args.where });
        const count = rows.length;
        const sumFields = Object.entries(args._sum ?? {}).map(([key]) => key);
        const avgFields = Object.entries(args._avg ?? {}).map(([key]) => key);
        const minFields = Object.entries(args._min ?? {}).map(([key]) => key);
        const maxFields = Object.entries(args._max ?? {}).map(([key]) => key);
        const result = { _count: count };
        for (const field of sumFields) {
            result._sum = result._sum || {};
            result._sum[field] = rows.reduce((acc, row) => acc + (row[field] ?? 0), 0);
        }
        for (const field of avgFields) {
            result._avg = result._avg || {};
            result._avg[field] =
                count > 0
                    ? rows.reduce((acc, row) => acc + (row[field] ?? 0), 0) / count
                    : 0;
        }
        for (const field of minFields) {
            result._min = result._min || {};
            result._min[field] = rows.reduce((min, row) => (row[field] < min ? row[field] : min), rows[0]?.[field]);
        }
        for (const field of maxFields) {
            result._max = result._max || {};
            result._max[field] = rows.reduce((max, row) => (row[field] > max ? row[field] : max), rows[0]?.[field]);
        }
        return result;
    }
}
let DatabaseService = class DatabaseService {
    constructor(dataSource) {
        this.dataSource = dataSource;
        this.owner = new EntityAccessor(this.dataSource, entities_1.OwnerEntity);
        this.company = new EntityAccessor(this.dataSource, entities_1.CompanyEntity);
        this.working_schedule = new EntityAccessor(this.dataSource, entities_1.WorkingScheduleEntity);
        this.work_day_schedules = new EntityAccessor(this.dataSource, entities_1.WorkDayScheduleEntity);
        this.ip_whitelists = new EntityAccessor(this.dataSource, entities_1.IpWhitelistEntity);
        this.attendance_settings = new EntityAccessor(this.dataSource, entities_1.AttendanceSettingsEntity);
        this.qr_attendance_points = new EntityAccessor(this.dataSource, entities_1.QrAttendancePointEntity);
        this.teacher_devices = new EntityAccessor(this.dataSource, entities_1.TeacherDeviceEntity);
        this.device_change_requests = new EntityAccessor(this.dataSource, entities_1.DeviceChangeRequestEntity);
        this.leave_policy = new EntityAccessor(this.dataSource, entities_1.LeavePolicyEntity);
        this.members = new EntityAccessor(this.dataSource, entities_1.MemberEntity);
        this.attendances = new EntityAccessor(this.dataSource, entities_1.AttendanceEntity);
        this.leaves = new EntityAccessor(this.dataSource, entities_1.LeaveEntity);
        this.holidays = new EntityAccessor(this.dataSource, entities_1.HolidayEntity);
        this.events = new EntityAccessor(this.dataSource, entities_1.EventEntity);
        this.notices = new EntityAccessor(this.dataSource, entities_1.NoticeEntity);
        this.work_experiences = new EntityAccessor(this.dataSource, entities_1.WorkExperienceEntity);
        this.educations = new EntityAccessor(this.dataSource, entities_1.EducationEntity);
        this.departments = new EntityAccessor(this.dataSource, entities_1.DepartmentEntity);
        this.designations = new EntityAccessor(this.dataSource, entities_1.DesignationEntity);
        this.locations = new EntityAccessor(this.dataSource, entities_1.LocationEntity);
        this.employement_types = new EntityAccessor(this.dataSource, entities_1.EmployementTypeEntity);
        this.hiring_sources = new EntityAccessor(this.dataSource, entities_1.HiringSourceEntity);
        this.notifications = new EntityAccessor(this.dataSource, entities_1.NotificationEntity);
        this.notification_receivers = new EntityAccessor(this.dataSource, entities_1.NotificationReceiverEntity);
        this.notification_targets = new EntityAccessor(this.dataSource, entities_1.NotificationTargetEntity);
        this.access_roles = new EntityAccessor(this.dataSource, entities_1.AccessRoleEntity);
        this.role_permissions = new EntityAccessor(this.dataSource, entities_1.RolePermissionEntity);
        this.job_postings = new EntityAccessor(this.dataSource, entities_1.JobPostingEntity);
        this.job_applications = new EntityAccessor(this.dataSource, entities_1.JobApplicationEntity);
        this.recruitment_tasks = new EntityAccessor(this.dataSource, entities_1.RecruitmentTaskEntity);
        this.tenant_api_keys = new EntityAccessor(this.dataSource, entities_1.TenantApiKeyEntity);
        this.onboarding_templates = new EntityAccessor(this.dataSource, entities_1.OnboardingTemplateEntity);
        this.onboarding_template_tasks = new EntityAccessor(this.dataSource, entities_1.OnboardingTemplateTaskEntity);
        this.onboarding_cases = new EntityAccessor(this.dataSource, entities_1.OnboardingCaseEntity);
        this.onboarding_tasks = new EntityAccessor(this.dataSource, entities_1.OnboardingTaskEntity);
        this.onboarding_documents = new EntityAccessor(this.dataSource, entities_1.OnboardingDocumentEntity);
        this.provisioning_items = new EntityAccessor(this.dataSource, entities_1.ProvisioningItemEntity);
        this.buddy_assignments = new EntityAccessor(this.dataSource, entities_1.BuddyAssignmentEntity);
        this.onboarding_surveys = new EntityAccessor(this.dataSource, entities_1.OnboardingSurveyEntity);
        this.probations = new EntityAccessor(this.dataSource, entities_1.ProbationEntity);
        this.probation_reviews = new EntityAccessor(this.dataSource, entities_1.ProbationReviewEntity);
        this.offboarding_templates = new EntityAccessor(this.dataSource, entities_1.OffboardingTemplateEntity);
        this.offboarding_template_tasks = new EntityAccessor(this.dataSource, entities_1.OffboardingTemplateTaskEntity);
        this.offboarding_cases = new EntityAccessor(this.dataSource, entities_1.OffboardingCaseEntity);
        this.offboarding_tasks = new EntityAccessor(this.dataSource, entities_1.OffboardingTaskEntity);
        this.equipment_returns = new EntityAccessor(this.dataSource, entities_1.EquipmentReturnEntity);
        this.exit_interview_templates = new EntityAccessor(this.dataSource, entities_1.ExitInterviewTemplateEntity);
        this.exit_interviews = new EntityAccessor(this.dataSource, entities_1.ExitInterviewEntity);
        this.final_settlements = new EntityAccessor(this.dataSource, entities_1.FinalSettlementEntity);
        this.final_settlement_items = new EntityAccessor(this.dataSource, entities_1.FinalSettlementItemEntity);
    }
    async onModuleInit() {
        if (!this.dataSource.isInitialized) {
            await this.dataSource.initialize();
        }
        logger_config_1.logger.info('Connected to the database');
    }
    async onModuleDestroy() {
        if (this.dataSource.isInitialized) {
            await this.dataSource.destroy();
        }
        logger_config_1.logger.info('Disconnected from the database');
    }
    runInTransaction(fn) {
        return this.dataSource.transaction(fn);
    }
};
exports.DatabaseService = DatabaseService;
exports.DatabaseService = DatabaseService = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectDataSource)()),
    __metadata("design:paramtypes", [typeorm_2.DataSource])
], DatabaseService);
//# sourceMappingURL=database.service.js.map