import { differenceInDays, endOfMonth, endOfYear, startOfMonth } from 'date-fns';
import { GraphQLError } from 'graphql';
import { AVERAGE_MONTHLY_WORK_DAYS, AVERAGE_MONTHLY_WORK_HOURS, } from '../../../shared/constants.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { EmployeesProvider } from '../../salaries/providers/employees.provider.js';
import { SalariesProvider } from '../../salaries/providers/salaries.provider.js';
import { LedgerProvider } from '../providers/ledger.provider.js';
function roundHalf(n) {
    return Math.round(n * 2) / 2;
}
function vacationDaysPerYearsOfExperience(years) {
    if (years >= 0 && years <= 5) {
        return roundHalf(years * 12);
    }
    const firstFiveYearsVacationDays = 5 * 12;
    if (years >= 6 && years <= 8) {
        const remainingYearsVacationDays = (years - 5) * 17;
        return roundHalf(firstFiveYearsVacationDays + remainingYearsVacationDays);
    }
    if (years >= 9) {
        const nextThreeYearsVacationDays = 3 * 17;
        const remainingYearsVacationDays = (years - 8) * 23;
        return roundHalf(firstFiveYearsVacationDays + nextThreeYearsVacationDays + remainingYearsVacationDays);
    }
    return 0;
}
export async function calculateVacationReserveAmount(injector, year) {
    const { ownerId, salaries: { vacationReserveTaxCategoryId, vacationReserveExpensesTaxCategoryId }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
    if (!vacationReserveTaxCategoryId) {
        throw new GraphQLError(`Vacation reserves tax category is not set`);
    }
    if (!vacationReserveExpensesTaxCategoryId) {
        throw new GraphQLError(`Vacation reserves expenses tax category is not set`);
    }
    const salariesPromise = injector
        .get(SalariesProvider)
        .getSalaryRecordsByDates({ fromDate: '2000-01', toDate: `${year}-12` });
    const employeesPromise = injector
        .get(EmployeesProvider)
        .getEmployeesByEmployerLoader.load(ownerId);
    const vacationLedgerRecordsPromise = injector
        .get(LedgerProvider)
        .getLedgerRecordsByFinancialEntityIdLoader.load(vacationReserveTaxCategoryId);
    const [salaries, employees, vacationLedgerRecords] = await Promise.all([
        salariesPromise,
        employeesPromise,
        vacationLedgerRecordsPromise,
    ]);
    const employeeMap = new Map();
    const yearEnd = endOfYear(new Date(`${year}-01-01`));
    for (const employee of employees) {
        if (employee.start_work_date.getFullYear() > year) {
            continue;
        }
        if (employee.end_work_date && employee.end_work_date.getTime() < yearEnd.getTime()) {
            continue;
        }
        employeeMap.set(employee.business_id, {
            employee,
            vacationDays: 0,
            startWorkDate: employee.start_work_date,
            firstSalary: null,
            latestSalary: null,
            salaries: [],
            isHourly: false,
        });
    }
    for (const salaryRecord of salaries) {
        // skip if employee is not found
        if (!employeeMap.has(salaryRecord.employee_id)) {
            continue;
        }
        const employee = employeeMap.get(salaryRecord.employee_id);
        // skip if salary record outside of employee's employment dates
        if (employee.employee.start_work_date.getTime() >
            endOfMonth(new Date(`${salaryRecord.month}-01`)).getTime()) {
            console.log(`salary record of employee ${employee.employee.first_name} before start date - ${employee.employee.start_work_date}`);
            continue;
        }
        if (employee.employee.end_work_date &&
            employee.employee.end_work_date.getTime() < new Date(`${salaryRecord.month}-01`).getTime()) {
            console.log(`salary record of employee ${employee.employee.first_name} after end date - ${employee.employee.end_work_date}`);
            continue;
        }
        if (!employee.firstSalary || salaryRecord.month < employee.firstSalary.month) {
            employee.firstSalary = salaryRecord;
        }
        if (!employee.latestSalary || salaryRecord.month > employee.latestSalary.month) {
            employee.latestSalary = salaryRecord;
        }
        if (salaryRecord.vacation_days_balance) {
            const daysExploit = Number(salaryRecord.vacation_days_balance);
            employee.vacationDays += daysExploit;
        }
        employee.salaries.push(salaryRecord);
    }
    let vacationReserveAmount = 0;
    let reservesPrompt = `Vacation reserve for ${year}:`;
    for (const employeeData of Array.from(employeeMap.values())) {
        if (!employeeData.latestSalary || !employeeData.firstSalary) {
            throw new GraphQLError(`Employee ${employeeData.employee.first_name} has no salary records`);
        }
        // calculate cumulative vacation days by seniority
        const isFirstSalaryHourly = Number.isNaN(Number(employeeData.firstSalary.job_percentage)) ||
            Number(employeeData.firstSalary.job_percentage) === 0;
        const formalEmployeeStartDate = employeeData.employee.start_work_date;
        const adjustedEmployeeStartDate = isFirstSalaryHourly
            ? startOfMonth(formalEmployeeStartDate)
            : formalEmployeeStartDate;
        const seniority = differenceInDays(yearEnd, adjustedEmployeeStartDate) / 365;
        const cumulativeVacationDays = vacationDaysPerYearsOfExperience(seniority);
        employeeData.vacationDays += cumulativeVacationDays;
        const averageJobPercentage = employeeData.salaries.reduce((accumulator, salary) => {
            let part = 0;
            if (!Number.isNaN(Number(salary.job_percentage)) && Number(salary.job_percentage) !== 0) {
                part = Number(salary.job_percentage) / 100;
            }
            else if (!Number.isNaN(Number(salary.hours)) && Number(salary.hours) !== 0) {
                part = Number(salary.hours) / AVERAGE_MONTHLY_WORK_HOURS;
            }
            return accumulator + part;
        }, 0) / employeeData.salaries.length;
        const vacationDaysByJobPercentage = employeeData.vacationDays * averageJobPercentage;
        const isLatestSalaryHourly = Number.isNaN(Number(employeeData.latestSalary.job_percentage)) ||
            Number(employeeData.latestSalary.job_percentage) === 0;
        const latestSalaryAmount = isLatestSalaryHourly
            ? Number(employeeData.latestSalary.hourly_rate) * AVERAGE_MONTHLY_WORK_HOURS
            : Number(employeeData.latestSalary.base_salary) +
                Number(employeeData.latestSalary.global_additional_hours);
        if (!latestSalaryAmount || Number.isNaN(latestSalaryAmount)) {
            throw new GraphQLError(`Employee ${employeeData.employee.first_name} has no valid salary amount for last month`);
        }
        const dailySalary = latestSalaryAmount / AVERAGE_MONTHLY_WORK_DAYS;
        const employeeReserve = Math.round(vacationDaysByJobPercentage * dailySalary);
        reservesPrompt += `\n- Employee ${employeeData.employee.first_name} reserve: ${vacationDaysByJobPercentage} days; ${dailySalary.toFixed(2)} daily payment; ${employeeReserve}`;
        vacationReserveAmount += employeeReserve;
    }
    const prevVacationReserveAmount = vacationLedgerRecords.reduce((acc, record) => {
        let factor = 0;
        if (record.value_date.getTime() >= new Date(year, 11, 31).getTime()) {
            return acc;
        }
        if (record.credit_entity1 === vacationReserveTaxCategoryId ||
            record.debit_entity1 === vacationReserveExpensesTaxCategoryId) {
            factor = 1;
        }
        else if (record.credit_entity1 === vacationReserveExpensesTaxCategoryId ||
            record.debit_entity1 === vacationReserveTaxCategoryId) {
            factor = -1;
        }
        return acc + Number(record.credit_local_amount1) * factor;
    }, 0);
    reservesPrompt += `\n- Prev reserve: ${prevVacationReserveAmount}`;
    vacationReserveAmount -= prevVacationReserveAmount;
    console.debug(reservesPrompt);
    return { vacationReserveAmount };
}
//# sourceMappingURL=vacation-reserve.helper.js.map