import type { Colaborador } from "./colaboradores.tsx";
import type { ParametrosFiscaisRH } from "./parametros-fiscais-rh.tsx";
import type { CalendarioTrabalho } from "./calendarios-trabalho.tsx";
import type { Presenca } from "./presencas.tsx";
import type { Feriado } from "./feriados-rh.tsx";
import type { AutorizacaoHoraExtra } from "./autorizacao-hora-extra.tsx";
import { listarPresencas } from "./presencas.tsx";
import { listarFeriados } from "./feriados-rh.tsx";
import { obterCalendarioTrabalhoAplicavel } from "./calendarios-trabalho.tsx";
import { listarAutorizacoesHoraExtra } from "./autorizacao-hora-extra.tsx";
import { obterConfiguracaoPresencas, arredondarMinutos, aplicarTolerancia, type ConfiguracaoPresencas } from "./configuracao-presencas.tsx";

// ========== MOTOR DE CALCULO DE HORAS (Presencas, Grupos B+) ==========
//
// Le Presencas + Calendario de Trabalho + Feriados + Autorizacoes de Hora
// Extra de um colaborador num periodo e devolve os valores monetarios das
// rubricas de sistema ligadas a assiduidade, mais os creditos propostos
// para o Banco de Horas e avisos de limite legal.
//
// Base legal (confirmada por leitura directa da Lei n.º 13/2023):
//   - Art. 98 (trabalho excepcional): trabalho em dia de descanso semanal,
//     complementar, feriado ou tolerancia de ponto — pago com +100%
//     (Art. 125 n.2), flat. Desdobrado em 5 categorias (sabado/domingo/
//     feriado nacional/feriado local/tolerancia de ponto) apenas para
//     reporting e auditoria separados — a majoracao legal e identica para
//     todas. Confere ainda direito a descanso compensatorio (Art. 98 n.4):
//     1 dia completo, ou meio-dia se a prestacao nao ultrapassar 5h — este
//     direito e INCONDICIONAL, independente de o pagamento ser em dinheiro
//     ou creditado no Banco de Horas.
//   - Art. 99 (trabalho extraordinario): trabalho fora do horario normal,
//     num dia normal de trabalho — pago com +50% ate as 24h e +100% para
//     alem das 20h (Art. 125 n.1). Limites: 96h/trimestre, 8h/semana,
//     200h/ano (Art. 99 n.3) — verificados aqui como AVISOS.
//   - Art. 121: "a remuneracao por tempo e feita em funcao do periodo de
//     tempo efectivamente despendido no trabalho" — base legal directa
//     para descontar atrasos e saidas antecipadas proporcionalmente.
//
// Autorizacao previa (autorizacao-hora-extra.tsx): quando existe uma
// autorizacao APROVADA para o dia com tipoCompensacao='tempo', as horas
// extra desse dia (uteis OU excepcionais) sao creditadas no Banco de Horas
// em vez de geradas como linha monetaria — ver banco-horas.tsx. Sem
// autorizacao, ou com tipoCompensacao='pagamento', mantem-se o
// comportamento automatico do Grupo B (pago directamente na folha).
//
// Ambito deliberadamente reduzido nesta iteracao:
//   - So conta horas extra no FIM do turno (chegar mais cedo nao e hora
//     extra) — mas saida ANTES do fim do turno conta como saida antecipada.
//   - Trabalho nocturno (Art. 100/125 n.3, +25%) NAO tem rubrica propria.
//   - Turnos rotativos continuam fora de ambito.

const MINUTOS_POR_DIA = 24 * 60;
const LIMIAR_HORA_EXTRA_MAJORACAO_TOTAL_MINUTOS = 20 * 60; // Art. 125 n.1 — fronteira das 20h
const LIMIAR_TRABALHO_EXCEPCIONAL_DIA_COMPLETO_MINUTOS = 5 * 60; // Art. 98 n.4

function arredondar(valor: number): number {
  return Math.round(valor * 100) / 100;
}

