/* ============================================================
 * Copyright (c) 2026 RAVAPI Soluções. www.ravapi.com
 * Valor por extenso — pt-BR
 *
 * Trabalha sempre em centavos (inteiro). Nunca receber float aqui:
 * 0.1 + 0.2 em ponto flutuante não dá 0.3, e recibo com valor errado
 * por arredondamento é problema jurídico, não bug de exibição.
 * ============================================================ */

const UNIDADES = ['', 'um', 'dois', 'três', 'quatro', 'cinco', 'seis', 'sete',
  'oito', 'nove', 'dez', 'onze', 'doze', 'treze', 'quatorze', 'quinze',
  'dezesseis', 'dezessete', 'dezoito', 'dezenove'];

const DEZENAS = ['', '', 'vinte', 'trinta', 'quarenta', 'cinquenta',
  'sessenta', 'setenta', 'oitenta', 'noventa'];

const CENTENAS = ['', 'cento', 'duzentos', 'trezentos', 'quatrocentos',
  'quinhentos', 'seiscentos', 'setecentos', 'oitocentos', 'novecentos'];

const ESCALAS: Array<[number, string, string]> = [
  [1_000_000_000_000, 'trilhão', 'trilhões'],
  [1_000_000_000, 'bilhão', 'bilhões'],
  [1_000_000, 'milhão', 'milhões'],
  [1_000, 'mil', 'mil'],
];

/** Escreve um número de 1 a 999. */
export function ate999(n: number): string {
  if (n <= 0 || n > 999) return '';
  if (n === 100) return 'cem';

  const centena = Math.floor(n / 100);
  const resto = n % 100;
  const partes: string[] = [];

  if (centena > 0) partes.push(CENTENAS[centena]);

  if (resto > 0) {
    if (resto < 20) {
      partes.push(UNIDADES[resto]);
    } else {
      const dezena = Math.floor(resto / 10);
      const unidade = resto % 10;
      partes.push(unidade > 0 ? `${DEZENAS[dezena]} e ${UNIDADES[unidade]}` : DEZENAS[dezena]);
    }
  }
  return partes.join(' e ');
}

/**
 * Escreve um inteiro qualquer por extenso, sem unidade monetária.
 *
 * A conjunção antes do último grupo não é estética: em português o "e"
 * entra quando o último grupo é menor que cem ou é uma centena redonda.
 *   1.500     → mil E quinhentos        (centena redonda)
 *   1.050     → mil E cinquenta         (menor que cem)
 *   1.234     → mil, duzentos e trinta e quatro
 *   1.500.000 → um milhão E quinhentos mil
 */
export function inteiroPorExtenso(n: number): string {
  if (n === 0) return 'zero';

  const partes: Array<{ texto: string; grupo: number }> = [];
  let resto = n;

  for (const [valor, singular, plural] of ESCALAS) {
    const quantos = Math.floor(resto / valor);
    if (quantos > 0) {
      // "mil" não leva "um" na frente: 1000 é "mil", não "um mil"
      const texto = valor === 1_000 && quantos === 1
        ? 'mil'
        : `${ate999(quantos)} ${quantos === 1 ? singular : plural}`;
      partes.push({ texto, grupo: quantos });
      resto %= valor;
    }
  }
  if (resto > 0) partes.push({ texto: ate999(resto), grupo: resto });

  if (partes.length === 1) return partes[0].texto;

  const ultimo = partes[partes.length - 1];
  const conjuncao = ultimo.grupo < 100 || ultimo.grupo % 100 === 0 ? ' e ' : ', ';
  const inicio = partes.slice(0, -1).map((p) => p.texto).join(', ');
  return inicio + conjuncao + ultimo.texto;
}

/**
 * Valor monetário por extenso a partir de centavos.
 *
 * Regras que a redação brasileira exige e que quase todo gerador erra:
 *  - 100 → "cem", 101 → "cento e um"
 *  - 1000 → "mil reais", nunca "um mil reais"
 *  - milhão/bilhão redondos levam "de": "um milhão DE reais"
 *  - singular no valor exato de 1: "um real", "um centavo"
 */
export function valorPorExtenso(centavos: number): string {
  if (!Number.isInteger(centavos)) {
    throw new TypeError(`valorPorExtenso espera centavos inteiros, recebeu ${centavos}`);
  }
  if (centavos < 0) throw new RangeError('valor negativo não tem recibo');
  if (centavos === 0) return 'zero real';

  const reais = Math.floor(centavos / 100);
  const cents = centavos % 100;
  const blocos: string[] = [];

  if (reais > 0) {
    const texto = inteiroPorExtenso(reais);
    // "de reais" quando o valor termina em milhão/bilhão/trilhão exato
    const redondoGrande = reais >= 1_000_000 && reais % 1_000_000 === 0;
    if (redondoGrande) blocos.push(`${texto} de reais`);
    else blocos.push(`${texto} ${reais === 1 ? 'real' : 'reais'}`);
  }

  if (cents > 0) {
    blocos.push(`${ate999(cents)} ${cents === 1 ? 'centavo' : 'centavos'}`);
  }

  return blocos.join(' e ');
}

/** Formata centavos como R$ 1.234,56 */
export function formatarBRL(centavos: number): string {
  const sinal = centavos < 0 ? '-' : '';
  const abs = Math.abs(centavos);
  const inteiro = Math.floor(abs / 100).toLocaleString('pt-BR');
  const frac = String(abs % 100).padStart(2, '0');
  return `${sinal}R$ ${inteiro},${frac}`;
}

/** Converte "1.234,56" em 123456 centavos, sem passar por float. */
export function paraCentavos(texto: string): number {
  const limpo = texto.trim().replace(/[R$\s]/g, '');
  const m = limpo.match(/^(-)?(\d{1,3}(?:\.\d{3})*|\d+)(?:,(\d{1,2}))?$/);
  if (!m) throw new TypeError(`valor monetário inválido: "${texto}"`);
  const [, neg, parteInteira, parteFrac = '0'] = m;
  const inteiro = BigInt(parteInteira.replace(/\./g, ''));
  const frac = BigInt(parteFrac.padEnd(2, '0'));
  const total = inteiro * 100n + frac;
  return Number(neg ? -total : total);
}
