import { __decorate, __metadata } from "tslib";
import DataLoader from 'dataloader';
import { Injectable, Scope } from 'graphql-modules';
import { sql } from '@pgtyped/runtime';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
import { BusinessesOperationProvider } from './businesses-operation.provider.js';
import { BusinessesProvider } from './businesses.provider.js';
import { TaxCategoriesProvider } from './tax-categories.provider.js';
const getFinancialEntitiesByIds = sql `
    SELECT *
    FROM accounter_schema.financial_entities
    WHERE id IN $$ids;`;
const getAllFinancialEntities = sql `
    SELECT *
    FROM accounter_schema.financial_entities;`;
const updateFinancialEntity = sql `
  UPDATE accounter_schema.financial_entities
  SET
  name = COALESCE(
    $name,
    name
  ),
  sort_code = COALESCE(
    $sortCode,
    sort_code
  ),
  type = COALESCE(
    $type,
    type
  ),
  irs_code = COALESCE(
    $irsCode,
    irs_code
  ),
  is_active = COALESCE(
    $isActive,
    is_active
  )
  WHERE
    id = $financialEntityId
  RETURNING *;
`;
const insertFinancialEntities = sql `
  INSERT INTO accounter_schema.financial_entities (type, owner_id, name, sort_code, irs_code, is_active)
  VALUES $$financialEntities(type, ownerId, name, sortCode, irsCode, isActive)
  RETURNING *;`;
const deleteFinancialEntity = sql `
    DELETE FROM accounter_schema.financial_entities
    WHERE id = $financialEntityId
    RETURNING id;
  `;