function paraMinutos(horaHHMM: string): number {
  const [horas, minutos] = horaHHMM.split(":").map(Number);
  return horas * 60 + minutos;
}

function formatarDataISO(ano: number, mes: number, dia: number): string {
  return `${ano}-${String(mes).padStart(2, "0")}-${String(dia).padStart(2, "0")}`;
}

function ultimoDiaDoMes(ano: number, mes: number): number {
  return new Date(Date.UTC(ano, mes, 0)).getUTCDate();
}

// ========== FUNCOES PURAS (testaveis isoladamente) ==========

export function calcularMinutosNormaisDiarios(calendario: CalendarioTrabalho): number {
  const entrada = paraMinutos(calendario.horaEntrada);
  let saida = paraMinutos(calendario.horaSaida);
  if (saida <= entrada) saida += MINUTOS_POR_DIA;
  let duracao = saida - entrada;

  if (calendario.inicioIntervalo && calendario.fimIntervalo) {
    const inicioIntervalo = paraMinutos(calendario.inicioIntervalo);
    let fimIntervalo = paraMinutos(calendario.fimIntervalo);
    if (fimIntervalo <= inicioIntervalo) fimIntervalo += MINUTOS_POR_DIA;
    duracao -= fimIntervalo - inicioIntervalo;
  }

  return Math.max(0, duracao);
}

// Divide um intervalo de horas extra (inicioExtraMin = fim do turno normal,
// fimExtraMin = saida real) pela fronteira das 20h do Art. 125 n.1.
export function dividirMinutosHoraExtraUtil(
  inicioExtraMin: number,
  fimExtraMin: number,
): { minutosAte20h: number; minutosApos20h: number } {
  if (fimExtraMin <= inicioExtraMin) {
    return { minutosAte20h: 0, minutosApos20h: 0 };
  }

  const minutosAte20h = Math.max(0, Math.min(fimExtraMin, LIMIAR_HORA_EXTRA_MAJORACAO_TOTAL_MINUTOS) - inicioExtraMin);
  const minutosApos20h = Math.max(0, fimExtraMin - Math.max(inicioExtraMin, LIMIAR_HORA_EXTRA_MAJORACAO_TOTAL_MINUTOS));

  return { minutosAte20h, minutosApos20h };
}

export function calcularValorHoraNormalPorMinuto(
  salarioBase: number,
  diasUteisNoMes: number,
  minutosNormaisPorDia: number,
): number {
  if (diasUteisNoMes <= 0 || minutosNormaisPorDia <= 0) return 0;
  return salarioBase / diasUteisNoMes / minutosNormaisPorDia;
}

// Art. 125 n.1: +50% ate as 24h, +100% para alem das 20h.
export function calcularValorHorasExtraUteis(
  minutosAte20h: number,
  minutosApos20h: number,
  valorPorMinuto: number,
  parametros: ParametrosFiscaisRH,
): number {
  const valorAte20h = minutosAte20h * valorPorMinuto * (1 + parametros.horaExtraMajoracaoPercentualAte24h / 100);
  const valorApos20h = minutosApos20h * valorPorMinuto * (1 + parametros.horaExtraMajoracaoPercentualApos20h / 100);
  return arredondar(valorAte20h + valorApos20h);
}

// Art. 98 + 125 n.2: trabalho em dia de descanso/feriado nao isento, +100% flat.
export function calcularValorHorasExtraExcepcional(
  minutosTotal: number,
  valorPorMinuto: number,
  parametros: ParametrosFiscaisRH,
): number {
  return arredondar(minutosTotal * valorPorMinuto * (1 + parametros.trabalhoExcepcionalMajoracaoPercentual / 100));
}

export function calcularDescontoFalta(
  diasFaltaInjustificada: number,
  salarioBase: number,
  diasUteisNoMes: number,
): number {
  if (diasUteisNoMes <= 0) return 0;
  return arredondar(diasFaltaInjustificada * (salarioBase / diasUteisNoMes));
}

