import { EMPTY_UUID } from '../../../shared/constants.js';
import { formatCurrency, formatFinancialAmount, getCurrencySymbol, } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { getChargeBusinesses } from '../../charges/helpers/common.helper.js';
import { FinancialEntitiesProvider } from '../../financial-entities/providers/financial-entities.provider.js';
import { TaxCategoriesProvider } from '../../financial-entities/providers/tax-categories.provider.js';
export class LedgerError extends Error {
    constructor(message) {
        super(message);
    }
}
export function isTransactionsOppositeSign([first, second]) {
    if (!first || !second) {
        throw new LedgerError('Transactions are missing');
    }
    const firstAmount = Number(first.amount);
    const secondAmount = Number(second.amount);
    if (Number.isNaN(firstAmount) || Number.isNaN(secondAmount)) {
        throw new LedgerError('Transaction amount is not a number');
    }
    return Number(first.amount) > 0 !== Number(second.amount) > 0;
}
export function validateTransactionBasicVariables(transaction) {
    const currency = formatCurrency(transaction.currency);
    const debitDate = transaction.debit_date_override ?? transaction.debit_date;
    if (!debitDate) {
        throw new LedgerError(`Transaction reference "${transaction.source_reference}" is missing debit date for currency ${currency}`);
    }
    const valueDate = transaction.debit_timestamp ?? debitDate;
    if (!transaction.business_id) {
        throw new LedgerError(`Transaction reference "${transaction.source_reference}" is missing business_id`);
    }
    const transactionBusinessId = transaction.business_id;
    return {
        currency,
        valueDate,
        transactionBusinessId,
    };
}
export function validateTransactionRequiredVariables(transaction, validationOptions = {}) {
    if (!transaction.debit_date && !transaction.debit_date_override) {
        throw new LedgerError(`Transaction reference "${transaction.source_reference}" is missing debit date for currency ${transaction.currency}`);
    }
    if (!transaction.business_id && !validationOptions.skipBusinessId) {
        throw new LedgerError(`Transaction reference "${transaction.source_reference}" is missing business_id`);
    }
    const debit_timestamp = transaction.debit_date_override ?? transaction.debit_timestamp ?? transaction.debit_date;
    return {
        ...transaction,
        debit_timestamp,
        currency: formatCurrency(transaction.currency),
    };
}
export function generatePartialLedgerEntry(transaction, ownerId, exchangeRate) {
    // set amounts
    let amount = Number(transaction.amount);
    let foreignAmount = undefined;
    if (exchangeRate) {
        foreignAmount = amount;
        // calculate amounts in ILS
        amount = exchangeRate * amount;
    }
    const absAmount = Math.abs(amount);
    const absForeignAmount = foreignAmount ? Math.abs(foreignAmount) : undefined;
    const isCreditorCounterparty = amount > 0;
    return {
        id: transaction.id,
        invoiceDate: transaction.event_date,
        valueDate: transaction.debit_timestamp,
        currency: transaction.currency,
        creditAmount1: absForeignAmount,
        localCurrencyCreditAmount1: absAmount,
        debitAmount1: absForeignAmount,
        localCurrencyDebitAmount1: absAmount,
        description: transaction.source_description ?? undefined,
        reference: transaction.origin_key,
        isCreditorCounterparty,
        ownerId,
        currencyRate: transaction.currency_rate ? Number(transaction.currency_rate) : undefined,
        chargeId: transaction.charge_id,
    };
}
function entrySingleAccountBalancer(ledgerBalance, entry, isCredit, ledgerEntityNumber, defaultLocalCurrency) {
    const entityId = isCredit
        ? ledgerEntityNumber === 1
            ? entry.creditAccountID1
            : entry.creditAccountID2
        : ledgerEntityNumber === 1
            ? entry.debitAccountID1
            : entry.debitAccountID2;
    if (!entityId) {
        return;
    }
    const current = ledgerBalance.get(entityId);
    const factor = isCredit ? 1 : -1;
    const currency = entry.currency;
    const amount = ((isCredit
        ? ledgerEntityNumber === 1
            ? entry.creditAmount1
            : entry.creditAmount2
        : ledgerEntityNumber === 1
            ? entry.debitAmount1
            : entry.debitAmount2) ?? 0) * factor;
    const localAmount = ((isCredit
        ? ledgerEntityNumber === 1
            ? entry.localCurrencyCreditAmount1
            : entry.localCurrencyCreditAmount2
        : ledgerEntityNumber === 1
            ? entry.localCurrencyDebitAmount1
            : entry.localCurrencyDebitAmount2) ?? 0) * factor;
    if (current) {
        current.amount += localAmount;
        current.foreignAmounts ||= {};
        if (amount && currency !== defaultLocalCurrency) {
            current.foreignAmounts[currency] = {
                foreign: (current.foreignAmounts[currency]?.foreign ?? 0) + amount,
                local: (current.foreignAmounts[currency]?.local ?? 0) + localAmount,
            };
        }
    }
    else {
        ledgerBalance.set(entityId, {
            amount: localAmount,
            entityId,
            foreignAmounts: amount && currency !== defaultLocalCurrency
                ? {
                    [currency]: {
                        foreign: amount,
                        local: localAmount,
                    },
                }
                : {},
        });
    }
}
export function updateLedgerBalanceByEntry(entry, ledgerBalance, defaultLocalCurrency) {
    entrySingleAccountBalancer(ledgerBalance, entry, true, 1, defaultLocalCurrency);
    entrySingleAccountBalancer(ledgerBalance, entry, true, 2, defaultLocalCurrency);
    entrySingleAccountBalancer(ledgerBalance, entry, false, 1, defaultLocalCurrency);
    entrySingleAccountBalancer(ledgerBalance, entry, false, 2, defaultLocalCurrency);
    return;
}
export async function getLedgerBalanceInfo(injector, ledgerBalance, errors = new Set(), allowedUnbalancedBusinesses = new Set(), financialEntities) {
    const { defaultLocalCurrency } = await injector
        .get(AdminContextProvider)
        .getVerifiedAdminContext();
    let ledgerBalanceSum = 0;
    let isBalanced = true;
    const unbalancedEntities = [];
    if (!financialEntities) {
        const financialEntityIDs = new Set(Array.from(ledgerBalance.values()).map(v => v.entityId));
        financialEntities = (await injector
            .get(FinancialEntitiesProvider)
            .getFinancialEntityByIdLoader.loadMany(Array.from(financialEntityIDs))
            .then(res => res.filter(fe => !!fe && 'id' in fe)));
    }
    for (const { amount, entityId } of ledgerBalance.values()) {
        if (Math.abs(amount) < 0.005) {
            continue;
        }
        const isBusiness = financialEntities?.some(financialEntity => financialEntity.id === entityId && financialEntity.type === 'business') ?? true;
        if (isBusiness && !allowedUnbalancedBusinesses.has(entityId)) {
            let businessIdentification = `ID="${entityId}"`;
            if (financialEntities) {
                const businessName = financialEntities.find(fe => fe.id === entityId)?.name;
                if (businessName) {
                    businessIdentification = `"${businessName}"`;
                }
            }
            errors.add(`Business ${businessIdentification} is unbalanced (By ${amount.toFixed(2)}${getCurrencySymbol(defaultLocalCurrency)})`);
            isBalanced = false;
        }
        unbalancedEntities.push({
            entityId,
            balance: formatFinancialAmount(amount, defaultLocalCurrency),
        });
        ledgerBalanceSum += amount;
    }
    if (Math.abs(ledgerBalanceSum) >= 0.005) {
        errors.add(`Total ledger balance is ${ledgerBalanceSum}`);
        isBalanced = false;
    }
    return {
        isBalanced,
        unbalancedEntities,
        balanceSum: ledgerBalanceSum,
        financialEntities,
    };
}
export function ledgerProtoToRecordsConverter(records) {
    return records.map(record => {
        const adjustedRecord = {
            charge_id: record.chargeId,
            created_at: new Date(),
            credit_entity1: record.creditAccountID1 ?? null,
            credit_entity2: record.creditAccountID2 ?? null,
            credit_foreign_amount1: record.creditAmount1?.toString() ?? null,
            credit_foreign_amount2: record.creditAmount2?.toString() ?? null,
            credit_local_amount1: record.localCurrencyCreditAmount1?.toString(),
            credit_local_amount2: record.localCurrencyCreditAmount2?.toString() ?? null,
            currency: record.currency,
            debit_entity1: record.debitAccountID1 ?? null,
            debit_entity2: record.debitAccountID2 ?? null,
            debit_foreign_amount1: record.debitAmount1?.toString() ?? null,
            debit_foreign_amount2: record.debitAmount2?.toString() ?? null,
            debit_local_amount1: record.localCurrencyDebitAmount1?.toString(),
            debit_local_amount2: record.localCurrencyDebitAmount2?.toString() ?? null,
            description: record.description ?? null,
            id: EMPTY_UUID,
            invoice_date: record.invoiceDate,
            owner_id: record.ownerId ?? null,
            reference1: record.reference ?? null,
            updated_at: new Date(),
            value_date: record.valueDate,
            locked: false,
        };
        return adjustedRecord;
    });
}
export async function getFinancialAccountTaxCategoryId(injector, transaction) {
    const taxCategory = await injector
        .get(TaxCategoriesProvider)
        .taxCategoryByFinancialAccountIdsAndCurrenciesLoader.load({
        financialAccountId: transaction.account_id,
        currency: transaction.currency,
    });
    if (!taxCategory) {
        throw new LedgerError(`Account id "${transaction.account_id}", with currency ${transaction.currency} is missing tax category`);
    }
    return taxCategory.id;
}
export async function multipleForeignCurrenciesBalanceEntries(injector, defaultLocalCurrency, documentEntries, transactionEntries, charge, foreignAmounts, balanceAgainstLocal) {
    const useDocuments = !charge.documents_optional_flag;
    if (!transactionEntries.length) {
        throw new LedgerError(`Failed to locate transaction entries for charge "${charge.id}"`);
    }
    if (!documentEntries.length && useDocuments) {
        throw new LedgerError(`Failed to locate document entries for charge "${charge.id}"`);
    }
    const ledgerEntries = [];
    const { mainBusinessId } = await getChargeBusinesses(charge.id, injector);
    if (mainBusinessId && Object.keys(foreignAmounts).length > 0) {
        const transactionEntry = transactionEntries.reduce((prev, curr) => {
            if (!prev) {
                return curr;
            }
            return prev.valueDate.getTime() > curr.valueDate.getTime() ? prev : curr;
        });
        const invoiceDate = new Date(Math.min(...(useDocuments
            ? documentEntries.map(entry => entry.invoiceDate.getTime())
            : transactionEntries.map(entry => entry.valueDate.getTime()))));
        // get the main foreign currency + diff in local currency
        let mainForeignCurrency = undefined;
        let localDiff = 0;
        if (Object.keys(foreignAmounts).length === 1 && balanceAgainstLocal) {
            const [currency, { local, foreign }] = Object.entries(foreignAmounts)[0];
            if (foreign === 0) {
                return [];
            }
            const isCreditorCounterparty = foreign < 0;
            const ledgerEntry = {
                id: transactionEntry.id + `|${currency}-balance`, // NOTE: this field is dummy
                creditAccountID1: mainBusinessId,
                debitAccountID1: mainBusinessId,
                localCurrencyCreditAmount1: Math.abs(local),
                localCurrencyDebitAmount1: Math.abs(local),
                ...(isCreditorCounterparty
                    ? {
                        creditAmount1: Math.abs(foreign),
                    }
                    : {
                        debitAmount1: Math.abs(foreign),
                    }),
                description: 'Foreign currency balance',
                isCreditorCounterparty,
                invoiceDate,
                valueDate: transactionEntry.valueDate,
                currency: currency,
                ownerId: transactionEntry.ownerId,
                chargeId: charge.id,
            };
            return [ledgerEntry];
        }
        for (const [currency, { local }] of Object.entries(foreignAmounts)) {
            if (mainForeignCurrency) {
                if (mainForeignCurrency.amount < local) {
                    mainForeignCurrency = { amount: local, currency: currency };
                }
            }
            else {
                mainForeignCurrency = { amount: local, currency: currency };
            }
            localDiff += local;
        }
        if (balanceAgainstLocal && !foreignAmounts[defaultLocalCurrency]) {
            foreignAmounts[defaultLocalCurrency] = {
                local: -localDiff,
                foreign: 0,
            };
        }
        for (const [currency, { local, foreign }] of Object.entries(foreignAmounts)) {
            let localToUse = local;
            if (!balanceAgainstLocal) {
                if (Math.abs(foreign) < 0.005) {
                    continue;
                }
                if (mainForeignCurrency?.currency === currency) {
                    localToUse -= localDiff;
                }
            }
            const isLocalCurrencyAndShouldBeBalanced = balanceAgainstLocal && mainForeignCurrency && defaultLocalCurrency === currency;
            const isCreditorCounterparty = isLocalCurrencyAndShouldBeBalanced
                ? mainForeignCurrency.amount > 0
                : foreign < 0;
            const ledgerEntry = {
                id: transactionEntry.id + `|${currency}-balance`, // NOTE: this field is dummy
                ...(isCreditorCounterparty
                    ? {
                        creditAccountID1: mainBusinessId,
                    }
                    : {
                        debitAccountID1: mainBusinessId,
                    }),
                localCurrencyCreditAmount1: Math.abs(localToUse),
                localCurrencyDebitAmount1: Math.abs(localToUse),
                ...(balanceAgainstLocal && !foreign
                    ? {}
                    : {
                        creditAmount1: Math.abs(foreign),
                        debitAmount1: Math.abs(foreign),
                    }),
                description: 'Foreign currency balance',
                isCreditorCounterparty,
                invoiceDate,
                valueDate: transactionEntry.valueDate,
                currency: currency,
                ownerId: transactionEntry.ownerId,
                chargeId: charge.id,
            };
            ledgerEntries.push(ledgerEntry);
        }
    }
    return ledgerEntries;
}
//# sourceMappingURL=utils.helper.js.map