"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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RecruitmentService = void 0;
const common_1 = require("@nestjs/common");
const crypto_1 = require("crypto");
const googleapis_1 = require("googleapis");
const typeorm_1 = require("typeorm");
const permission_resolver_helpers_1 = require("../../common/access-control/permission-resolver.helpers");
const permission_resolver_service_1 = require("../../common/access-control/permission-resolver.service");
const config_1 = require("../../config");
const entities_1 = require("../../config/database/entities");
const utils_1 = require("../../utils");
const notifications_service_1 = require("../notifications/notifications.service");
const onboarding_service_1 = require("../onboarding/onboarding.service");
function slugify(value) {
    return value
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '');
}
let RecruitmentService = class RecruitmentService {
    constructor(db, permissionResolver, notificationsService, hashService, mailService, onboardingService) {
        this.db = db;
        this.permissionResolver = permissionResolver;
        this.notificationsService = notificationsService;
        this.hashService = hashService;
        this.mailService = mailService;
        this.onboardingService = onboardingService;
    }
    buildPagination(page, limit, total) {
        const totalPages = Math.ceil(total / limit);
        return {
            pagination: {
                total_items: total,
                total_pages: totalPages,
                current_page: page,
                items_per_page: limit,
                has_next_page: page < totalPages,
                has_previous_page: page > 1,
                next_page: page < totalPages ? page + 1 : null,
                previous_page: page > 1 ? page - 1 : null,
            },
        };
    }
    async getPermissions(ctx) {
        return this.permissionResolver.resolveForUser(ctx);
    }
    normalizeRoleOverview(value) {
        if (!value)
            return null;
        if (Array.isArray(value))
            return value;
        return value
            .split('\n')
            .map((line) => line.trim())
            .filter(Boolean);
    }
    async ensureUniqueJobSlug(owner_id, slug, excludeId) {
        const existing = await this.db.job_postings.findFirst({
            where: {
                owner_id,
                slug,
                record_status: (0, typeorm_1.Not)('deleted'),
                ...(excludeId ? { id: (0, typeorm_1.Not)(excludeId) } : {}),
            },
        });
        if (existing) {
            throw (0, utils_1.createGeneralError)('A job posting with this slug already exists', 'slug', common_1.HttpStatus.CONFLICT);
        }
    }
    async resolveOwnerByOrgSlug(orgSlug) {
        let owner = await this.db.owner.findFirst({
            where: { slug: orgSlug },
        });
        if (!owner) {
            owner = await this.db.owner.findUnique({ where: { id: orgSlug } });
        }
        if (!owner) {
            throw (0, utils_1.createGeneralError)('Organization not found', 'orgSlug', common_1.HttpStatus.NOT_FOUND);
        }
        return owner;
    }
    async createJobPosting(data, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            (0, permission_resolver_helpers_1.assertScope)(permissions, 'recruitment.create');
            const slug = slugify(data.slug ?? data.title);
            await this.ensureUniqueJobSlug(owner_id, slug);
            const job = await this.db.job_postings.create({
                data: {
                    owner_id,
                    title: data.title,
                    slug,
                    short_description: data.short_description ?? 'NT/APL',
                    salary_range: data.salary_range ?? 'NT/APL',
                    location: data.location ?? 'NT/APL',
                    job_type: data.job_type ?? 'NT/APL',
                    experience: data.experience ?? 'NT/APL',
                    department_id: data.department_id ?? null,
                    deadline: data.deadline ?? null,
                    vacancy_count: data.vacancy_count ?? 1,
                    role_overview: this.normalizeRoleOverview(data.role_overview),
                    basics_benefits: data.basics_benefits ?? [],
                    what_youll_do: data.what_youll_do ?? [],
                    what_were_looking_for: data.what_were_looking_for ?? [],
                    banner_image: data.banner_image ?? null,
                    icon_image: data.icon_image ?? null,
                    categories: data.categories ?? [],
                    tags: data.tags ?? [],
                    status: data.status ?? entities_1.JobPostingStatusEnum.draft,
                },
            });
            return {
                data: job,
                message: 'Job posting created successfully',
                status: common_1.HttpStatus.CREATED,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in createJobPosting: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async getJobPostings(query, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            (0, permission_resolver_helpers_1.assertScope)(permissions, 'recruitment.view');
            const page = query.page ?? 1;
            const limit = query.limit ?? 10;
            const skip = (page - 1) * limit;
            const where = { owner_id, record_status: (0, typeorm_1.Not)('deleted') };
            if (query.status)
                where.status = query.status;
            if (query.department_id)
                where.department_id = query.department_id;
            if (query.search) {
                where.OR = [
                    { title: { contains: query.search } },
                    { slug: { contains: query.search } },
                    { location: { contains: query.search } },
                ];
            }
            const orderBy = query.sort === 'title'
                ? { title: 'asc' }
                : query.sort === 'oldest'
                    ? { created_at: 'asc' }
                    : { created_at: 'desc' };
            const [jobs, total] = await Promise.all([
                this.db.job_postings.findMany({
                    where,
                    include: { department: true, owner: true },
                    orderBy,
                    skip,
                    take: limit,
                }),
                this.db.job_postings.count({ where }),
            ]);
            return {
                data: jobs,
                message: 'Job postings retrieved successfully',
                status: common_1.HttpStatus.OK,
                details: this.buildPagination(page, limit, total),
            };
        }
        catch (error) {
            config_1.logger.error(`Error in getJobPostings: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async getJobPostingById(id, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            (0, permission_resolver_helpers_1.assertScope)(permissions, 'recruitment.view');
            const job = await this.db.job_postings.findUnique({
                where: { id, owner_id, record_status: (0, typeorm_1.Not)('deleted') },
                include: { department: true, owner: true },
            });
            if (!job) {
                throw (0, utils_1.createGeneralError)('Job posting not found', 'job', common_1.HttpStatus.NOT_FOUND);
            }
            return {
                data: job,
                message: 'Job posting retrieved successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in getJobPostingById: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async updateJobPosting(id, data, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            (0, permission_resolver_helpers_1.assertScope)(permissions, 'recruitment.update');
            const existing = await this.db.job_postings.findUnique({
                where: { id, owner_id, record_status: (0, typeorm_1.Not)('deleted') },
            });
            if (!existing) {
                throw (0, utils_1.createGeneralError)('Job posting not found', 'job', common_1.HttpStatus.NOT_FOUND);
            }
            const slug = data.slug ? slugify(data.slug) : undefined;
            if (slug)
                await this.ensureUniqueJobSlug(owner_id, slug, id);
            const job = await this.db.job_postings.update({
                where: { id, owner_id },
                data: {
                    ...data,
                    ...(slug ? { slug } : {}),
                    ...(data.role_overview !== undefined
                        ? { role_overview: this.normalizeRoleOverview(data.role_overview) }
                        : {}),
                },
                include: { department: true },
            });
            return {
                data: job,
                message: 'Job posting updated successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in updateJobPosting: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async deleteJobPosting(id, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            (0, permission_resolver_helpers_1.assertScope)(permissions, 'recruitment.delete');
            const existing = await this.db.job_postings.findUnique({
                where: { id, owner_id, record_status: (0, typeorm_1.Not)('deleted') },
            });
            if (!existing) {
                throw (0, utils_1.createGeneralError)('Job posting not found', 'job', common_1.HttpStatus.NOT_FOUND);
            }
            await this.db.job_postings.update({
                where: { id, owner_id },
                data: { record_status: 'deleted' },
            });
            return {
                data: null,
                message: 'Job posting deleted successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in deleteJobPosting: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async createApplication(data, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            (0, permission_resolver_helpers_1.assertScope)(permissions, 'recruitment.create');
            const job = await this.db.job_postings.findUnique({
                where: {
                    id: data.job_posting_id,
                    owner_id,
                    record_status: (0, typeorm_1.Not)('deleted'),
                },
            });
            if (!job) {
                throw (0, utils_1.createGeneralError)('Job posting not found', 'job_posting_id', common_1.HttpStatus.NOT_FOUND);
            }
            const existingApplication = await this.db.job_applications.findFirst({
                where: {
                    owner_id,
                    email: data.email.trim(),
                },
            });
            if (existingApplication) {
                throw (0, utils_1.createGeneralError)(`An applicant with email "${data.email.trim()}" already exists in the applicant list`, 'email', common_1.HttpStatus.CONFLICT);
            }
            const application = await this.db.job_applications.create({
                data: {
                    owner_id,
                    job_posting_id: data.job_posting_id,
                    full_name: data.full_name,
                    email: data.email,
                    phone: data.phone ?? 'NT/APL',
                    location: data.location ?? 'NT/APL',
                    photo_url: data.photo_url ?? null,
                    experience_years: data.experience_years ?? 0,
                    resume: data.resume ?? null,
                    skills: data.skills ?? [],
                    work_history: data.work_history ?? [],
                    notes: data.notes ?? null,
                    source: data.source ?? 'manual',
                    status: data.status ?? entities_1.JobApplicationStatusEnum.applied,
                },
                include: { job_posting: true },
            });
            let onboarding_record_id = null;
            console.log('onboarding', data.status);
            if (data.status === entities_1.JobApplicationStatusEnum.hired ||
                data.status === 'hired') {
                console.log('onboarding 2', data.status);
                onboarding_record_id = await this.handleCandidateHiring(application, owner_id, data.template_id);
            }
            await this.notificationsService.sendNotification({
                owner_id,
                title: 'New candidate application',
                message: `${application.full_name} applied for ${job.title}`,
                type: 'RECRUITMENT',
                meta: { application_id: application.id, job_posting_id: job.id },
                include_admins: true,
                include_owner: true,
            });
            return {
                data: application,
                message: 'Application created successfully',
                status: common_1.HttpStatus.CREATED,
                details: onboarding_record_id ? { onboarding_record_id } : null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in createApplication: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async getApplications(query, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            (0, permission_resolver_helpers_1.assertScope)(permissions, 'recruitment.view');
            const page = query.page ?? 1;
            const limit = query.limit ?? 10;
            const skip = (page - 1) * limit;
            const where = { owner_id };
            if (query.job_posting_id)
                where.job_posting_id = query.job_posting_id;
            if (query.status) {
                where.status = query.status;
            }
            if (query.source)
                where.source = query.source;
            if (query.start_date || query.end_date) {
                where.applied_at = {
                    ...(query.start_date ? { gte: query.start_date } : {}),
                    ...(query.end_date ? { lte: query.end_date } : {}),
                };
            }
            if (query.search) {
                where.OR = [
                    { full_name: { contains: query.search } },
                    { email: { contains: query.search } },
                    { phone: { contains: query.search } },
                ];
            }
            const orderBy = query.sort === 'name'
                ? { full_name: 'asc' }
                : query.sort === 'oldest'
                    ? { applied_at: 'asc' }
                    : { applied_at: 'desc' };
            const [applications, total] = await Promise.all([
                this.db.job_applications.findMany({
                    where,
                    include: { job_posting: { include: { department: true } } },
                    orderBy,
                    skip,
                    take: limit,
                }),
                this.db.job_applications.count(where),
            ]);
            console.log({ where });
            return {
                data: applications,
                message: 'Applications retrieved successfully',
                status: common_1.HttpStatus.OK,
                details: this.buildPagination(page, limit, total),
            };
        }
        catch (error) {
            config_1.logger.error(`Error in getApplications: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async getApplicationById(id, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            (0, permission_resolver_helpers_1.assertScope)(permissions, 'recruitment.view');
            const application = await this.db.job_applications.findUnique({
                where: { id, owner_id },
                include: {
                    job_posting: { include: { department: true } },
                    tasks: true,
                },
            });
            if (!application) {
                throw (0, utils_1.createGeneralError)('Application not found', 'application', common_1.HttpStatus.NOT_FOUND);
            }
            return {
                data: application,
                message: 'Application retrieved successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in getApplicationById: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async handleCandidateHiring(application, owner_id, targetTemplateId) {
        console.log('checking', application);
        try {
            if (!application || !application.email)
                return null;
            let member = await this.db.members.findFirst({
                where: { email: application.email, owner_id },
            });
            if (!member) {
                let accessRole = await this.db.access_roles.findFirst({
                    where: { owner_id, slug: 'member' },
                });
                if (!accessRole) {
                    accessRole = await this.db.access_roles.findFirst({
                        where: { owner_id },
                    });
                }
                if (!accessRole) {
                    config_1.logger.warn(`No access role found for owner ${owner_id} when hiring ${application.email}`);
                    return null;
                }
                const candidateFullName = (application.full_name ||
                    application.name ||
                    'New Hire').trim();
                const nameParts = candidateFullName.split(' ');
                const firstName = nameParts[0] || 'New';
                const lastName = nameParts.slice(1).join(' ') || 'Hire';
                const hashedPassword = await this.hashService.hashPassword((0, crypto_1.randomUUID)().slice(0, 12));
                const employeeId = `EMP-${Date.now().toString().slice(-6)}`;
                member = await this.db.members.create({
                    data: {
                        email: application.email,
                        first_name: firstName,
                        last_name: lastName,
                        name: candidateFullName,
                        phone: application.phone || 'NT/APL',
                        employee_id: employeeId,
                        password: hashedPassword,
                        role: 'member',
                        access_role_id: accessRole.id,
                        owner_id,
                        department_id: application.job_posting?.department_id || null,
                        designation_id: application.job_posting?.designation_id || null,
                        image: {
                            file_name: 'profile.webp',
                            original_name: 'profile.webp',
                            file_size: 8192,
                            mime_type: 'image/webp',
                        },
                    },
                });
            }
            else {
                const candidateFullName = (application.full_name ||
                    application.name ||
                    member.name ||
                    'New Hire').trim();
                const nameParts = candidateFullName.split(' ');
                const firstName = nameParts[0] || member.first_name || 'New';
                const lastName = nameParts.slice(1).join(' ') || member.last_name || 'Hire';
                await this.db.members.update({
                    where: { id: member.id },
                    data: {
                        first_name: firstName,
                        last_name: lastName,
                        name: candidateFullName,
                        phone: application.phone && application.phone !== 'NT/APL'
                            ? application.phone
                            : member.phone,
                        department_id: application.job_posting?.department_id || member.department_id,
                        designation_id: application.job_posting?.designation_id || member.designation_id,
                    },
                });
            }
            let templateIdToUse = targetTemplateId;
            if (!templateIdToUse) {
                const defaultTemplate = await this.onboardingService.getDefaultTemplate(owner_id);
                templateIdToUse = defaultTemplate?.id;
            }
            const existingCase = await this.db.onboarding_cases.findFirst({
                where: {
                    member_id: member.id,
                },
                orderBy: { created_at: 'desc' },
            });
            if (existingCase) {
                if (templateIdToUse && existingCase.template_id !== templateIdToUse) {
                    await this.db.onboarding_cases.update({
                        where: { id: existingCase.id },
                        data: { template_id: templateIdToUse },
                    });
                }
                return existingCase.id;
            }
            const onboardingCase = await this.onboardingService.createCase({
                member_id: member.id,
                job_application_id: application.id,
                template_id: templateIdToUse || undefined,
                start_date: application.start_date
                    ? new Date(application.start_date)
                    : new Date(),
                notes: `Auto-assigned from recruitment pipeline hire of ${(application.full_name || application.name || '').trim()}`,
            }, owner_id);
            return onboardingCase?.id || onboardingCase?.data?.id || null;
        }
        catch (error) {
            config_1.logger.error(`Error in handleCandidateHiring: ${String(error)}`);
            return null;
        }
    }
    async updateApplication(id, data, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            (0, permission_resolver_helpers_1.assertScope)(permissions, 'recruitment.update');
            const existing = await this.db.job_applications.findUnique({
                where: { id, owner_id },
            });
            if (!existing) {
                throw (0, utils_1.createGeneralError)('Application not found', 'application', common_1.HttpStatus.NOT_FOUND);
            }
            const application = await this.db.job_applications.update({
                where: { id, owner_id },
                data,
                include: { job_posting: true },
            });
            let onboarding_record_id = null;
            if (data.status === entities_1.JobApplicationStatusEnum.hired ||
                data.status === 'hired') {
                onboarding_record_id = await this.handleCandidateHiring(application, owner_id, data.template_id);
            }
            return {
                data: application,
                message: 'Application updated successfully',
                status: common_1.HttpStatus.OK,
                details: onboarding_record_id ? { onboarding_record_id } : null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in updateApplication: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async updateApplicationStatus(id, data, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions, { manage: true });
            const existing = await this.db.job_applications.findUnique({
                where: { id, owner_id },
                include: { job_posting: true },
            });
            if (!existing) {
                throw (0, utils_1.createGeneralError)('Application not found', 'application', common_1.HttpStatus.NOT_FOUND);
            }
            const application = await this.db.job_applications.update({
                where: { id, owner_id },
                data: { status: data.status },
                include: { job_posting: true },
            });
            let onboarding_record_id = null;
            if (data.status === entities_1.JobApplicationStatusEnum.hired ||
                data.status === 'hired') {
                onboarding_record_id = await this.handleCandidateHiring(application, owner_id, data.template_id);
            }
            return {
                data: application,
                status: common_1.HttpStatus.OK,
                message: 'Application status updated successfully',
                details: onboarding_record_id ? { onboarding_record_id } : null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in updateApplicationStatus: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async deleteApplication(id, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            const existing = await this.db.job_applications.findUnique({
                where: { id, owner_id },
            });
            if (!existing) {
                throw (0, utils_1.createGeneralError)('Application not found', 'application', common_1.HttpStatus.NOT_FOUND);
            }
            await this.db.job_applications.delete({
                where: { id, owner_id },
            });
            return {
                data: null,
                message: 'Job application deleted successfully',
                status: common_1.HttpStatus.OK,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in deleteApplication: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async bulkUpdateApplicationStatus(data, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions, { manage: true });
            const applications = await this.db.job_applications.findMany({
                where: { id: (0, typeorm_1.In)(data.ids), owner_id },
                include: { job_posting: true },
            });
            await this.db.job_applications.updateMany({
                where: { id: (0, typeorm_1.In)(data.ids), owner_id },
                data: { status: data.status },
            });
            let last_onboarding_record_id = null;
            if (data.status === entities_1.JobApplicationStatusEnum.hired ||
                data.status === 'hired') {
                for (const app of applications) {
                    const caseId = await this.handleCandidateHiring(app, owner_id);
                    if (caseId)
                        last_onboarding_record_id = caseId;
                }
            }
            return {
                data: { updated_count: data.ids.length },
                message: 'Applications updated successfully',
                status: common_1.HttpStatus.OK,
                details: last_onboarding_record_id
                    ? { onboarding_record_id: last_onboarding_record_id }
                    : null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in bulkUpdateApplicationStatus: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async createTask(data, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions, { manage: true });
            const isTemplate = !data.application_id ||
                data.application_id === '00000000-0000-0000-0000-000000000000';
            if (!isTemplate && data.application_id) {
                const application = await this.db.job_applications.findUnique({
                    where: { id: data.application_id, owner_id },
                });
                if (!application) {
                    throw (0, utils_1.createGeneralError)('Application not found', 'application_id', common_1.HttpStatus.NOT_FOUND);
                }
            }
            const task = await this.db.recruitment_tasks.create({
                data: {
                    application_id: isTemplate ? null : data.application_id,
                    owner_id,
                    title: data.title,
                    description: data.description ?? null,
                    due_date: data.due_date ?? null,
                    public_token: (0, crypto_1.randomUUID)(),
                    status: entities_1.RecruitmentTaskStatusEnum.pending,
                    submission: data.files ? { template_files: data.files } : null,
                },
            });
            return {
                data: task,
                message: 'Recruitment task created successfully',
                status: common_1.HttpStatus.CREATED,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in createTask: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async updateTask(id, data, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions, { manage: true });
            const existing = await this.db.recruitment_tasks.findUnique({
                where: { id, owner_id },
            });
            if (!existing) {
                throw (0, utils_1.createGeneralError)('Recruitment task not found', 'task', common_1.HttpStatus.NOT_FOUND);
            }
            const updated = await this.db.recruitment_tasks.update({
                where: { id, owner_id },
                data: {
                    title: data.title !== undefined ? data.title : undefined,
                    description: data.description !== undefined ? data.description : undefined,
                    due_date: data.due_date !== undefined ? data.due_date : undefined,
                    submission: data.files !== undefined
                        ? data.files
                            ? { template_files: data.files }
                            : null
                        : undefined,
                },
            });
            return {
                data: updated,
                message: 'Recruitment task updated successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in updateTask: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async deleteTask(id, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions, { manage: true });
            const existing = await this.db.recruitment_tasks.findUnique({
                where: { id, owner_id },
            });
            if (!existing) {
                throw (0, utils_1.createGeneralError)('Recruitment task not found', 'task', common_1.HttpStatus.NOT_FOUND);
            }
            await this.db.recruitment_tasks.delete({
                where: { id, owner_id },
            });
            return {
                data: null,
                message: 'Recruitment task deleted successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in deleteTask: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async assignBulkTasks(data, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions, { manage: true });
            const templateTask = await this.db.recruitment_tasks.findUnique({
                where: { id: data.task_id, owner_id },
            });
            if (!templateTask) {
                throw (0, utils_1.createGeneralError)('Task template not found', 'task_id', common_1.HttpStatus.NOT_FOUND);
            }
            const assignedTasks = [];
            for (const appId of data.application_ids) {
                const application = await this.db.job_applications.findUnique({
                    where: { id: appId, owner_id },
                });
                if (!application) {
                    continue;
                }
                const task = await this.db.recruitment_tasks.create({
                    data: {
                        application_id: appId,
                        owner_id,
                        title: templateTask.title,
                        description: templateTask.description,
                        due_date: templateTask.due_date,
                        public_token: (0, crypto_1.randomUUID)(),
                        status: entities_1.RecruitmentTaskStatusEnum.pending,
                        submission: templateTask.submission,
                    },
                });
                try {
                    const frontendUrl = process.env.FRONTEND_URL ?? 'http://localhost:5173';
                    const taskUrl = `${frontendUrl}/tasks/${task.public_token}`;
                    this.mailService.sendMail('recruitment-task-mail', application.email, {
                        name: application.full_name,
                        title: task.title,
                        description: task.description || '',
                        link: taskUrl,
                    }, `Action required: ${task.title}`);
                }
                catch (emailError) {
                    config_1.logger.error(`Failed to send email to ${application.email}: ${String(emailError)}`);
                }
                assignedTasks.push(task);
            }
            return {
                data: assignedTasks,
                message: `Task assigned to ${assignedTasks.length} candidates successfully`,
                status: common_1.HttpStatus.CREATED,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in assignBulkTasks: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async getTasks(query, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            (0, permission_resolver_helpers_1.assertScope)(permissions, 'recruitment.view');
            const page = query.page ?? 1;
            const limit = query.limit ?? 10;
            const skip = (page - 1) * limit;
            const where = { owner_id };
            if (query.application_id) {
                if (query.application_id === '00000000-0000-0000-0000-000000000000') {
                    where.OR = [
                        { application_id: (0, typeorm_1.IsNull)() },
                        { application_id: '00000000-0000-0000-0000-000000000000' },
                    ];
                }
                else {
                    where.application_id = query.application_id;
                }
            }
            if (query.status)
                where.status = query.status;
            const [tasks, total] = await Promise.all([
                this.db.recruitment_tasks.findMany({
                    where,
                    include: { application: true },
                    orderBy: { created_at: 'desc' },
                    skip,
                    take: limit,
                }),
                this.db.recruitment_tasks.count({ where }),
            ]);
            return {
                data: tasks,
                message: 'Recruitment tasks retrieved successfully',
                status: common_1.HttpStatus.OK,
                details: this.buildPagination(page, limit, total),
            };
        }
        catch (error) {
            config_1.logger.error(`Error in getTasks: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async sendTaskEmail(id, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions, { manage: true });
            const task = await this.db.recruitment_tasks.findUnique({
                where: { id, owner_id },
                include: { application: true },
            });
            if (!task) {
                throw (0, utils_1.createGeneralError)('Task not found', 'task', common_1.HttpStatus.NOT_FOUND);
            }
            const frontendUrl = process.env.FRONTEND_URL ?? 'http://localhost:5173';
            const taskUrl = `${frontendUrl}/tasks/${task.public_token}`;
            this.mailService.sendMail('recruitment-task-mail', task.application.email, {
                name: task.application.full_name,
                title: task.title,
                description: task.description || '',
                link: taskUrl,
            }, `Action required: ${task.title}`);
            return {
                data: { task_url: taskUrl },
                message: 'Task email sent successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in sendTaskEmail: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async getStats(owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions);
            const [open_positions, active_candidates, interviews_scheduled, hiredApplications,] = await Promise.all([
                this.db.job_postings.count({
                    where: {
                        owner_id,
                        status: entities_1.JobPostingStatusEnum.published,
                        record_status: (0, typeorm_1.Not)('deleted'),
                    },
                }),
                this.db.job_applications.count({
                    where: {
                        owner_id,
                        status: {
                            in: [
                                'applied',
                                'screening',
                                'interview',
                                'offer',
                            ],
                        },
                    },
                }),
                this.db.job_applications.count({
                    where: {
                        owner_id,
                        status: entities_1.JobApplicationStatusEnum.interview,
                    },
                }),
                this.db.job_applications.findMany({
                    where: {
                        owner_id,
                        status: entities_1.JobApplicationStatusEnum.hired,
                    },
                    select: { applied_at: true, updated_at: true },
                }),
            ]);
            const hireDurations = hiredApplications
                .map((item) => {
                const applied = new Date(item.applied_at).getTime();
                const hired = new Date(item.updated_at).getTime();
                return Math.max(0, hired - applied);
            })
                .filter(Boolean);
            const avg_time_to_hire_days = hireDurations.length > 0
                ? Math.round(hireDurations.reduce((sum, ms) => sum + ms, 0) /
                    hireDurations.length /
                    (1000 * 60 * 60 * 24))
                : 0;
            return {
                data: {
                    open_positions,
                    active_candidates,
                    interviews_scheduled,
                    avg_time_to_hire_days,
                },
                message: 'Recruitment stats retrieved successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in getStats: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async createApiKey(data, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions, { apiKeys: true });
            const rawKey = `tt_${(0, crypto_1.randomBytes)(24).toString('hex')}`;
            const keyPrefix = rawKey.slice(0, 12);
            const secretHash = await this.hashService.hashPassword(rawKey);
            const apiKey = await this.db.tenant_api_keys.create({
                data: {
                    owner_id,
                    name: data.name,
                    key_prefix: keyPrefix,
                    secret_hash: secretHash,
                    status: entities_1.TenantApiKeyStatusEnum.active,
                },
            });
            return {
                data: { ...apiKey, secret: rawKey },
                message: 'API key created successfully',
                status: common_1.HttpStatus.CREATED,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in createApiKey: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async listApiKeys(owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions, { apiKeys: true });
            const keys = await this.db.tenant_api_keys.findMany({
                where: { owner_id, status: (0, typeorm_1.Not)('revoked') },
                orderBy: { created_at: 'desc' },
            });
            return {
                data: keys.map(({ secret_hash, ...key }) => key),
                message: 'API keys retrieved successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in listApiKeys: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async revokeApiKey(id, owner_id, ctx) {
        try {
            const permissions = await this.getPermissions(ctx);
            (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(permissions, { apiKeys: true });
            const existing = await this.db.tenant_api_keys.findUnique({
                where: { id, owner_id },
            });
            if (!existing) {
                throw (0, utils_1.createGeneralError)('API key not found', 'id', common_1.HttpStatus.NOT_FOUND);
            }
            await this.db.tenant_api_keys.update({
                where: { id, owner_id },
                data: { status: entities_1.TenantApiKeyStatusEnum.revoked },
            });
            return {
                data: null,
                message: 'API key revoked successfully',
                status: common_1.HttpStatus.OK,
                details: null,
            };
        }
        catch (error) {
            config_1.logger.error(`Error in revokeApiKey: ${String(error)}`);
            if (error instanceof config_1.ErrorResponse)
                throw error;
            throw (0, utils_1.createGeneralError)(String(error), 'root', common_1.HttpStatus.BAD_REQUEST);
        }
    }
    async validateApiKey(ownerId, apiKey) {
        const prefix = apiKey.slice(0, 12);
        const record = await this.db.tenant_api_keys.findFirst({
            where: {
                owner_id: ownerId,
                key_prefix: prefix,
                status: entities_1.TenantApiKeyStatusEnum.active,
            },
        });
        if (!record)
            return null;
        const valid = await this.hashService.comparePasswords(apiKey, record.secret_hash);
        if (!valid)
            return null;
        await this.db.tenant_api_keys.update({
            where: { id: record.id },
            data: { last_used_at: new Date() },
        });
        return record;
    }
    async getPublicJobs(orgSlug) {
        const owner = await this.resolveOwnerByOrgSlug(orgSlug);
        const jobs = await this.db.job_postings.findMany({
            where: {
                owner_id: owner.id,
                status: entities_1.JobPostingStatusEnum.published,
                record_status: (0, typeorm_1.Not)('deleted'),
            },
            include: { department: true },
            orderBy: { created_at: 'desc' },
        });
        return {
            data: jobs,
            message: 'Published jobs retrieved successfully',
            status: common_1.HttpStatus.OK,
            details: null,
        };
    }
    async getPublicJob(orgSlug, jobSlug) {
        const owner = await this.resolveOwnerByOrgSlug(orgSlug);
        const job = await this.db.job_postings.findFirst({
            where: {
                owner_id: owner.id,
                slug: jobSlug,
                status: entities_1.JobPostingStatusEnum.published,
                record_status: (0, typeorm_1.Not)('deleted'),
            },
            include: { department: true },
        });
        if (!job) {
            throw (0, utils_1.createGeneralError)('Job not found', 'jobSlug', common_1.HttpStatus.NOT_FOUND);
        }
        return {
            data: job,
            message: 'Job retrieved successfully',
            status: common_1.HttpStatus.OK,
            details: null,
        };
    }
    async publicApply(orgSlug, jobSlug, data) {
        const owner = await this.resolveOwnerByOrgSlug(orgSlug);
        const job = await this.db.job_postings.findFirst({
            where: {
                owner_id: owner.id,
                slug: jobSlug,
                status: entities_1.JobPostingStatusEnum.published,
                record_status: (0, typeorm_1.Not)('deleted'),
            },
        });
        if (!job) {
            throw (0, utils_1.createGeneralError)('Job not found', 'jobSlug', common_1.HttpStatus.NOT_FOUND);
        }
        const application = await this.db.job_applications.create({
            data: {
                owner_id: owner.id,
                job_posting_id: job.id,
                full_name: data.full_name,
                email: data.email,
                phone: data.phone ?? 'NT/APL',
                location: data.location ?? 'NT/APL',
                experience_years: data.experience_years ?? 0,
                resume: data.resume ?? null,
                skills: data.skills ?? [],
                work_history: data.work_history ?? [],
                source: 'public_form',
            },
        });
        await this.notificationsService.sendNotification({
            owner_id: owner.id,
            title: 'New public application',
            message: `${data.full_name} applied for ${job.title}`,
            type: 'RECRUITMENT',
            meta: { application_id: application.id, job_posting_id: job.id },
            include_admins: true,
            include_owner: true,
        });
        return {
            data: { id: application.id },
            message: 'Application submitted successfully',
            status: common_1.HttpStatus.CREATED,
            details: null,
        };
    }
    async publicApiListJobs(ownerId) {
        const jobs = await this.db.job_postings.findMany({
            where: {
                owner_id: ownerId,
                status: entities_1.JobPostingStatusEnum.published,
                record_status: (0, typeorm_1.Not)('deleted'),
            },
            orderBy: { created_at: 'desc' },
        });
        return {
            data: jobs,
            message: 'Jobs retrieved successfully',
            status: common_1.HttpStatus.OK,
            details: null,
        };
    }
    async publicApiCreateApplication(ownerId, payload) {
        const job = await this.db.job_postings.findUnique({
            where: {
                id: payload.job_posting_id,
                owner_id: ownerId,
                status: entities_1.JobPostingStatusEnum.published,
                record_status: (0, typeorm_1.Not)('deleted'),
            },
        });
        if (!job) {
            throw (0, utils_1.createGeneralError)('Job posting not found', 'job_posting_id', common_1.HttpStatus.NOT_FOUND);
        }
        const application = await this.db.job_applications.create({
            data: {
                owner_id: ownerId,
                job_posting_id: job.id,
                full_name: payload.full_name,
                email: payload.email,
                phone: payload.phone ?? 'NT/APL',
                location: payload.location ?? 'NT/APL',
                experience_years: payload.experience_years ?? 0,
                resume: payload.resume ?? null,
                skills: payload.skills ?? [],
                work_history: payload.work_history ?? [],
                source: 'public_api',
            },
        });
        return {
            data: application,
            message: 'Application submitted successfully',
            status: common_1.HttpStatus.CREATED,
            details: null,
        };
    }
    async getTaskByToken(token) {
        const task = await this.db.recruitment_tasks.findFirst({
            where: { public_token: token },
            include: {
                application: {
                    include: { job_posting: true },
                },
            },
        });
        if (!task) {
            throw (0, utils_1.createGeneralError)('Task not found', 'token', common_1.HttpStatus.NOT_FOUND);
        }
        return {
            data: {
                id: task.id,
                title: task.title,
                description: task.description,
                due_date: task.due_date,
                status: task.status,
                candidate_name: task.application.full_name,
                job_title: task.application.job_posting?.title ?? null,
            },
            message: 'Task retrieved successfully',
            status: common_1.HttpStatus.OK,
            details: null,
        };
    }
    async submitTaskByToken(token, data) {
        const task = await this.db.recruitment_tasks.findFirst({
            where: { public_token: token },
            include: { application: true },
        });
        if (!task) {
            throw (0, utils_1.createGeneralError)('Task not found', 'token', common_1.HttpStatus.NOT_FOUND);
        }
        const updated = await this.db.recruitment_tasks.update({
            where: { id: task.id },
            data: {
                status: entities_1.RecruitmentTaskStatusEnum.submitted,
                submission: {
                    text: data.text ?? null,
                    files: data.files ?? [],
                    submitted_at: new Date().toISOString(),
                },
            },
        });
        await this.notificationsService.sendNotification({
            owner_id: task.owner_id,
            title: 'Candidate task submitted',
            message: `${task.application.full_name} submitted "${task.title}"`,
            type: 'RECRUITMENT',
            meta: { task_id: task.id, application_id: task.application_id },
            include_admins: true,
            include_owner: true,
        });
        return {
            data: updated,
            message: 'Task submitted successfully',
            status: common_1.HttpStatus.OK,
            details: null,
        };
    }
    parseDurationMinutes(durationStr) {
        if (!durationStr)
            return 45;
        const match = durationStr.match(/(\d+)/);
        if (match) {
            let num = parseInt(match[1], 10);
            if (durationStr.toLowerCase().includes('hour')) {
                num = num * 60;
            }
            return num;
        }
        return 45;
    }
    async createGoogleMeetEvent(params) {
        try {
            const clientId = process.env.GOOGLE_CLIENT_ID ||
                '978902865675-gb1cggjgktplvh84mokg9blvqmfarn7r.apps.googleusercontent.com';
            const clientSecret = process.env.GOOGLE_CLIENT_SECRET ||
                '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';
            const refreshToken = process.env.GOOGLE_REFRESH_TOKEN ||
                '1//04ig9Mo9kSUN6CgYIARAAGAQSNwF-L9IrToNk5taoQpWZFoUIjzbUMi8CdDacAmsgbsilWQbSo6t4DCptWAgJdaG2HfZ961PF1Cs';
            const accessToken = process.env.GOOGLE_ACCESS_TOKEN ||
                'ya29.a0ARGnu0Zx5sDjlbjDfnUagFhKPwOe7AfRLBAylIxPCmvQRfkmSX6RudiNjKz2_7josohyeZTkOj9DJC1TXWRc4xfW18RQ4VmzUE2tKcklmiCGnKTcz8_m_KRiovmEeI2WcKlvTzU0lZKXMmlPkTXA0IinVlNaw_AX79NbgZhyTEAKZcAK0A8EO5oonDQ-jh2cI55KUsYaCgYKARoSARESFQHGX2MiidlFxo--pil2KWgycz2ylQ0206';
            const oauth2Client = new googleapis_1.google.auth.OAuth2(clientId, clientSecret);
            oauth2Client.setCredentials({
                refresh_token: refreshToken,
                access_token: accessToken,
            });
            const calendar = googleapis_1.google.calendar({ version: 'v3', auth: oauth2Client });
            const duration = params.durationMinutes || 45;
            const endDateTime = new Date(params.startDateTime.getTime() + duration * 60 * 1000);
            const attendees = [];
            if (params.candidateEmail && params.candidateEmail.includes('@')) {
                attendees.push({ email: params.candidateEmail });
            }
            if (params.interviewerEmails && Array.isArray(params.interviewerEmails)) {
                params.interviewerEmails.forEach((email) => {
                    if (email && typeof email === 'string' && email.includes('@')) {
                        attendees.push({ email });
                    }
                });
            }
            const res = await calendar.events.insert({
                calendarId: 'primary',
                conferenceDataVersion: 1,
                requestBody: {
                    summary: params.summary,
                    description: params.description || '',
                    start: {
                        dateTime: params.startDateTime.toISOString(),
                        timeZone: params.timeZone || 'Asia/Dhaka',
                    },
                    end: {
                        dateTime: endDateTime.toISOString(),
                        timeZone: params.timeZone || 'Asia/Dhaka',
                    },
                    attendees: attendees.length > 0 ? attendees : undefined,
                    conferenceData: {
                        createRequest: {
                            requestId: `meet-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
                            conferenceSolutionKey: {
                                type: 'hangoutsMeet',
                            },
                        },
                    },
                },
            });
            const meetLink = res.data.hangoutLink ||
                res.data.conferenceData?.entryPoints?.find((ep) => ep.entryPointType === 'video')?.uri;
            if (meetLink) {
                config_1.logger.log(`Google Meet created successfully: ${meetLink}`);
                return meetLink;
            }
        }
        catch (err) {
            config_1.logger.error('Failed to create Google Meet event via API, generating fallback Google Meet link:', err?.message || err);
        }
        const p1 = Math.random().toString(36).substring(2, 5);
        const p2 = Math.random().toString(36).substring(2, 6);
        const p3 = Math.random().toString(36).substring(2, 5);
        const generatedMeetLink = `https://meet.google.com/${p1}-${p2}-${p3}`;
        config_1.logger.log(`Generated fallback Google Meet link: ${generatedMeetLink}`);
        return generatedMeetLink;
    }
    async createZoomMeetingEvent(params) {
        try {
            const accountId = process.env.ZOOM_ACCOUNT_ID || 'H02YfDVeReeSH6xmx2bKvw';
            const clientId = process.env.ZOOM_CLIENT_ID || 'RgwVf5UqQV6wxxY4UuvN0w';
            const clientSecret = process.env.ZOOM_CLIENT_SECRET || '5jEQzuBxtINf0W4NLu5zA8wmeEH4kPyO';
            const authHeader = 'Basic ' +
                Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
            const tokenRes = await fetch(`https://zoom.us/oauth/token?grant_type=account_credentials&account_id=${accountId}`, {
                method: 'POST',
                headers: {
                    Authorization: authHeader,
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
            });
            const tokenData = (await tokenRes.json());
            if (tokenData.access_token) {
                const meetingRes = await fetch('https://api.zoom.us/v2/users/me/meetings', {
                    method: 'POST',
                    headers: {
                        Authorization: `Bearer ${tokenData.access_token}`,
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        topic: params.summary,
                        type: 2,
                        start_time: params.startDateTime.toISOString(),
                        duration: params.durationMinutes || 45,
                        agenda: params.description || 'Interview Session',
                        settings: {
                            host_video: true,
                            participant_video: true,
                            join_before_host: true,
                            mute_upon_entry: false,
                        },
                    }),
                });
                const meetingData = (await meetingRes.json());
                const zoomLink = meetingData.join_url;
                if (zoomLink) {
                    config_1.logger.log(`Zoom meeting created successfully via API: ${zoomLink}`);
                    return zoomLink;
                }
                else {
                    config_1.logger.error('Zoom API returned error or no join_url:', meetingData);
                }
            }
            else {
                config_1.logger.error('Failed to obtain Zoom access token:', tokenData.reason || tokenData.error || tokenData);
            }
        }
        catch (err) {
            config_1.logger.error('Failed to create Zoom meeting via Zoom API:', err?.message || err);
        }
        const meetingId = Math.floor(80000000000 + Math.random() * 19999999999);
        const pwd = Math.random().toString(36).substring(2, 8);
        const generatedZoomLink = `https://zoom.us/j/${meetingId}?pwd=${pwd}`;
        config_1.logger.log(`Generated fallback Zoom meeting link: ${generatedZoomLink}`);
        return generatedZoomLink;
    }
    async scheduleInterview(data, owner_id, permCtx) {
        const perms = await this.getPermissions(permCtx);
        (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(perms, { manage: true });
        const owner = await this.db.owner.findFirst({
            where: { id: owner_id },
            include: { company: true },
        });
        const rawName = owner?.company?.name;
        const companyName = rawName && rawName.trim() !== 'NT/APL' && rawName.trim() !== ''
            ? rawName.trim()
            : 'Team Tracker';
        const rawAddress = owner?.company?.address || owner?.address;
        const companyAddress = rawAddress && rawAddress.trim() !== 'NT/APL' && rawAddress.trim() !== ''
            ? rawAddress.trim()
            : 'Company Address';
        const scheduledApplications = [];
        const emailsSent = [];
        for (const applicationId of data.application_ids) {
            const application = await this.db.job_applications.findFirst({
                where: { id: applicationId, owner_id },
                include: { job_posting: true },
            });
            if (!application)
                continue;
            const interviewAt = new Date(`${data.interview_date}T${data.interview_time}`);
            let finalMeetingLink = (data.meeting_link || '').trim();
            const platformName = (data.platform_name || '').toLowerCase();
            if (!finalMeetingLink) {
                if (platformName.includes('zoom')) {
                    const generatedZoom = await this.createZoomMeetingEvent({
                        summary: data.subject ||
                            `Interview Invitation — ${application.job_posting?.title || 'Job Position'}`,
                        description: data.instructions ||
                            `Interview with ${application.full_name} for ${application.job_posting?.title || 'Job Position'}`,
                        startDateTime: interviewAt,
                        durationMinutes: this.parseDurationMinutes(data.duration),
                    });
                    if (generatedZoom) {
                        finalMeetingLink = generatedZoom;
                    }
                }
                if (!finalMeetingLink) {
                    const generatedMeet = await this.createGoogleMeetEvent({
                        summary: data.subject ||
                            `Interview Invitation — ${application.job_posting?.title || 'Job Position'}`,
                        description: data.instructions ||
                            `Interview with ${application.full_name} for ${application.job_posting?.title || 'Job Position'}`,
                        startDateTime: interviewAt,
                        durationMinutes: this.parseDurationMinutes(data.duration),
                        candidateEmail: application.email,
                        interviewerEmails: data.interviewers,
                        timeZone: data.timezone,
                    });
                    if (generatedMeet) {
                        finalMeetingLink = generatedMeet;
                    }
                }
            }
            let joinButtonText = 'Join Meeting';
            if (platformName.includes('zoom')) {
                joinButtonText = 'Join Zoom Meeting';
            }
            else if (platformName.includes('meet') ||
                platformName.includes('google')) {
                joinButtonText = 'Join with Google Meet';
            }
            else if (data.platform_name) {
                joinButtonText = `Join ${data.platform_name}`;
            }
            const notesObj = {
                subject: data.subject,
                interviewers: data.interviewers || [],
                interview_type: data.interview_type || 'Technical Interview',
                platform_name: data.platform_name || 'Google Meet',
                duration: data.duration || '45 minutes',
                meeting_link: finalMeetingLink,
                instructions: data.instructions || '',
                scheduled_at: new Date().toISOString(),
            };
            const updated = await this.db.job_applications.update({
                where: { id: applicationId },
                data: {
                    status: entities_1.JobApplicationStatusEnum.interview,
                    interview_at: interviewAt,
                    notes: JSON.stringify(notesObj),
                },
            });
            scheduledApplications.push(updated);
            if (data.send_email !== false && application.email) {
                const interviewersList = Array.isArray(data.interviewers) && data.interviewers.length > 0
                    ? data.interviewers.join(', ')
                    : 'Hiring Team';
                const isLinkValid = finalMeetingLink &&
                    finalMeetingLink !== '#' &&
                    (finalMeetingLink.startsWith('http://') ||
                        finalMeetingLink.startsWith('https://'));
                const meetingLinkHref = isLinkValid ? finalMeetingLink : '#';
                const meetingLinkDisplay = isLinkValid
                    ? finalMeetingLink
                    : 'Link will be provided before the interview';
                const placeholders = {
                    subject: data.subject ||
                        `Interview Invitation — ${application.job_posting?.title || 'Job Position'}`,
                    company_name: companyName,
                    candidate_name: application.full_name,
                    candidate_email: application.email || '',
                    job_title: application.job_posting?.title || 'Job Position',
                    interview_date: data.interview_date,
                    interview_time: data.interview_time,
                    timezone: data.timezone || 'GMT+6',
                    interview_type: data.interview_type || 'Technical Interview',
                    platform_name: data.platform_name || 'Zoom',
                    duration: data.duration || '45 minutes',
                    interviewer_name: interviewersList,
                    interviewer_role: application.job_posting?.title
                        ? `${application.job_posting.title} Interviewer`
                        : 'Interviewer',
                    meeting_link: meetingLinkHref,
                    meeting_link_display: meetingLinkDisplay,
                    join_button_text: joinButtonText,
                    instructions: data.instructions ||
                        '<p>Please review the interview details above and join using the meeting link at the scheduled time.</p>',
                    sender_name: owner?.name || 'Hiring Team',
                    sender_role: 'Hiring Manager',
                    company_address: companyAddress,
                };
                try {
                    this.mailService.sendMail('interview-schedule-email', application.email, placeholders, data.subject || 'Interview Scheduled');
                    emailsSent.push(application.email);
                }
                catch (err) {
                    config_1.logger.error(`Failed to send interview email to ${application.email}:`, err);
                }
            }
        }
        return {
            data: {
                scheduled_count: scheduledApplications.length,
                emails_sent: emailsSent,
            },
            message: `Successfully scheduled interview for ${scheduledApplications.length} candidate(s)`,
            status: common_1.HttpStatus.OK,
            details: null,
        };
    }
    async cancelInterview(data, owner_id, permCtx) {
        const perms = await this.getPermissions(permCtx);
        (0, permission_resolver_helpers_1.assertRecruitmentModuleAccess)(perms, { manage: true });
        const owner = await this.db.owner.findFirst({
            where: { id: owner_id },
            include: { company: true },
        });
        const rawName = owner?.company?.name || owner?.name;
        const companyName = rawName && rawName.trim() !== 'NT/APL' && rawName.trim() !== ''
            ? rawName.trim()
            : 'Team Tracker';
        const rawAddress = owner?.company?.address || owner?.address;
        const companyAddress = rawAddress && rawAddress.trim() !== 'NT/APL' && rawAddress.trim() !== ''
            ? rawAddress.trim()
            : 'Company Address';
        const cancelledApplications = [];
        const emailsSent = [];
        for (const applicationId of data.application_ids) {
            const application = await this.db.job_applications.findFirst({
                where: { id: applicationId, owner_id },
                include: { job_posting: true },
            });
            if (!application)
                continue;
            let prevDate = 'Scheduled Date';
            let prevTime = '';
            if (application.interview_at) {
                const d = new Date(application.interview_at);
                prevDate = d.toLocaleDateString('en-US', {
                    weekday: 'short',
                    month: 'short',
                    day: 'numeric',
                    year: 'numeric',
                });
                prevTime = d.toLocaleTimeString('en-US', {
                    hour: '2-digit',
                    minute: '2-digit',
                });
            }
            let parsedNotes = {};
            try {
                if (application.notes) {
                    parsedNotes = JSON.parse(application.notes);
                }
            }
            catch {
                parsedNotes = { raw: application.notes };
            }
            parsedNotes.cancellation = {
                cancelled_at: new Date().toISOString(),
                reason: data.reason || null,
            };
            const updated = await this.db.job_applications.update({
                where: { id: applicationId },
                data: {
                    interview_at: null,
                    notes: JSON.stringify(parsedNotes),
                },
            });
            cancelledApplications.push(updated);
            if (data.send_email !== false && application.email) {
                const reasonHtml = data.reason
                    ? `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin-bottom: 18px;">
              <tr>
                <td style="background-color: #fff1f2; border: 1px solid #fecdd3; border-left: 4px solid #ef4444; border-radius: 6px; padding: 12px 16px;">
                  <p style="margin: 0 0 4px 0; font-size: 11px; font-weight: 700; color: #be123c; text-transform: uppercase; letter-spacing: 0.05em;">Reason for Cancellation</p>
                  <p style="margin: 0; font-size: 14px; font-weight: 400; color: #334155; line-height: 1.6; white-space: pre-wrap;">${data.reason}</p>
                </td>
              </tr>
            </table>`
                    : '';
                const placeholders = {
                    company_name: companyName,
                    candidate_name: application.full_name,
                    job_title: application.job_posting?.title || 'Job Position',
                    interview_date: prevDate,
                    interview_time: prevTime,
                    cancellation_reason: reasonHtml,
                    sender_name: owner?.name || 'Hiring Team',
                    sender_role: 'Hiring Manager',
                    company_address: companyAddress,
                };
                try {
                    this.mailService.sendMail('interview-cancellation-email', application.email, placeholders, 'Interview Schedule Cancellation');
                    emailsSent.push(application.email);
                }
                catch (err) {
                    config_1.logger.error(`Failed to send cancellation email to ${application.email}:`, err);
                }
            }
        }
        return {
            data: {
                cancelled_count: cancelledApplications.length,
                emails_sent: emailsSent,
            },
            message: `Successfully cancelled interview for ${cancelledApplications.length} candidate(s)`,
            status: common_1.HttpStatus.OK,
            details: null,
        };
    }
};
exports.RecruitmentService = RecruitmentService;
exports.RecruitmentService = RecruitmentService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [config_1.DatabaseService,
        permission_resolver_service_1.PermissionResolverService,
        notifications_service_1.NotificationsService,
        config_1.HashService,
        config_1.MailService,
        onboarding_service_1.OnboardingService])
], RecruitmentService);
//# sourceMappingURL=recruitment.service.js.map