// Art. 121: remuneracao por tempo efectivamente despendido — desconta o
// tempo de atraso ou de saida antecipada proporcionalmente, sem majoracao.
export function calcularDescontoTempo(minutos: number, valorPorMinuto: number): number {
  return arredondar(minutos * valorPorMinuto);
}

// Minutos "bancaveis" de uma hora extra — a mesma majoracao usada para o
// pagamento, mas expressa em tempo em vez de dinheiro (ex.: 4h de domingo,
// +100%, banca como 8h = 1 dia normal de 8h).
export function calcularMinutosBancaveis(minutosTrabalhados: number, majoracaoPercentual: number): number {
  return Math.round(minutosTrabalhados * (1 + majoracaoPercentual / 100));
}

// Art. 98 n.4 — direito a descanso compensatorio por trabalho excepcional.
export function calcularMinutosDescansoCompensatorio(minutosTrabalhados: number, minutosNormaisPorDia: number): number {
  if (minutosTrabalhados <= 0) return 0;
  return minutosTrabalhados > LIMIAR_TRABALHO_EXCEPCIONAL_DIA_COMPLETO_MINUTOS ? minutosNormaisPorDia : Math.round(minutosNormaisPorDia / 2);
}

// ========== CLASSIFICACAO DE DIAS ==========

export type TipoDiaExcepcional = "sabado" | "domingo" | "feriado_nacional" | "feriado_local" | "tolerancia_ponto";

const TIPOS_DIA_EXCEPCIONAL: TipoDiaExcepcional[] = ["sabado", "domingo", "feriado_nacional", "feriado_local", "tolerancia_ponto"];

function classificarTipoDiaExcepcional(diaSemana: number, feriado: Feriado | undefined): TipoDiaExcepcional {
  if (feriado) {
    if (feriado.tipo === "nacional") return "feriado_nacional";
    if (feriado.tipo === "local") return "feriado_local";
    return "tolerancia_ponto";
  }
  return diaSemana === 0 ? "domingo" : "sabado";
}

// ========== AGREGACAO A PARTIR DE PRESENCAS (I/O) ==========

interface BucketHoraExtra {
  pago: number;
  banco: number;
}

function bucketVazio(): BucketHoraExtra {
  return { pago: 0, banco: 0 };
}

interface AgregadoHoras {
  horasExtraUteisAte20h: BucketHoraExtra;
  horasExtraUteisApos20h: BucketHoraExtra;
  horasExtraExcepcional: Record<TipoDiaExcepcional, BucketHoraExtra>;
  minutosSaidaAntecipada: number;
  diasFaltaInjustificada: number;
  minutosAtraso: number;
  diasUteisNoIntervalo: number;
  descansosCompensatorios: Array<{ data: string; minutosTrabalhados: number }>;
}

function agregadoVazio(): AgregadoHoras {
  const excepcional = {} as Record<TipoDiaExcepcional, BucketHoraExtra>;
  for (const tipo of TIPOS_DIA_EXCEPCIONAL) excepcional[tipo] = bucketVazio();

  return {
    horasExtraUteisAte20h: bucketVazio(),
    horasExtraUteisApos20h: bucketVazio(),
    horasExtraExcepcional: excepcional,
    minutosSaidaAntecipada: 0,
    diasFaltaInjustificada: 0,
    minutosAtraso: 0,
    diasUteisNoIntervalo: 0,
    descansosCompensatorios: [],
  };
}

