import type { charge_type } from '../../charges/types.js';
import type { ChargesMatcherModule } from '../types.js';

type ChargeMatchQueueMode = ChargesMatcherModule.ChargeMatchQueueMode;

/**
 * DB charge types that are expected to hold both documents and transactions,
 * and therefore require a document ↔ transaction match. Every other type (e.g.
 * BANK_DEPOSIT, CREDITCARD_BANK, DIVIDEND, FOREIGN_SECURITIES, INTERNAL, VAT,
 * PAYROLL, CONVERSION, FINANCIAL) never needs an accounting document and is
 * excluded from the awaiting-match queue. A whitelist keeps new,
 * non-matchable types out of the queue by default.
 */
const MATCHABLE_CHARGE_TYPES = new Set<charge_type>(['COMMON', 'BUSINESS_TRIP']);

/**
 * Only COMMON and BUSINESS_TRIP charges (or charges with a not-yet-resolved
 * `null` type, which resolve to either COMMON or BUSINESS_TRIP) are expected to
 * hold both documents and transactions, so only they belong in the
 * awaiting-match queue.
 */
export function chargeRequiresMatch(charge: { type?: charge_type | null }): boolean {
  return !charge.type || MATCHABLE_CHARGE_TYPES.has(charge.type);
}

/**
 * Max number of charges evaluated when sorting the queue BY_SCORE. Scoring is
 * calculated on the fly and each pass loads candidates, transactions and
 * documents, so the evaluation window is deliberately capped to bound request
 * latency and DB load.
 */
export const BY_SCORE_EVALUATION_CAP = 100;

/**
 * The subset of enriched charge fields the queue filtering relies on.
 * Counts are int8 aggregates, surfaced by pgtyped as strings.
 */
export interface QueueChargeCounts {
  transactions_count: string | null;
  invoices_count: string | null;
  receipts_count: string | null;
}

function hasTransactions(charge: QueueChargeCounts): boolean {
  return Number(charge.transactions_count ?? 0) > 0;
}

function hasReceiptDocuments(charge: QueueChargeCounts): boolean {
  return Number(charge.receipts_count ?? 0) > 0;
}

function hasAccountingDocuments(charge: QueueChargeCounts): boolean {
  return Number(charge.invoices_count ?? 0) > 0 || hasReceiptDocuments(charge);
}

/**
 * A charge is transaction-based when it has transactions but no receipt
 * documents (mirrors the auto-match unmatched-charge semantics)
 */
export function isTransactionBaseCharge(charge: QueueChargeCounts): boolean {
  return hasTransactions(charge) && !hasReceiptDocuments(charge);
}

/**
 * A charge is document-based when it has accounting documents but no
 * transactions
 */
export function isDocumentBaseCharge(charge: QueueChargeCounts): boolean {
  return !hasTransactions(charge) && hasAccountingDocuments(charge);
}

/**
 * A charge belongs in the awaiting-match queue when it is unmatched:
 * transaction-based XOR document-based. Charges with both sides (matched) or
 * neither (empty) are excluded.
 */
export function isUnmatchedBaseCharge(charge: QueueChargeCounts): boolean {
  return isTransactionBaseCharge(charge) || isDocumentBaseCharge(charge);
}

/**
 * Apply the optional queue mode filter on top of the unmatched check
 */
export function matchesQueueMode(
  charge: QueueChargeCounts,
  mode?: ChargeMatchQueueMode | null,
): boolean {
  switch (mode) {
    case 'DOC_BASE':
      return isDocumentBaseCharge(charge);
    case 'TRANSACTION_BASE':
      return isTransactionBaseCharge(charge);
    default:
      return isUnmatchedBaseCharge(charge);
  }
}
