import { GraphQLError } from 'graphql';
import { ChargeTypeEnum } from '../../../shared/enums.js';
import { errorSimplifier } from '../../../shared/errors.js';
import { dateToTimelessDateString } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { ScopeProvider } from '../../auth/providers/scope.provider.js';
import { getMinDate } from '../../ledger/helpers/ledger-lock.js';
import { generateLedgerRecordsForFinancialCharge } from '../../ledger/resolvers/ledger-generation/financial-ledger-generation.resolver.js';
import { MiscExpensesProvider } from '../../misc-expenses/providers/misc-expenses.provider.js';
import { getChargeType } from '../helpers/charge-type.js';
import { getChargeLedgerMeta } from '../helpers/common.helper.js';
import { generateAndTagCharge } from '../helpers/financial-charge.helper.js';
import { ChargesProvider } from '../providers/charges.provider.js';
import { commonChargeFields } from './common.js';
// Validate an explicit write-target business against the caller's memberships,
// then load that business's admin context (not the request's primary), so
// charge generation uses the correct per-business tax configuration.
async function getWriteTargetAdminContext(injector, ownerId) {
    const target = await injector.get(ScopeProvider).resolveWriteTarget(ownerId);
    const targetAdminContext = await injector
        .get(AdminContextProvider)
        .adminContextByOwnerIdLoader.load(target);
    if (!targetAdminContext) {
        throw new GraphQLError('Admin context not found for the target business', {
            extensions: { code: 'NOT_FOUND' },
        });
    }
    return targetAdminContext;
}
export const financialChargesResolvers = {
    Query: {
        annualFinancialCharges: async (_, { ownerId, year }, { injector }) => {
            // This report is shaped around a single business's tax categories. When a
            // business is requested explicitly, validate it is within the read scope
            // and use its admin context; otherwise default to the request's primary.
            let targetAdminContext;
            if (ownerId) {
                const [scopedId] = await injector.get(ScopeProvider).getReadScope([ownerId]);
                targetAdminContext = await injector
                    .get(AdminContextProvider)
                    .adminContextByOwnerIdLoader.load(scopedId);
                if (!targetAdminContext) {
                    throw new GraphQLError('Admin context not found for the requested business', {
                        extensions: { code: 'NOT_FOUND' },
                    });
                }
            }
            else {
                targetAdminContext = await injector.get(AdminContextProvider).getVerifiedAdminContext();
            }
            const charges = await injector.get(ChargesProvider).getChargesByFilters({
                ownerIds: [targetAdminContext.ownerId],
                type: 'FINANCIAL',
                fromAnyDate: year,
                toAnyDate: year,
            });
            return {
                id: `${year}-financial-charges`,
                revaluationCharge: charges.find(c => c.business_array?.includes(targetAdminContext.general.taxCategories.exchangeRevaluationTaxCategoryId)),
                taxExpensesCharge: charges.find(c => c.business_array?.includes(targetAdminContext.authorities.taxExpensesTaxCategoryId)),
                depreciationCharge: targetAdminContext.depreciation.accumulatedDepreciationTaxCategoryId
                    ? charges.find(c => c.business_array?.includes(targetAdminContext.depreciation.accumulatedDepreciationTaxCategoryId))
                    : null,
                recoveryReserveCharge: targetAdminContext.salaries.recoveryReserveTaxCategoryId
                    ? charges.find(c => c.business_array?.includes(targetAdminContext.salaries.recoveryReserveTaxCategoryId))
                    : null,
                vacationReserveCharge: targetAdminContext.salaries.vacationReserveTaxCategoryId
                    ? charges.find(c => c.business_array?.includes(targetAdminContext.salaries.vacationReserveTaxCategoryId))
                    : null,
                bankDepositsRevaluationCharge: targetAdminContext.bankDeposits
                    .bankDepositInterestIncomeTaxCategoryId
                    ? charges.find(c => c.business_array?.includes(targetAdminContext.bankDeposits.bankDepositBusinessId))
                    : null,
            };
        },
    },
    Mutation: {
        generateRevaluationCharge: async (_, { date, ownerId }, context, info) => {
            const { injector } = context;
            const { ledgerLock, general: { taxCategories: { exchangeRevaluationTaxCategoryId }, }, } = await getWriteTargetAdminContext(injector, ownerId);
            if (ledgerLock && date <= ledgerLock) {
                throw new GraphQLError('Cannot generate revaluation charge for locked period');
            }
            try {
                const charge = await generateAndTagCharge(injector, ownerId, exchangeRevaluationTaxCategoryId, `Revaluation charge for ${date}`);
                await generateLedgerRecordsForFinancialCharge(charge, { insertLedgerRecordsIfNotExists: true }, context, info);
                return charge;
            }
            catch (e) {
                throw errorSimplifier('Error generating revaluation charge', e);
            }
        },
        generateBankDepositsRevaluationCharge: async (_, { date, ownerId }, context, info) => {
            const { injector } = context;
            const { ledgerLock, bankDeposits: { bankDepositInterestIncomeTaxCategoryId }, } = await getWriteTargetAdminContext(injector, ownerId);
            if (ledgerLock && date <= ledgerLock) {
                throw new GraphQLError('Cannot generate revaluation charge for locked period');
            }
            try {
                if (!bankDepositInterestIncomeTaxCategoryId) {
                    throw new GraphQLError('Bank deposit interest income tax category missing');
                }
                const charge = await generateAndTagCharge(injector, ownerId, bankDepositInterestIncomeTaxCategoryId, `Bank deposits revaluation charge for ${date}`);
                await generateLedgerRecordsForFinancialCharge(charge, { insertLedgerRecordsIfNotExists: true }, context, info);
                return charge;
            }
            catch (e) {
                throw errorSimplifier('Error generating bank deposits revaluation charge', e);
            }
        },
        generateTaxExpensesCharge: async (_, { year, ownerId }, context, info) => {
            const { injector } = context;
            const { ledgerLock, authorities: { taxExpensesTaxCategoryId }, } = await getWriteTargetAdminContext(injector, ownerId);
            if (ledgerLock && `${year}-12-31` <= ledgerLock) {
                throw new GraphQLError('Cannot generate revaluation charge for locked period');
            }
            try {
                const charge = await generateAndTagCharge(injector, ownerId, taxExpensesTaxCategoryId, `Tax expenses charge for ${year.substring(0, 4)}`);
                await generateLedgerRecordsForFinancialCharge(charge, { insertLedgerRecordsIfNotExists: true }, context, info);
                return charge;
            }
            catch (e) {
                throw errorSimplifier('Error generating tax expenses charge', e);
            }
        },
        generateDepreciationCharge: async (_, { year, ownerId }, context, info) => {
            const { injector } = context;
            const { ledgerLock, depreciation: { accumulatedDepreciationTaxCategoryId }, } = await getWriteTargetAdminContext(injector, ownerId);
            if (ledgerLock && `${year}-12-31` <= ledgerLock) {
                throw new GraphQLError('Cannot generate revaluation charge for locked period');
            }
            if (!accumulatedDepreciationTaxCategoryId) {
                throw new GraphQLError('Accumulated depreciation tax category missing');
            }
            try {
                const charge = await generateAndTagCharge(injector, ownerId, accumulatedDepreciationTaxCategoryId, `Depreciation charge for ${year.substring(0, 4)}`);
                await generateLedgerRecordsForFinancialCharge(charge, { insertLedgerRecordsIfNotExists: true }, context, info);
                return charge;
            }
            catch (e) {
                throw errorSimplifier('Error generating depreciation charge', e);
            }
        },
        generateRecoveryReserveCharge: async (_, { year, ownerId }, context, info) => {
            const { injector } = context;
            const { ledgerLock, salaries: { recoveryReserveTaxCategoryId }, } = await getWriteTargetAdminContext(injector, ownerId);
            if (ledgerLock && `${year}-12-31` <= ledgerLock) {
                throw new GraphQLError('Cannot generate revaluation charge for locked period');
            }
            if (!recoveryReserveTaxCategoryId) {
                throw new GraphQLError('Recovery reserve tax category missing');
            }
            try {
                const charge = await generateAndTagCharge(injector, ownerId, recoveryReserveTaxCategoryId, `Recovery reserve charge for ${year.substring(0, 4)}`);
                await generateLedgerRecordsForFinancialCharge(charge, { insertLedgerRecordsIfNotExists: true }, context, info);
                return charge;
            }
            catch (e) {
                throw errorSimplifier('Error generating recovery reserve charge', e);
            }
        },
        generateVacationReserveCharge: async (_, { year, ownerId }, context, info) => {
            const { injector } = context;
            const { ledgerLock, salaries: { vacationReserveTaxCategoryId }, } = await getWriteTargetAdminContext(injector, ownerId);
            if (ledgerLock && `${year}-12-31` <= ledgerLock) {
                throw new GraphQLError('Cannot generate revaluation charge for locked period');
            }
            if (!vacationReserveTaxCategoryId) {
                throw new GraphQLError('Vacation reserve tax category missing');
            }
            try {
                const charge = await generateAndTagCharge(injector, ownerId, vacationReserveTaxCategoryId, `Vacation reserves charge for ${year.substring(0, 4)}`);
                await generateLedgerRecordsForFinancialCharge(charge, { insertLedgerRecordsIfNotExists: true }, context, info);
                return charge;
            }
            catch (e) {
                throw errorSimplifier('Error generating vacation reserves charge', e);
            }
        },
        generateBalanceCharge: async (_, { description, balanceRecords }, context, info) => {
            const { injector } = context;
            if (!balanceRecords.length) {
                throw new GraphQLError('Balance charge must include balance records');
            }
            const { defaultTaxCategoryId, ownerId, ledgerLock } = await injector
                .get(AdminContextProvider)
                .getVerifiedAdminContext();
            if (!defaultTaxCategoryId) {
                throw new GraphQLError('Default tax category missing');
            }
            try {
                if (ledgerLock) {
                    const minDate = getMinDate(balanceRecords.map(record => [new Date(record.invoiceDate), record.valueDate]).flat());
                    if (minDate && dateToTimelessDateString(minDate) <= ledgerLock) {
                        throw new GraphQLError('Cannot generate balance charge for locked period');
                    }
                }
                const charge = await generateAndTagCharge(injector, ownerId, defaultTaxCategoryId, description);
                await Promise.all(balanceRecords.map(async (record) => {
                    if (!record.debtorId && !record.creditorId) {
                        throw new GraphQLError('Balance record must include at least one counterparty (debtor or creditor)');
                    }
                    await injector
                        .get(MiscExpensesProvider)
                        .insertExpense({ ...record, chargeId: charge.id })
                        .catch(() => {
                        throw new GraphQLError('Error adding balance records');
                    });
                }));
                await generateLedgerRecordsForFinancialCharge(charge, { insertLedgerRecordsIfNotExists: true }, context, info);
                return charge;
            }
            catch (e) {
                throw errorSimplifier('Error generating balance charge', e);
            }
        },
    },
    FinancialCharge: {
        __isTypeOf: async (DbCharge, { injector }) => (await getChargeType(DbCharge, injector).catch(error => {
            throw errorSimplifier('Failed to determine charge type', error);
        })) === ChargeTypeEnum.Financial,
        ...commonChargeFields,
        type: () => 'FINANCIAL',
        vat: () => null,
        totalAmount: () => null,
        property: () => false,
        isInvoicePaymentDifferentCurrency: () => false,
        minEventDate: async (DbCharge, _, { injector }) => {
            try {
                const { ledgerMinInvoiceDate } = await getChargeLedgerMeta(DbCharge.id, injector);
                return ledgerMinInvoiceDate;
            }
            catch (error) {
                throw errorSimplifier('Failed to fetch min event date', error);
            }
        },
        minDebitDate: async (DbCharge, _, { injector }) => {
            try {
                const { ledgerMinValueDate } = await getChargeLedgerMeta(DbCharge.id, injector);
                return ledgerMinValueDate;
            }
            catch (error) {
                throw errorSimplifier('Failed to fetch min debit date', error);
            }
        },
        maxEventDate: async (DbCharge, _, { injector }) => {
            try {
                const { ledgerMaxInvoiceDate } = await getChargeLedgerMeta(DbCharge.id, injector);
                return ledgerMaxInvoiceDate;
            }
            catch (error) {
                throw errorSimplifier('Failed to fetch max event date', error);
            }
        },
        maxDebitDate: async (DbCharge, _, { injector }) => {
            try {
                const { ledgerMaxValueDate } = await getChargeLedgerMeta(DbCharge.id, injector);
                return ledgerMaxValueDate;
            }
            catch (error) {
                throw errorSimplifier('Failed to fetch max debit date', error);
            }
        },
        // minDocumentsDate:
        // validationData:
        // metadata:
        yearsOfRelevance: () => null,
    },
};
//# sourceMappingURL=financial-charges.resolver.js.map