import { CountryCode, Currency } from '../../../shared/enums.js';
import { formatCurrency, formatFinancialAmount } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { ExchangeProvider } from '../../exchange-rates/providers/exchange.provider.js';
import { getTransactionDebitDate } from '../../transactions/helpers/debit-date.helper.js';
import { TransactionsProvider } from '../../transactions/providers/transactions.provider.js';
export class BusinessTripError extends Error {
    constructor(message) {
        super(message);
    }
}
export function convertSummaryCategoryDataToRow(category, data) {
    const excessExpenditure = (data[Currency.Ils]?.taxable ?? 0) - (data[Currency.Ils]?.total ?? 0);
    return {
        type: category,
        totalForeignCurrency: formatFinancialAmount(data[Currency.Usd]?.total ?? 0, Currency.Usd),
        taxableForeignCurrency: formatFinancialAmount(data[Currency.Usd]?.taxable ?? 0, Currency.Usd),
        maxTaxableForeignCurrency: formatFinancialAmount(data[Currency.Usd]?.maxTaxable ?? 0, Currency.Usd),
        totalLocalCurrency: formatFinancialAmount(data[Currency.Ils]?.total ?? 0, Currency.Ils),
        taxableLocalCurrency: formatFinancialAmount(data[Currency.Ils]?.taxable ?? 0, Currency.Ils),
        maxTaxableLocalCurrency: formatFinancialAmount(data[Currency.Ils]?.maxTaxable ?? 0, Currency.Ils),
        excessExpenditure: formatFinancialAmount(Math.max(excessExpenditure, 0), Currency.Ils),
    };
}
export function calculateTotalReportSummaryCategory(data) {
    const totalSumCategory = Object.values(data).reduce((acc, category) => {
        Object.entries(category).map(([currency, { total, taxable }]) => {
            acc[currency] ||= { total: 0, taxable: 0, maxTaxable: 0 };
            acc[currency].total += total;
            acc[currency].taxable += taxable;
        });
        return acc;
    }, {});
    return totalSumCategory;
}
export function getExpenseCoreData(tripExpense) {
    if (tripExpense.payed_by_employee) {
        if (!tripExpense.currency || !tripExpense.amount || !tripExpense.date) {
            throw new BusinessTripError(`Currency, amount or date not found for employee-paid trip expense ID ${tripExpense.id}`);
        }
        return {
            amount: Number(tripExpense.amount),
            currency: formatCurrency(tripExpense.currency),
            date: new Date(tripExpense.date),
        };
    }
    if (!tripExpense.currency || !tripExpense.amount || !tripExpense.value_date) {
        throw new BusinessTripError(`Currency, amount or date not found for business trip expense ID ${tripExpense.id}`);
    }
    return {
        amount: Number(tripExpense.amount),
        currency: formatCurrency(tripExpense.currency),
        date: new Date(tripExpense.value_date),
    };
}
async function getDefaultCurrenciesAmountsAndExchangeRate(injector, currency, amount, date) {
    const { defaultLocalCurrency, defaultCryptoConversionFiatCurrency } = await injector
        .get(AdminContextProvider)
        .getVerifiedAdminContext();
    const exchangeRatePromise = currency === defaultLocalCurrency
        ? Promise.resolve(1)
        : injector.get(ExchangeProvider).getExchangeRates(currency, defaultLocalCurrency, date);
    const usdRatePromise = currency === defaultCryptoConversionFiatCurrency
        ? Promise.resolve(1)
        : injector
            .get(ExchangeProvider)
            .getExchangeRates(currency, defaultCryptoConversionFiatCurrency, date);
    const [localRate, foreignRate] = await Promise.all([exchangeRatePromise, usdRatePromise]);
    const localAmount = localRate * amount;
    const foreignAmount = foreignRate * amount;
    return { localAmount, foreignAmount };
}
export async function getExpenseAmountsData(injector, businessTripExpense) {
    try {
        const { amount, currency, date } = getExpenseCoreData(businessTripExpense);
        const { localAmount, foreignAmount } = await getDefaultCurrenciesAmountsAndExchangeRate(injector, currency, amount, date);
        return { localAmount, foreignAmount };
    }
    catch (error) {
        // handle merged transaction of different currencies
        if (!(error instanceof Error) ||
            !error?.message?.includes('Currency, amount or date not found for business trip expense') ||
            !businessTripExpense.transaction_ids ||
            businessTripExpense.transaction_ids.length <= 1) {
            throw error;
        }
        const transactions = await injector
            .get(TransactionsProvider)
            .transactionByIdLoader.loadMany(businessTripExpense.transaction_ids);
        const allTransactions = transactions.filter(transaction => transaction && !(transaction instanceof Error));
        let localAmount = 0;
        let foreignAmount = 0;
        await Promise.all(allTransactions.map(async (transaction) => {
            if (!transaction || transaction instanceof Error) {
                return;
            }
            const amount = Number(transaction.amount);
            const currency = transaction.currency;
            const date = getTransactionDebitDate(transaction);
            if (!amount || !currency || !date) {
                const errorMessage = `Currency, amount or date not found for transaction ID ${transaction.id}`;
                console.error(errorMessage);
                throw new BusinessTripError(errorMessage);
            }
            const { localAmount: transactionLocalAmount, foreignAmount: transactionForeignAmount } = await getDefaultCurrenciesAmountsAndExchangeRate(injector, currency, amount, date);
            localAmount += transactionLocalAmount;
            foreignAmount += transactionForeignAmount;
        }));
        return { localAmount, foreignAmount };
    }
}
export async function flightExpenseDataCollector(injector, businessTripExpense, partialSummaryData) {
    // populate category
    partialSummaryData['FLIGHT'] ??= {};
    const category = partialSummaryData['FLIGHT'];
    const { localAmount, foreignAmount } = await getExpenseAmountsData(injector, businessTripExpense);
    // calculate taxable amount
    const fullyTaxableClasses = ['ECONOMY', 'PREMIUM_ECONOMY', 'BUSINESS'];
    if (!businessTripExpense.class) {
        console.error(`Flight class not found for flight expense ID ${businessTripExpense.id}`);
        throw new BusinessTripError('Flights expenses: some flights are missing class');
    }
    if (!fullyTaxableClasses.includes(businessTripExpense.class)) {
        console.error(`Taxability logic for flight class ${businessTripExpense.class} is not implemented yet (trip expense ID: ${businessTripExpense.id})`);
        throw new BusinessTripError(`Flights expenses: taxability logic for class ${businessTripExpense.class} is not implemented yet`);
    }
    // for all classes <= business, the amount is fully taxable
    const localTaxable = localAmount;
    const foreignTaxable = foreignAmount;
    // update amounts
    const { defaultLocalCurrency, defaultCryptoConversionFiatCurrency } = await injector
        .get(AdminContextProvider)
        .getVerifiedAdminContext();
    category[defaultLocalCurrency] ||= { total: 0, taxable: 0, maxTaxable: 0 };
    category[defaultLocalCurrency].total += localAmount;
    category[defaultLocalCurrency].taxable += localTaxable;
    category[defaultLocalCurrency].maxTaxable += localTaxable;
    category[defaultCryptoConversionFiatCurrency] ||= { total: 0, taxable: 0, maxTaxable: 0 };
    category[defaultCryptoConversionFiatCurrency].total += foreignAmount;
    category[defaultCryptoConversionFiatCurrency].taxable += foreignTaxable;
    category[defaultCryptoConversionFiatCurrency].maxTaxable += foreignTaxable;
    return void 0;
}
export async function employeeAccommodationDataByTrip() { }
export async function otherExpensesDataCollector(injector, otherExpenses, partialSummaryData) {
    const { defaultLocalCurrency, defaultCryptoConversionFiatCurrency } = await injector
        .get(AdminContextProvider)
        .getVerifiedAdminContext();
    // populate category
    partialSummaryData['OTHER'] ??= {};
    const category = partialSummaryData['OTHER'];
    category[defaultLocalCurrency] ||= { total: 0, taxable: 0, maxTaxable: 0 };
    category[defaultCryptoConversionFiatCurrency] ||= { total: 0, taxable: 0, maxTaxable: 0 };
    if (otherExpenses.length === 0) {
        return void 0;
    }
    await Promise.all(otherExpenses.map(async (businessTripExpense) => {
        const { localAmount, foreignAmount } = await getExpenseAmountsData(injector, businessTripExpense);
        category[defaultLocalCurrency].total += localAmount;
        category[defaultLocalCurrency].taxable += localAmount;
        category[defaultLocalCurrency].maxTaxable += localAmount;
        category[defaultCryptoConversionFiatCurrency].total += foreignAmount;
        category[defaultCryptoConversionFiatCurrency].taxable += foreignAmount;
        category[defaultCryptoConversionFiatCurrency].maxTaxable += foreignAmount;
    }));
    return void 0;
}
export async function travelAndSubsistenceExpensesDataCollector(injector, businessTripExpenses, partialSummaryData, taxVariables, { destinationCode, unAccommodatedDays, attendees }) {
    const { defaultLocalCurrency, defaultCryptoConversionFiatCurrency } = await injector
        .get(AdminContextProvider)
        .getVerifiedAdminContext();
    // populate category
    partialSummaryData['TRAVEL_AND_SUBSISTENCE'] ??= {};
    const category = partialSummaryData['TRAVEL_AND_SUBSISTENCE'];
    category[defaultLocalCurrency] ||= { total: 0, taxable: 0, maxTaxable: 0 };
    category[defaultCryptoConversionFiatCurrency] ||= { total: 0, taxable: 0, maxTaxable: 0 };
    const totalBusinessDays = Array.from(attendees.values()).reduce((acc, attendee) => acc + attendee.daysCount, 0);
    const maxTaxableUsd = getAttendeeTravelAndSubsistenceMaxTax(totalBusinessDays, unAccommodatedDays, destinationCode, taxVariables);
    category[defaultLocalCurrency].maxTaxable += 0; // TODO: calculate
    category[defaultCryptoConversionFiatCurrency].maxTaxable += maxTaxableUsd;
    // set actual expense amounts
    await Promise.all(businessTripExpenses.map(async (businessTripExpense) => {
        const { localAmount, foreignAmount } = await getExpenseAmountsData(injector, businessTripExpense);
        category[defaultLocalCurrency].total += localAmount;
        category[defaultCryptoConversionFiatCurrency].total += foreignAmount;
    }));
    // set taxable amounts
    const taxableAmount = Math.max(category[defaultCryptoConversionFiatCurrency].total, maxTaxableUsd);
    const taxablePortion = category[defaultCryptoConversionFiatCurrency].total === 0
        ? 0
        : taxableAmount / category[defaultCryptoConversionFiatCurrency].total;
    category[defaultLocalCurrency].taxable += category[defaultLocalCurrency].total * taxablePortion;
    category[defaultCryptoConversionFiatCurrency].taxable += taxableAmount;
    return void 0;
}
export async function carRentalExpensesDataCollector(injector, businessTripExpenses, partialSummaryData, taxVariables, destinationCode) {
    const { defaultLocalCurrency, defaultCryptoConversionFiatCurrency } = await injector
        .get(AdminContextProvider)
        .getVerifiedAdminContext();
    // populate category
    partialSummaryData['CAR_RENTAL'] ??= {};
    const category = partialSummaryData['CAR_RENTAL'];
    category[defaultLocalCurrency] ||= { total: 0, taxable: 0, maxTaxable: 0 };
    category[defaultCryptoConversionFiatCurrency] ||= { total: 0, taxable: 0, maxTaxable: 0 };
    let rentalDays = 0;
    // set actual expense amounts
    await Promise.all(businessTripExpenses.map(async (businessTripExpense) => {
        if (!businessTripExpense.is_fuel_expense) {
            rentalDays += businessTripExpense.days;
        }
        const { localAmount, foreignAmount } = await getExpenseAmountsData(injector, businessTripExpense);
        category[defaultLocalCurrency].total += localAmount;
        category[defaultCryptoConversionFiatCurrency].total += foreignAmount;
    }));
    // set max taxable amounts
    const { max_car_rental_per_day } = taxVariables;
    const maxDailyRentalAmount = Number(max_car_rental_per_day);
    if (Number.isNaN(maxDailyRentalAmount)) {
        throw new BusinessTripError('Tax variables are not set');
    }
    const increasedLimitDestination = isIncreasedLimitDestination(destinationCode) ? 1.25 : 1;
    const maxTaxableUsd = maxDailyRentalAmount * rentalDays * increasedLimitDestination * -1;
    category[defaultLocalCurrency].maxTaxable += 0; // TODO: calculate
    category[defaultCryptoConversionFiatCurrency].maxTaxable += maxTaxableUsd;
    // set taxable amounts
    const taxableAmount = Math.max(category[defaultCryptoConversionFiatCurrency].total, maxTaxableUsd);
    const taxablePortion = category[defaultCryptoConversionFiatCurrency].total === 0
        ? 0
        : taxableAmount / category[defaultCryptoConversionFiatCurrency].total;
    category[defaultLocalCurrency].taxable += category[defaultLocalCurrency].total * taxablePortion;
    category[defaultCryptoConversionFiatCurrency].taxable += taxableAmount;
    return void 0;
}
export function isIncreasedLimitDestination(destinationCode) {
    if (!destinationCode) {
        return false;
    }
    const increasedLimitDestinations = [
        CountryCode.Angola,
        CountryCode.Australia,
        CountryCode.Austria,
        CountryCode.Belgium,
        CountryCode.Cameroon,
        CountryCode.Canada,
        CountryCode.Denmark,
        CountryCode['United Arab Emirates (the)'],
        CountryCode.Finland,
        CountryCode.France,
        CountryCode.Germany,
        CountryCode.Greece,
        CountryCode['Hong Kong'],
        CountryCode.Iceland,
        CountryCode.Ireland,
        CountryCode.Italy,
        CountryCode.Japan,
        CountryCode['Korea (the Republic of)'],
        CountryCode["Korea (the Democratic People's Republic of)"],
        CountryCode.Luxembourg,
        CountryCode['Netherlands (the)'],
        CountryCode.Norway,
        CountryCode.Oman,
        CountryCode.Qatar,
        CountryCode.Spain,
        CountryCode.Sweden,
        CountryCode.Switzerland,
        CountryCode['Taiwan (Province of China)'],
        CountryCode['United Kingdom of Great Britain and Northern Ireland (the)'],
    ];
    return increasedLimitDestinations.includes(destinationCode.toLocaleUpperCase());
}
export function onlyUnique(value, index, array) {
    return array.indexOf(value) === index;
}
export function getAttendeeTravelAndSubsistenceMaxTax(totalBusinessDays, unAccommodatedDays, destinationCode, taxVariables) {
    const { max_tns_with_accommodation, max_tns_without_accommodation } = taxVariables;
    const maxExpenseWithAccommodation = Number(max_tns_with_accommodation);
    const maxExpenseWithoutAccommodation = Number(max_tns_without_accommodation);
    if (Number.isNaN(maxExpenseWithAccommodation) || Number.isNaN(maxExpenseWithoutAccommodation)) {
        throw new BusinessTripError('Tax variables are not set');
    }
    const accommodatedDays = totalBusinessDays - unAccommodatedDays;
    const increasedLimitFactor = isIncreasedLimitDestination(destinationCode) ? 1.25 : 1;
    const maxTaxableUsd = (maxExpenseWithAccommodation * accommodatedDays +
        maxExpenseWithoutAccommodation * unAccommodatedDays) *
        increasedLimitFactor *
        -1;
    return maxTaxableUsd;
}
export * from './business-trip-report-accommodation.helper.js';
//# sourceMappingURL=business-trip-report.helper.js.map