/*
 * O "Pix copia e cola" (BR Code estático do Banco Central) a partir da chave:
 * o mesmo texto que o app do banco lê no QR. Serve ao QR da arte (o editor
 * aceita o código colado; o assistente monta pela chave). Campos no padrão
 * EMV: id de 2 dígitos + tamanho de 2 dígitos + valor; fecha com o CRC16
 * (CCITT-FALSE) do texto inteiro.
 */

export type TipoDaChavePix = "cpf" | "cnpj" | "telefone" | "email" | "aleatoria";
export const TIPOS_DA_CHAVE_PIX: TipoDaChavePix[] = ["cpf", "cnpj", "telefone", "email", "aleatoria"];

export type PedidoDoPix = {
  chave: string;
  tipo?: TipoDaChavePix;
  /* Quem recebe e a cidade (vão no código; o banco mostra o nome do cadastro do Pix). */
  nome: string;
  cidade: string;
  /* Em reais; sem valor, quem paga digita. */
  valor?: number | null;
  /* Texto curto que aparece para quem paga (opcional). */
  mensagem?: string | null;
  /* Identificador da cobrança (letras e números, até 25); sem ele, "***". */
  identificador?: string | null;
};

export type ResultadoDoPix = { ok: true; conteudo: string; chave: string; tipo: TipoDaChavePix } | { ok: false; erro: string };

const campo = (id: string, valor: string) => `${id}${String(valor.length).padStart(2, "0")}${valor}`;

/** Sem acento e só o que o padrão aceita (ASCII visível). */
function semAcento(texto: string) {
  return texto
    .normalize("NFD")
    .replace(/[̀-ͯ]/g, "")
    .replace(/[^\x20-\x7E]/g, "")
    .replace(/\s+/g, " ")
    .trim();
}

export function crc16(texto: string): string {
  let crc = 0xffff;
  for (let i = 0; i < texto.length; i += 1) {
    crc ^= texto.charCodeAt(i) << 8;
    for (let b = 0; b < 8; b += 1) crc = crc & 0x8000 ? ((crc << 1) ^ 0x1021) & 0xffff : (crc << 1) & 0xffff;
  }
  return crc.toString(16).toUpperCase().padStart(4, "0");
}

function cpfValido(d: string) {
  if (!/^\d{11}$/.test(d) || /^(\d)\1{10}$/.test(d)) return false;
  const digito = (n: number) => {
    let soma = 0;
    for (let i = 0; i < n; i += 1) soma += Number(d[i]) * (n + 1 - i);
    const r = (soma * 10) % 11;
    return r === 10 ? 0 : r;
  };
  return digito(9) === Number(d[9]) && digito(10) === Number(d[10]);
}