// Percorre dia-a-dia um intervalo [inicio, fim], classificando cada dia
// (normal vs. excepcional — Art. 98 n.1) a partir do Calendario de
// Trabalho e dos Feriados aplicaveis, cruzando com o registo de Presenca e
// com a Autorizacao de Hora Extra (aprovada) desse dia, se existirem.
function agregarHorasIntervalo(
  calendario: CalendarioTrabalho,
  regimeContinuidade: boolean | undefined,
  configuracao: ConfiguracaoPresencas,
  presencasPorData: Map<string, Presenca>,
  feriadosPorData: Map<string, Feriado>,
  autorizacoesPorData: Map<string, AutorizacaoHoraExtra>,
  dataInicioIso: string,
  dataFimIso: string,
): AgregadoHoras {
  const agregado = agregadoVazio();
  const entradaNormalMin = paraMinutos(calendario.horaEntrada);
  let saidaNormalMin = paraMinutos(calendario.horaSaida);
  if (saidaNormalMin <= entradaNormalMin) saidaNormalMin += MINUTOS_POR_DIA;

  let cursor = new Date(`${dataInicioIso}T00:00:00Z`);
  const fim = new Date(`${dataFimIso}T00:00:00Z`);

  while (cursor.getTime() <= fim.getTime()) {
    const dataIso = cursor.toISOString().slice(0, 10);
    const diaSemana = cursor.getUTCDay();
    const eDiaTrabalhoNormal = calendario.diasTrabalho.includes(diaSemana);

    if (eDiaTrabalhoNormal) {
      agregado.diasUteisNoIntervalo += 1;
    }

    const feriado = feriadosPorData.get(dataIso);
    const eExcepcional = !eDiaTrabalhoNormal || (feriado !== undefined && !regimeContinuidade);
    const tipoExcepcional = eExcepcional ? classificarTipoDiaExcepcional(diaSemana, feriado) : null;

    const presenca = presencasPorData.get(dataIso);
    const autorizacao = autorizacoesPorData.get(dataIso);
    const bancoEscolhido = autorizacao?.tipoCompensacao === "tempo";

    if (presenca?.tipo === "presente" && presenca.horaEntrada && presenca.horaSaida) {
      const entradaMin = paraMinutos(presenca.horaEntrada);
      let saidaMin = paraMinutos(presenca.horaSaida);
      if (saidaMin <= entradaMin) saidaMin += MINUTOS_POR_DIA;

      if (eExcepcional && tipoExcepcional) {
        const minutosTrabalhados = Math.max(0, saidaMin - entradaMin);
        const minutosArredondados = arredondarMinutos(minutosTrabalhados, configuracao);
        const bucket = agregado.horasExtraExcepcional[tipoExcepcional];
        if (bancoEscolhido) bucket.banco += minutosArredondados;
        else bucket.pago += minutosArredondados;

        if (minutosTrabalhados > 0) {
          agregado.descansosCompensatorios.push({ data: dataIso, minutosTrabalhados });
        }
      } else if (!eExcepcional) {
        if (saidaMin > saidaNormalMin) {
          const minutosExtra = arredondarMinutos(saidaMin - saidaNormalMin, configuracao);
          const { minutosAte20h, minutosApos20h } = dividirMinutosHoraExtraUtil(saidaNormalMin, saidaNormalMin + minutosExtra);
          if (bancoEscolhido) {
            agregado.horasExtraUteisAte20h.banco += minutosAte20h;
            agregado.horasExtraUteisApos20h.banco += minutosApos20h;
          } else {
            agregado.horasExtraUteisAte20h.pago += minutosAte20h;
            agregado.horasExtraUteisApos20h.pago += minutosApos20h;
          }
        } else if (saidaMin < saidaNormalMin) {
          const minutosSaidaAntecipada = aplicarTolerancia(saidaNormalMin - saidaMin, configuracao.toleranciaSaidaAntecipadaMinutos);
          agregado.minutosSaidaAntecipada += arredondarMinutos(minutosSaidaAntecipada, configuracao);
        }
      }
    } else if (presenca?.tipo === "falta" && presenca.justificada !== true && !eExcepcional) {
      agregado.diasFaltaInjustificada += 1;
    } else if (presenca?.tipo === "atraso" && presenca.minutosAtraso) {
      const minutosAtraso = aplicarTolerancia(presenca.minutosAtraso, configuracao.toleranciaAtrasoMinutos);
      agregado.minutosAtraso += arredondarMinutos(minutosAtraso, configuracao);
    }

    cursor = new Date(cursor.getTime() + MINUTOS_POR_DIA * 60 * 1000);
  }

  return agregado;
}

