/**
 * Os períodos dos painéis, sempre no dia do Brasil (AAAA-MM-DD).
 *
 * Tudo aqui é conta pura: recebe "hoje" de fora, para dar o mesmo resultado no
 * teste e no servidor (que roda em UTC).
 */

export const PERIODOS = ["hoje", "7d", "30d", "este_mes", "mes_passado", "este_ano", "personalizado"] as const;
export type Periodo = (typeof PERIODOS)[number];

export const ROTULO_DO_PERIODO: Record<Periodo, string> = {
  hoje: "Hoje",
  "7d": "Últimos 7 dias",
  "30d": "Últimos 30 dias",
  este_mes: "Este mês",
  mes_passado: "Mês passado",
  este_ano: "Este ano",
  personalizado: "Personalizado",
};

/* Um período personalizado vai até dois anos: mais que isso pesa no banco e
   não cabe num gráfico que se leia. */
export const DIAS_MAXIMOS_DO_PERIODO = 731;

export type Intervalo = { de: string; ate: string };
export type PeriodoResolvido = Intervalo & { rotulo: string; anterior: Intervalo; rotuloDoAnterior: string };

const SO_DATA = /^\d{4}-\d{2}-\d{2}$/;

export function ehData(valor: unknown): valor is string {
  if (typeof valor !== "string" || !SO_DATA.test(valor)) return false;
  const [ano, mes, dia] = valor.split("-").map(Number);
  const data = new Date(Date.UTC(ano, mes - 1, dia));
  return data.getUTCFullYear() === ano && data.getUTCMonth() === mes - 1 && data.getUTCDate() === dia;
}

export function ehPeriodo(valor: unknown): valor is Periodo {
  return typeof valor === "string" && (PERIODOS as readonly string[]).includes(valor);
}

/** O dia de hoje no Brasil, AAAA-MM-DD. */
export function hojeNoBrasil(agora: Date = new Date()) {
  return new Intl.DateTimeFormat("en-CA", { timeZone: "America/Sao_Paulo", year: "numeric", month: "2-digit", day: "2-digit" }).format(agora);
}

export function somarDias(iso: string, dias: number) {
  const [ano, mes, dia] = iso.split("-").map(Number);
  return new Date(Date.UTC(ano, mes - 1, dia + dias)).toISOString().slice(0, 10);
}

export function diasEntre(inicio: string, fim: string) {
  return Math.round((Date.parse(`${fim}T00:00:00Z`) - Date.parse(`${inicio}T00:00:00Z`)) / 86_400_000);
}

function ultimoDiaDoMes(ano: number, mes: number) {
  return new Date(Date.UTC(ano, mes, 0)).getUTCDate();
}

function iso(ano: number, mes: number, dia: number) {
  return new Date(Date.UTC(ano, mes - 1, dia)).toISOString().slice(0, 10);
}

/** O mesmo dia `meses` antes, encostado no fim do mês quando o dia não existe (31/03 → 28/02). */
function voltarMeses(dataIso: string, meses: number) {
  const [ano, mes, dia] = dataIso.split("-").map(Number);
  const alvo = new Date(Date.UTC(ano, mes - 1 - meses, 1));
  const a = alvo.getUTCFullYear();
  const m = alvo.getUTCMonth() + 1;
  return iso(a, m, Math.min(dia, ultimoDiaDoMes(a, m)));
}

export function dataBr(dataIso: string) {
  const [ano, mes, dia] = dataIso.split("-");
  return `${dia}/${mes}/${ano}`;
}

function rotuloDoIntervalo(intervalo: Intervalo) {
  return intervalo.de === intervalo.ate ? dataBr(intervalo.de) : `${dataBr(intervalo.de)} a ${dataBr(intervalo.ate)}`;
}

/**
 * Transforma o período escolhido em datas, junto com o período anterior de
 * mesmo tamanho, que é a base da comparação do quadro de número.
 *
 * "Este mês" compara com o mês passado até o mesmo dia; "este ano", com o ano
 * passado até o mesmo dia. Os demais comparam com a janela imediatamente
 * anterior, do mesmo tamanho.
 */