function cnpjValido(d: string) {
  if (!/^\d{14}$/.test(d) || /^(\d)\1{13}$/.test(d)) return false;
  const digito = (n: number) => {
    const pesos = n === 12 ? [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2] : [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
    const soma = pesos.reduce((s, p, i) => s + p * Number(d[i]), 0);
    const r = soma % 11;
    return r < 2 ? 0 : 11 - r;
  };
  return digito(12) === Number(d[12]) && digito(13) === Number(d[13]);
}

const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

/** A chave no formato do Pix (telefone com +55, CPF/CNPJ só números, e-mail minúsculo). */
export function chavePix(bruta: string, tipo?: TipoDaChavePix): { ok: true; chave: string; tipo: TipoDaChavePix } | { ok: false; erro: string } {
  const texto = String(bruta ?? "").trim();
  if (!texto) return { ok: false, erro: "Falta a chave Pix." };
  const digitos = texto.replace(/\D/g, "");
  let qual = tipo;
  if (!qual) {
    if (texto.includes("@")) qual = "email";
    else if (UUID.test(texto)) qual = "aleatoria";
    else if (texto.startsWith("+")) qual = "telefone";
    else if (/[a-z]/i.test(texto)) return { ok: false, erro: `Não reconheci a chave Pix "${texto}". Diga o tipo (cpf, cnpj, telefone, email ou aleatoria).` };
    else if (digitos.length === 14) qual = "cnpj";
    else if (digitos.length === 11) {
      /* 11 números: CPF ou celular com DDD. O jeito de escrever decide; senão, a conta do CPF. */
      if (/^\d{3}\.\d{3}\.\d{3}-\d{2}$/.test(texto)) qual = "cpf";
      else if (/[()\s]/.test(texto) || /^\d{7}-\d{4}$/.test(texto)) qual = "telefone";
      else if (cpfValido(digitos) && digitos[2] === "9") return { ok: false, erro: `A chave "${texto}" pode ser um CPF ou um celular. Diga o tipo da chave (cpf ou telefone).` };
      else qual = cpfValido(digitos) ? "cpf" : "telefone";
    } else if (digitos.length === 10 || ((digitos.length === 12 || digitos.length === 13) && digitos.startsWith("55"))) qual = "telefone";
    else return { ok: false, erro: `Não reconheci a chave Pix "${texto}". Diga o tipo (cpf, cnpj, telefone, email ou aleatoria).` };
  }
  switch (qual) {
    case "email": {
      const email = texto.toLowerCase();
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) && email.length <= 77 ? { ok: true, chave: email, tipo: qual } : { ok: false, erro: `"${texto}" não é um e-mail válido para chave Pix.` };
    }
    case "aleatoria":
      return UUID.test(texto) ? { ok: true, chave: texto.toLowerCase(), tipo: qual } : { ok: false, erro: `"${texto}" não é uma chave aleatória do Pix (são 32 letras e números com hífens).` };
    case "cpf":
      return cpfValido(digitos) ? { ok: true, chave: digitos, tipo: qual } : { ok: false, erro: `O CPF "${texto}" não confere (os dígitos finais não batem).` };
    case "cnpj":
      return cnpjValido(digitos) ? { ok: true, chave: digitos, tipo: qual } : { ok: false, erro: `O CNPJ "${texto}" não confere (os dígitos finais não batem).` };
    case "telefone": {
      const comPais = texto.startsWith("+") ? digitos : digitos.length === 10 || digitos.length === 11 ? `55${digitos}` : digitos;
      return /^55\d{10,11}$/.test(comPais) ? { ok: true, chave: `+${comPais}`, tipo: qual } : { ok: false, erro: `O telefone "${texto}" precisa do DDD (ex.: (87) 98870-1216).` };
    }
    default:
      return { ok: false, erro: `Tipo de chave "${String(qual)}" não existe. Use: ${TIPOS_DA_CHAVE_PIX.join(", ")}.` };
  }
}

/** O código "copia e cola" do Pix estático. */
export function pixCopiaECola(pedido: PedidoDoPix): ResultadoDoPix {
  const chave = chavePix(pedido.chave, pedido.tipo);
  if (!chave.ok) return chave;
  const nome = semAcento(pedido.nome ?? "").slice(0, 25);
  const cidade = semAcento(pedido.cidade ?? "").slice(0, 15);
  if (!nome) return { ok: false, erro: "Falta o nome de quem recebe o Pix." };
  if (!cidade) return { ok: false, erro: "Falta a cidade de quem recebe o Pix." };
  const valor = pedido.valor === undefined || pedido.valor === null ? null : Number(pedido.valor);
  if (valor !== null && (!Number.isFinite(valor) || valor <= 0 || valor > 9_999_999_999)) return { ok: false, erro: "O valor do Pix precisa ser maior que zero." };
  const identificador = semAcento(pedido.identificador ?? "").replace(/[^A-Za-z0-9]/g, "").slice(0, 25) || "***";
  /* A conta do Pix (id 26) cabe em 99 letras: a mensagem encurta para caber. */
  const base = campo("00", "br.gov.bcb.pix") + campo("01", chave.chave);
  const folga = 99 - base.length - 4;
  const mensagem = folga > 0 ? semAcento(pedido.mensagem ?? "").slice(0, Math.min(72, folga)) : "";
  const conta = base + (mensagem ? campo("02", mensagem) : "");
  const corpo =
    campo("00", "01") +
    campo("26", conta) +
    campo("52", "0000") +
    campo("53", "986") +
    (valor !== null ? campo("54", valor.toFixed(2)) : "") +
    campo("58", "BR") +
    campo("59", nome) +
    campo("60", cidade) +
    campo("62", campo("05", identificador)) +
    "6304";
  return { ok: true, conteudo: corpo + crc16(corpo), chave: chave.chave, tipo: chave.tipo };
}