function segundaFeiraDaSemana(dataIso: string): Date {
  const data = new Date(`${dataIso}T00:00:00Z`);
  const diaIso = (data.getUTCDay() + 6) % 7; // 0=segunda..6=domingo
  return new Date(data.getTime() - diaIso * MINUTOS_POR_DIA * 60 * 1000);
}

// Art. 99 n.3: 96h/trimestre, 8h/semana, 200h/ano de trabalho
// extraordinario (HEXTRADU — o trabalho excepcional do Art. 98 nao entra
// nestes limites, que sao especificos do Art. 99). So gera avisos.
function verificarLimitesHoraExtra(
  calendario: CalendarioTrabalho,
  regimeContinuidade: boolean | undefined,
  configuracao: ConfiguracaoPresencas,
  presencasPorData: Map<string, Presenca>,
  feriadosPorData: Map<string, Feriado>,
  autorizacoesPorData: Map<string, AutorizacaoHoraExtra>,
  ano: number,
  mes: number,
  parametros: ParametrosFiscaisRH,
): string[] {
  const avisos: string[] = [];
  const minutosUteis = (agregado: AgregadoHoras) =>
    agregado.horasExtraUteisAte20h.pago + agregado.horasExtraUteisAte20h.banco +
    agregado.horasExtraUteisApos20h.pago + agregado.horasExtraUteisApos20h.banco;

  const ultimoDia = ultimoDiaDoMes(ano, mes);
  const semanasVerificadas = new Set<string>();

  for (let dia = 1; dia <= ultimoDia; dia += 1) {
    const dataIso = formatarDataISO(ano, mes, dia);
    const inicioSemana = segundaFeiraDaSemana(dataIso);
    const inicioSemanaIso = inicioSemana.toISOString().slice(0, 10);
    if (semanasVerificadas.has(inicioSemanaIso)) continue;
    semanasVerificadas.add(inicioSemanaIso);

    const fimSemanaIso = new Date(inicioSemana.getTime() + 6 * MINUTOS_POR_DIA * 60 * 1000).toISOString().slice(0, 10);
    const agregadoSemana = agregarHorasIntervalo(calendario, regimeContinuidade, configuracao, presencasPorData, feriadosPorData, autorizacoesPorData, inicioSemanaIso, fimSemanaIso);
    const horasSemana = minutosUteis(agregadoSemana) / 60;

    if (horasSemana > parametros.horaExtraLimiteHorasPorSemana) {
      avisos.push(
        `Semana de ${inicioSemanaIso} a ${fimSemanaIso}: ${horasSemana.toFixed(1)}h de trabalho extraordinário (limite Art. 99 n.3: ${parametros.horaExtraLimiteHorasPorSemana}h/semana).`,
      );
    }
  }

  const trimestre = Math.floor((mes - 1) / 3);
  const mesInicioTrimestre = trimestre * 3 + 1;
  const mesFimTrimestre = mesInicioTrimestre + 2;
  const dataInicioTrimestre = formatarDataISO(ano, mesInicioTrimestre, 1);
  const dataFimTrimestre = formatarDataISO(ano, mesFimTrimestre, ultimoDiaDoMes(ano, mesFimTrimestre));
  const agregadoTrimestre = agregarHorasIntervalo(calendario, regimeContinuidade, configuracao, presencasPorData, feriadosPorData, autorizacoesPorData, dataInicioTrimestre, dataFimTrimestre);
  const horasTrimestre = minutosUteis(agregadoTrimestre) / 60;

  if (horasTrimestre > parametros.horaExtraLimiteHorasPorTrimestre) {
    avisos.push(
      `${trimestre + 1}.º trimestre de ${ano}: ${horasTrimestre.toFixed(1)}h de trabalho extraordinário (limite Art. 99 n.3: ${parametros.horaExtraLimiteHorasPorTrimestre}h/trimestre).`,
    );
  }

  const agregadoAno = agregarHorasIntervalo(calendario, regimeContinuidade, configuracao, presencasPorData, feriadosPorData, autorizacoesPorData, formatarDataISO(ano, 1, 1), formatarDataISO(ano, 12, 31));
  const horasAno = minutosUteis(agregadoAno) / 60;

  if (horasAno > parametros.horaExtraLimiteHorasPorAno) {
    avisos.push(`Ano ${ano}: ${horasAno.toFixed(1)}h de trabalho extraordinário (limite Art. 99 n.3: ${parametros.horaExtraLimiteHorasPorAno}h/ano).`);
  }

  // Limite INTERNO da empresa (configuracao-presencas.tsx), mais restritivo
  // que o legal — verificado apenas para o mes corrente.
  if (configuracao.limiteHorasExtraUteisPorMesMinutos !== null) {
    const agregadoMes = agregarHorasIntervalo(calendario, regimeContinuidade, configuracao, presencasPorData, feriadosPorData, autorizacoesPorData, formatarDataISO(ano, mes, 1), formatarDataISO(ano, mes, ultimoDia));
    const minutosMes = minutosUteis(agregadoMes);
    if (minutosMes > configuracao.limiteHorasExtraUteisPorMesMinutos) {
      avisos.push(
        `Mês ${mes}/${ano}: ${(minutosMes / 60).toFixed(1)}h de trabalho extraordinário (limite interno da empresa: ${(configuracao.limiteHorasExtraUteisPorMesMinutos / 60).toFixed(1)}h/mês).`,
      );
    }
  }

  return avisos;
}