const replaceFinancialEntities = sql `
  WITH ledger_debit1 AS (
    UPDATE accounter_schema.ledger_records
    SET debit_entity1 = $targetEntityId
    WHERE debit_entity1 = $entityIdToReplace
    RETURNING id
  ),
  ledger_debit2 AS (
    UPDATE accounter_schema.ledger_records
    SET debit_entity2 = $targetEntityId
    WHERE debit_entity2 = $entityIdToReplace
    RETURNING id
  ),
  ledger_credit1 AS (
    UPDATE accounter_schema.ledger_records
    SET credit_entity1 = $targetEntityId
    WHERE credit_entity1 = $entityIdToReplace
    RETURNING id
  ),
  ledger_credit2 AS (
    UPDATE accounter_schema.ledger_records
    SET credit_entity2 = $targetEntityId
    WHERE credit_entity2 = $entityIdToReplace
    RETURNING id
  ),
  misc_expenses_creditor AS (
    UPDATE accounter_schema.misc_expenses
    SET creditor_id = $targetEntityId
    WHERE creditor_id = $entityIdToReplace
    RETURNING id
  )
  UPDATE accounter_schema.misc_expenses
    SET debtor_id = $targetEntityId
    WHERE debtor_id = $entityIdToReplace
  RETURNING id;
`;
let FinancialEntitiesProvider = class FinancialEntitiesProvider {
    db;
    businessesProvider;
    businessesOperationProvider;
    taxCategoriesProvider;
    constructor(db, businessesProvider, businessesOperationProvider, taxCategoriesProvider) {
        this.db = db;
        this.businessesProvider = businessesProvider;
        this.businessesOperationProvider = businessesOperationProvider;
        this.taxCategoriesProvider = taxCategoriesProvider;
    }
    async batchFinancialEntitiesByIds(ids) {
        const financialEntities = await getFinancialEntitiesByIds.run({
            ids,
        }, this.db);
        return ids.map(id => financialEntities.find(fe => fe.id === id));
    }
    getFinancialEntityByIdLoader = new DataLoader((keys) => this.batchFinancialEntitiesByIds(keys));
    allFinancialEntitiesCache = null;
    getAllFinancialEntities() {
        if (this.allFinancialEntitiesCache) {
            return this.allFinancialEntitiesCache;
        }
        const result = getAllFinancialEntities.run(undefined, this.db).then(entities => {
            entities.map(entity => {
                this.getFinancialEntityByIdLoader.prime(entity.id, entity);
            });
            return entities;
        });
        this.allFinancialEntitiesCache = result;
        return result;
    }
    /** Returns a map of sort_code → entity ids, built once per request from the cached entity list. */
    entityBySortCodeMapCache = null;
    getEntityBySortCodeMap() {
        if (this.entityBySortCodeMapCache)
            return this.entityBySortCodeMapCache;
        this.entityBySortCodeMapCache = this.getAllFinancialEntities().then(entities => {
            const map = new Map();
            for (const entity of entities) {
                if (entity.sort_code == null)
                    continue;
                const bucket = map.get(entity.sort_code);
                if (bucket) {
                    bucket.push(entity.id);
                }
                else {
                    map.set(entity.sort_code, [entity.id]);
                }
            }
            return map;
        });
        return this.entityBySortCodeMapCache;
    }
    updateFinancialEntity(params) {
        if (params.financialEntityId) {
            this.invalidateFinancialEntityById(params.financialEntityId);
        }
        return updateFinancialEntity.run(params, this.db);
    }
    insertFinancialEntity(params, client) {
        this.allFinancialEntitiesCache = null;
        return insertFinancialEntities.run({ financialEntities: [params] }, client ?? this.db);
    }
    async batchInsertFinancialEntities(newFinancialEntities) {
        const financialEntities = await insertFinancialEntities.run({
            financialEntities: newFinancialEntities,
        }, this.db);
        return newFinancialEntities.map(fe => financialEntities.find(f => f.name === fe.name) ?? null);
    }
    insertFinancialEntitiesLoader = new DataLoader((financialEntities) => this.batchInsertFinancialEntities(financialEntities), {
        cache: false,
    });
    async deleteFinancialEntityById(financialEntityId) {
        const entity = await this.getFinancialEntityByIdLoader.load(financialEntityId);
        if (!entity) {
            throw new Error(`Financial entity with id ${financialEntityId} not found`);
        }
        if (entity.id === entity.owner_id) {
            throw new Error('Cannot delete owner entity');
        }
        this.invalidateFinancialEntityById(financialEntityId);
        // remove business
        const deleteBusiness = entity.type === 'business'
            ? this.businessesOperationProvider.deleteBusinessById(financialEntityId)
            : Promise.resolve();
        // remove tax category
        const deleteTaxCategory = entity.type === 'tax_category'
            ? this.taxCategoriesProvider.deleteTaxCategoryById(financialEntityId)
            : Promise.resolve();
        await Promise.all([deleteBusiness, deleteTaxCategory]);
        // TODO: should remove ledger, misc expenses?
        // delete entity
        deleteFinancialEntity.run({ financialEntityId }, this.db);
    }
    async replaceFinancialEntity(targetEntityId, entityIdToReplace, deleteEntity = false) {
        const [entityToReplace, entity] = await Promise.all([
            this.getFinancialEntityByIdLoader.load(entityIdToReplace),
            this.getFinancialEntityByIdLoader.load(targetEntityId),
        ]);
        if (!entityToReplace) {
            throw new Error(`Financial entity with id ${entityIdToReplace} not found`);
        }
        if (!entity) {
            throw new Error(`Financial entity with id ${targetEntityId} not found`);
        }
        if (entityToReplace.type !== entity.type) {
            throw new Error('Cannot replace entities of different types');
        }
        if (entityToReplace.id === entityToReplace.owner_id) {
            throw new Error('Cannot replace owner entity');
        }
        this.invalidateFinancialEntityById(entityIdToReplace);
        this.invalidateFinancialEntityById(targetEntityId);
        // convert ledger, misc expenses
        await replaceFinancialEntities.run({ targetEntityId, entityIdToReplace }, this.db);
        // convert business
        const businessReplacementPromise = entity.type === 'business'
            ? this.businessesProvider.replaceBusiness(targetEntityId, entityIdToReplace).then(() => {
                if (deleteEntity)
                    this.businessesOperationProvider.deleteBusinessById(entityIdToReplace);
            })
            : Promise.resolve();
        // convert tax category
        const taxCategoryReplacementPromise = entity.type === 'tax_category'
            ? this.taxCategoriesProvider.replaceTaxCategory(targetEntityId, entityIdToReplace, deleteEntity)
            : Promise.resolve();
        await Promise.all([businessReplacementPromise, taxCategoryReplacementPromise]);
        if (deleteEntity) {
            await this.deleteFinancialEntityById(entityIdToReplace);
        }
    }
    invalidateFinancialEntityById(financialEntityId) {
        this.businessesProvider.invalidateBusinessById(financialEntityId);
        this.taxCategoriesProvider.invalidateTaxCategoryById(financialEntityId);
        this.allFinancialEntitiesCache = null;
        this.getFinancialEntityByIdLoader.clear(financialEntityId);
    }
    clearCache() {
        this.taxCategoriesProvider.clearCache();
        this.businessesProvider.clearCache();
        this.getFinancialEntityByIdLoader.clearAll();
        this.allFinancialEntitiesCache = null;
    }
};
FinancialEntitiesProvider = __decorate([
    Injectable({
        scope: Scope.Operation,
        global: true,
    }),
    __metadata("design:paramtypes", [TenantAwareDBClient,
        BusinessesProvider,
        BusinessesOperationProvider,
        TaxCategoriesProvider])
], FinancialEntitiesProvider);
export { FinancialEntitiesProvider };
//# sourceMappingURL=financial-entities.provider.js.map