/**
 * Interés manual: un cargo por cuota que carga el administrativo desde Cuentas
 * por Cobrar, para las empresas que cobran interés pero no lo calculan con una
 * tasa (config_mora). Se cobra junto con la cuota y no toca su capital.
 *
 * No es una cuota más: las cuotas son el desglose de la factura y su suma tiene
 * que dar el total facturado. El cargo vive aparte y se salda al cobrarse.
 */

export type EstadoInteresManual = 'pendiente' | 'cobrado' | 'anulado';

export interface CargoInteresManual {
  factura_cuota_id: string | null;
  monto: unknown;
  monto_cobrado?: unknown;
  estado: EstadoInteresManual;
}

/** Lo que falta cobrar de un cargo: puede haberse cobrado en partes. */
export function pendienteDeCargo(cargo: { monto: unknown; monto_cobrado?: unknown; estado: EstadoInteresManual }): number {
  if (cargo.estado !== 'pendiente') return 0;
  const monto = Number(cargo.monto) || 0;
  const cobrado = Number(cargo.monto_cobrado) || 0;
  return Math.max(0, monto - cobrado);
}

/**
 * Reparte lo que este cobro paga de interés entre los cargos, del más viejo al
 * más nuevo. `montoACobrar` null significa "todo lo pendiente"; el cobrador
 * puede cobrar menos y el resto queda para el próximo cobro.
 */
export function repartirCobro(
  cargos: Array<{ id: string; monto: unknown; monto_cobrado?: unknown; estado: EstadoInteresManual }>,
  montoACobrar: number | null,
): Array<{ id: string; monto: number }> {
  const total = cargos.reduce((acc, c) => acc + pendienteDeCargo(c), 0);
  let restante = montoACobrar == null ? total : Math.max(0, Math.min(Number(montoACobrar) || 0, total));

  const reparto: Array<{ id: string; monto: number }> = [];
  for (const cargo of cargos) {
    if (restante <= 0) break;
    const pendiente = pendienteDeCargo(cargo);
    if (pendiente <= 0) continue;
    const aplicar = Math.min(pendiente, restante);
    reparto.push({ id: cargo.id, monto: aplicar });
    restante -= aplicar;
  }
  return reparto;
}

/** Motivo por el que no se puede cargar un interés sobre una cuota. */
export type RechazoCarga = 'MONTO_INVALIDO' | 'CUOTA_PAGADA' | 'MOTIVO_REQUERIDO';

export function validarCarga(params: {
  monto: number;
  motivo?: string | null;
  estadoCuota?: string | null;
  exigirMotivo?: boolean;
}): RechazoCarga | null {
  const monto = Number(params.monto);
  if (!Number.isFinite(monto) || monto <= 0) return 'MONTO_INVALIDO';
  const estado = String(params.estadoCuota ?? '').toLowerCase();
  if (estado === 'pagado' || estado === 'anulado' || estado === 'anulada') return 'CUOTA_PAGADA';
  if (params.exigirMotivo && !String(params.motivo ?? '').trim()) return 'MOTIVO_REQUERIDO';
  return null;
}

/** Total pendiente por cuota, para sumarlo al cobro. Ignora cobrados y anulados. */
export function pendientePorCuota(cargos: CargoInteresManual[]): Map<string, number> {
  const mapa = new Map<string, number>();
  for (const cargo of cargos) {
    if (!cargo.factura_cuota_id) continue; // el de la cuenta no cuelga de una cuota
    const pendiente = pendienteDeCargo(cargo);
    if (pendiente <= 0) continue;
    mapa.set(cargo.factura_cuota_id, (mapa.get(cargo.factura_cuota_id) ?? 0) + pendiente);
  }
  return mapa;
}

/** Suma de los cargos pendientes de las cuotas que entran en este cobro. */
export function totalPendiente(cargos: CargoInteresManual[], cuotaIds: string[]): number {
  const pendientes = pendientePorCuota(cargos);
  return cuotaIds.reduce((total, id) => total + (pendientes.get(id) ?? 0), 0);
}

/**
 * Cuotas que quedaron saldadas de capital pero todavía deben interés.
 *
 * Con imputación capital-primero una cuota puede cerrarse debiendo interés. Si
 * el cargo sigue colgado de ella queda vivo pero incobrable: las cuotas pagadas
 * no se pueden seleccionar en el cobro. Estas son las que hay que despegar.
 */
export function cuotasConInteresHuerfano(
  cuotaIdsSaldadas: string[],
  cargos: CargoInteresManual[],
): string[] {
  if (cuotaIdsSaldadas.length === 0) return [];
  const saldadas = new Set(cuotaIdsSaldadas);
  const huerfanas = new Set<string>();
  for (const cargo of cargos) {
    if (!cargo.factura_cuota_id) continue; // el de la cuenta ya está donde debe
    if (!saldadas.has(cargo.factura_cuota_id)) continue;
    if (pendienteDeCargo(cargo) <= 0) continue;
    huerfanas.add(cargo.factura_cuota_id);
  }
  return [...huerfanas];
}