export interface CreditoBancoHorasProposto {
  data: string;
  origem: "hora_extra_util" | "hora_extra_excepcional" | "descanso_compensatorio";
  minutos: number;
  autorizacaoId?: string;
}

export interface ApuramentoHorasMensal {
  // Valores monetarios das rubricas de sistema (ver rubricas-salariais.tsx)
  valorHorasExtraUteis: number;
  valorHorasExtraSabado: number;
  valorHorasExtraDomingo: number;
  valorHorasExtraFeriadoNacional: number;
  valorHorasExtraFeriadoLocal: number;
  valorHorasExtraTolerancia: number;
  valorDescontoFalta: number;
  valorDescontoAtraso: number;
  valorDescontoSaidaAntecipada: number;

  // Creditos propostos para o Banco de Horas — persistidos por
  // folha-pagamento.tsx APOS o processamento da folha ser bem sucedido.
  creditosBancoHorasPropostos: CreditoBancoHorasProposto[];

  // Minutos totais por categoria (pago + banco de horas juntos) — para
  // relatorios de conformidade (relatorios-presencas.tsx), onde interessa o
  // tempo trabalhado independentemente da forma de compensacao.
  minutosHorasExtraUteis: number;
  minutosHorasExtraSabado: number;
  minutosHorasExtraDomingo: number;
  minutosHorasExtraFeriadoNacional: number;
  minutosHorasExtraFeriadoLocal: number;
  minutosHorasExtraTolerancia: number;

  diasFaltaInjustificada: number;
  minutosAtraso: number;
  minutosSaidaAntecipada: number;
  avisos: string[];
}