export function resolverPeriodo(periodo: Periodo, hoje: string, personalizado?: { de?: string | null; ate?: string | null }): PeriodoResolvido {
  const [ano, mes] = hoje.split("-").map(Number);
  let atual: Intervalo;
  let anterior: Intervalo;

  switch (periodo) {
    case "hoje":
      atual = { de: hoje, ate: hoje };
      anterior = { de: somarDias(hoje, -1), ate: somarDias(hoje, -1) };
      break;
    case "7d":
    case "30d": {
      const dias = periodo === "7d" ? 7 : 30;
      atual = { de: somarDias(hoje, -(dias - 1)), ate: hoje };
      anterior = { de: somarDias(atual.de, -dias), ate: somarDias(atual.de, -1) };
      break;
    }
    case "este_mes":
      atual = { de: iso(ano, mes, 1), ate: hoje };
      anterior = { de: voltarMeses(atual.de, 1), ate: voltarMeses(hoje, 1) };
      break;
    case "mes_passado": {
      const de = voltarMeses(iso(ano, mes, 1), 1);
      const [a, m] = de.split("-").map(Number);
      atual = { de, ate: iso(a, m, ultimoDiaDoMes(a, m)) };
      const deAnterior = voltarMeses(de, 1);
      const [aa, ma] = deAnterior.split("-").map(Number);
      anterior = { de: deAnterior, ate: iso(aa, ma, ultimoDiaDoMes(aa, ma)) };
      break;
    }
    case "este_ano":
      atual = { de: iso(ano, 1, 1), ate: hoje };
      anterior = { de: iso(ano - 1, 1, 1), ate: voltarMeses(hoje, 12) };
      break;
    case "personalizado": {
      const de = personalizado?.de;
      const ate = personalizado?.ate;
      if (!ehData(de) || !ehData(ate)) throw new Error("Período personalizado precisa de início e fim (AAAA-MM-DD).");
      if (ate < de) throw new Error("O fim do período é antes do início.");
      if (diasEntre(de, ate) > DIAS_MAXIMOS_DO_PERIODO) throw new Error("Período grande demais: no máximo dois anos.");
      atual = { de, ate };
      const tamanho = diasEntre(de, ate) + 1;
      anterior = { de: somarDias(de, -tamanho), ate: somarDias(de, -1) };
      break;
    }
  }

  return {
    ...atual,
    rotulo: periodo === "personalizado" ? rotuloDoIntervalo(atual) : ROTULO_DO_PERIODO[periodo],
    anterior,
    rotuloDoAnterior: rotuloDoIntervalo(anterior),
  };
}

export type Granularidade = "dia" | "mes";

/** Até dois meses, a linha do tempo vai por dia; acima disso, por mês. */
export function granularidadeDe(intervalo: Intervalo): Granularidade {
  return diasEntre(intervalo.de, intervalo.ate) <= 62 ? "dia" : "mes";
}

/** Todas as casas da linha do tempo, na ordem, para nenhum dia vazio sumir do gráfico. */
export function casasDoTempo(intervalo: Intervalo, granularidade: Granularidade): string[] {
  const casas: string[] = [];
  if (granularidade === "dia") {
    for (let dia = intervalo.de; dia <= intervalo.ate; dia = somarDias(dia, 1)) casas.push(dia);
    return casas;
  }
  let [ano, mes] = intervalo.de.split("-").map(Number);
  const fim = intervalo.ate.slice(0, 7);
  for (;;) {
    const chave = `${ano}-${String(mes).padStart(2, "0")}`;
    casas.push(chave);
    if (chave >= fim) break;
    mes += 1;
    if (mes > 12) {
      mes = 1;
      ano += 1;
    }
  }
  return casas;
}

const MESES = ["jan", "fev", "mar", "abr", "mai", "jun", "jul", "ago", "set", "out", "nov", "dez"];

export function rotuloDaCasa(casa: string, granularidade: Granularidade) {
  const [ano, mes, dia] = casa.split("-");
  if (granularidade === "dia") return `${dia}/${mes}`;
  return `${MESES[Number(mes) - 1]}/${ano.slice(2)}`;
}

/** Em que casa da linha do tempo cai um dia. */
export function casaDoDia(dia: string, granularidade: Granularidade) {
  return granularidade === "dia" ? dia.slice(0, 10) : dia.slice(0, 7);
}

/**
 * O dia no Brasil de um instante gravado pelo banco (timestamptz).
 * O Brasil não tem horário de verão desde 2019: três horas a menos que o UTC.
 */
export function diaNoBrasil(instante: string | null | undefined): string | null {
  if (!instante) return null;
  if (SO_DATA.test(instante)) return instante;
  const ms = Date.parse(instante);
  if (!Number.isFinite(ms)) return null;
  return new Date(ms - 3 * 3_600_000).toISOString().slice(0, 10);
}

/** O começo de um dia do Brasil, como instante, para filtrar colunas com hora. */
export function inicioDoDia(dia: string) {
  return `${dia}T00:00:00-03:00`;
}