function apuramentoVazio(aviso?: string): ApuramentoHorasMensal {
  return {
    valorHorasExtraUteis: 0,
    valorHorasExtraSabado: 0,
    valorHorasExtraDomingo: 0,
    valorHorasExtraFeriadoNacional: 0,
    valorHorasExtraFeriadoLocal: 0,
    valorHorasExtraTolerancia: 0,
    valorDescontoFalta: 0,
    valorDescontoAtraso: 0,
    valorDescontoSaidaAntecipada: 0,
    creditosBancoHorasPropostos: [],
    minutosHorasExtraUteis: 0,
    minutosHorasExtraSabado: 0,
    minutosHorasExtraDomingo: 0,
    minutosHorasExtraFeriadoNacional: 0,
    minutosHorasExtraFeriadoLocal: 0,
    minutosHorasExtraTolerancia: 0,
    diasFaltaInjustificada: 0,
    minutosAtraso: 0,
    minutosSaidaAntecipada: 0,
    avisos: aviso ? [aviso] : [],
  };
}

// Apura, para um colaborador e um mes/ano, os valores das rubricas de
// sistema ligadas a Presencas e os creditos propostos para o Banco de
// Horas — ver rubricas-salariais.tsx, calculo-salarial.tsx e
// folha-pagamento.tsx, que persiste os creditos apos o processamento.
export async function apurarHorasColaboradorMes(
  empresaId: string,
  colaborador: Colaborador,
  ano: number,
  mes: number,
  parametros: ParametrosFiscaisRH,
): Promise<ApuramentoHorasMensal> {
  const calendario = await obterCalendarioTrabalhoAplicavel(empresaId, colaborador.calendarioTrabalhoId);
  if (!calendario) {
    return apuramentoVazio("Nenhum calendário de trabalho configurado — horas extra, faltas e atrasos não foram calculados automaticamente.");
  }

  const configuracao = await obterConfiguracaoPresencas(empresaId);

  const dataInicioAno = formatarDataISO(ano, 1, 1);
  const dataFimAno = formatarDataISO(ano, 12, 31);

  const [presencasDoAno, feriadosDoAno, autorizacoesDoAno] = await Promise.all([
    listarPresencas(empresaId, { colaboradorId: colaborador.id, dataInicio: dataInicioAno, dataFim: dataFimAno }),
    listarFeriados(ano, { provincia: colaborador.provincia }),
    listarAutorizacoesHoraExtra(empresaId, { colaboradorId: colaborador.id, estado: "aprovado", dataInicio: dataInicioAno, dataFim: dataFimAno }),
  ]);

  const presencasPorData = new Map(presencasDoAno.map((p) => [p.data, p]));
  const feriadosPorData = new Map(feriadosDoAno.map((f) => [f.data, f]));
  const autorizacoesPorData = new Map(autorizacoesDoAno.map((a) => [a.data, a]));

  const ultimoDia = ultimoDiaDoMes(ano, mes);
  const dataInicioMes = formatarDataISO(ano, mes, 1);
  const dataFimMes = formatarDataISO(ano, mes, ultimoDia);

  const agregadoMes = agregarHorasIntervalo(calendario, colaborador.regimeContinuidade, configuracao, presencasPorData, feriadosPorData, autorizacoesPorData, dataInicioMes, dataFimMes);
  const minutosNormaisPorDia = calcularMinutosNormaisDiarios(calendario);
  const valorPorMinuto = calcularValorHoraNormalPorMinuto(colaborador.salarioBase, agregadoMes.diasUteisNoIntervalo, minutosNormaisPorDia);

  const valorHorasExtraUteis = calcularValorHorasExtraUteis(
    agregadoMes.horasExtraUteisAte20h.pago,
    agregadoMes.horasExtraUteisApos20h.pago,
    valorPorMinuto,
    parametros,
  );

  const valoresExcepcional: Record<TipoDiaExcepcional, number> = {} as Record<TipoDiaExcepcional, number>;
  for (const tipo of TIPOS_DIA_EXCEPCIONAL) {
    valoresExcepcional[tipo] = calcularValorHorasExtraExcepcional(agregadoMes.horasExtraExcepcional[tipo].pago, valorPorMinuto, parametros);
  }

  const valorDescontoFalta = calcularDescontoFalta(agregadoMes.diasFaltaInjustificada, colaborador.salarioBase, agregadoMes.diasUteisNoIntervalo);
  const valorDescontoAtraso = calcularDescontoTempo(agregadoMes.minutosAtraso, valorPorMinuto);
  const valorDescontoSaidaAntecipada = calcularDescontoTempo(agregadoMes.minutosSaidaAntecipada, valorPorMinuto);

  // Creditos propostos para o Banco de Horas.
  const creditosBancoHorasPropostos: CreditoBancoHorasProposto[] = [];

  const minutosBancoUteis =
    calcularMinutosBancaveis(agregadoMes.horasExtraUteisAte20h.banco, parametros.horaExtraMajoracaoPercentualAte24h) +
    calcularMinutosBancaveis(agregadoMes.horasExtraUteisApos20h.banco, parametros.horaExtraMajoracaoPercentualApos20h);
  if (minutosBancoUteis > 0) {
    creditosBancoHorasPropostos.push({ data: dataFimMes, origem: "hora_extra_util", minutos: minutosBancoUteis });
  }

  let minutosBancoExcepcionalTotal = 0;
  for (const tipo of TIPOS_DIA_EXCEPCIONAL) {
    minutosBancoExcepcionalTotal += agregadoMes.horasExtraExcepcional[tipo].banco;
  }
  if (minutosBancoExcepcionalTotal > 0) {
    const minutosBancaveis = calcularMinutosBancaveis(minutosBancoExcepcionalTotal, parametros.trabalhoExcepcionalMajoracaoPercentual);
    creditosBancoHorasPropostos.push({ data: dataFimMes, origem: "hora_extra_excepcional", minutos: minutosBancaveis });
  }

  for (const descanso of agregadoMes.descansosCompensatorios) {
    const minutosDireito = calcularMinutosDescansoCompensatorio(descanso.minutosTrabalhados, minutosNormaisPorDia);
    if (minutosDireito > 0) {
      creditosBancoHorasPropostos.push({ data: descanso.data, origem: "descanso_compensatorio", minutos: minutosDireito });
    }
  }

  const avisos = verificarLimitesHoraExtra(calendario, colaborador.regimeContinuidade, configuracao, presencasPorData, feriadosPorData, autorizacoesPorData, ano, mes, parametros);

  const minutosPorTipo = (tipo: TipoDiaExcepcional) => agregadoMes.horasExtraExcepcional[tipo].pago + agregadoMes.horasExtraExcepcional[tipo].banco;

  return {
    valorHorasExtraUteis,
    valorHorasExtraSabado: valoresExcepcional.sabado,
    valorHorasExtraDomingo: valoresExcepcional.domingo,
    valorHorasExtraFeriadoNacional: valoresExcepcional.feriado_nacional,
    valorHorasExtraFeriadoLocal: valoresExcepcional.feriado_local,
    valorHorasExtraTolerancia: valoresExcepcional.tolerancia_ponto,
    valorDescontoFalta,
    valorDescontoAtraso,
    valorDescontoSaidaAntecipada,
    creditosBancoHorasPropostos,
    minutosHorasExtraUteis: agregadoMes.horasExtraUteisAte20h.pago + agregadoMes.horasExtraUteisAte20h.banco + agregadoMes.horasExtraUteisApos20h.pago + agregadoMes.horasExtraUteisApos20h.banco,
    minutosHorasExtraSabado: minutosPorTipo("sabado"),
    minutosHorasExtraDomingo: minutosPorTipo("domingo"),
    minutosHorasExtraFeriadoNacional: minutosPorTipo("feriado_nacional"),
    minutosHorasExtraFeriadoLocal: minutosPorTipo("feriado_local"),
    minutosHorasExtraTolerancia: minutosPorTipo("tolerancia_ponto"),
    diasFaltaInjustificada: agregadoMes.diasFaltaInjustificada,
    minutosAtraso: agregadoMes.minutosAtraso,
    minutosSaidaAntecipada: agregadoMes.minutosSaidaAntecipada,
    avisos,
  };
}
