import sharp from "sharp";
import { ChromeAusente } from "@/lib/catalogo-pdf/servidor/imprimir";
import { BannerDemorou, GeradorOcupado, fotografarFolhas, imprimirImagem } from "@/lib/assistentes/ferramentas/gerador-de-banner";
import { paginaParaGrafica, paginasDoDocumento, type DocumentoDoBanner } from "./documento";
import { pdfDeFotos } from "./pdf-de-fotos";
import { ImagemRecusada, htmlComAsImagens } from "./servidor";

/*
 * O arquivo de uma arte (como o "Baixar" do Canva): PNG (com ou sem fundo),
 * JPG (com a qualidade escolhida) ou PDF com uma folha por página. Comum ao
 * download do editor (/api/artes/exportar), ao link público da arte
 * (/api/artes/publica/<código>/baixar) e ao assistente (baixar_arte).
 *
 * PNG e JPG são de uma página (`pagina`, de 0). O PDF é a foto de cada folha
 * (JPEG em 2× ou mais), no tamanho real quando a página é de impressão
 * (pixels ÷ ppp). `espelhar` vira tudo na horizontal (o transfer da
 * sublimação). A escala (1×, 2× ou 3×) desce sozinha acima de 8.000 px no
 * maior lado ou 40 milhões de pixels; `escala` na volta diz qual valeu.
 */

const MAIOR_LADO = 8000;
const MAXIMO_DE_PIXELS = 40_000_000;

export class ArquivoRecusado extends Error {
  constructor(
    mensagem: string,
    readonly status: number,
  ) {
    super(mensagem);
  }
}

export type PedidoDoArquivo = {
  formato: "png" | "jpg" | "pdf";
  pagina?: number;
  escala?: number;
  transparente?: boolean;
  espelhar?: boolean;
  qualidade?: number;
  grafica?: boolean;
};

export function nomeDoArquivo(bruto: unknown, extensao: string) {
  const base = String(bruto || "arte")
    .normalize("NFD")
    .replace(/[̀-ͯ]/g, "")
    .replace(/[^a-zA-Z0-9 _-]/g, "")
    .trim()
    .replace(/\s+/g, "-")
    .slice(0, 60);
  return `${base || "arte"}.${extensao}`;
}

export async function arquivoDaArte(documento: DocumentoDoBanner, pedido: PedidoDoArquivo): Promise<{ conteudo: Buffer; tipo: string; escala: number }> {
  const formato = pedido.formato;
  const transparente = formato === "png" && pedido.transparente === true;
  const espelhar = pedido.espelhar === true;
  const qualidade = Math.round(Math.min(100, Math.max(40, Number(pedido.qualidade) || 90)));
  const todas = paginasDoDocumento(documento);
  const paraGrafica = formato === "pdf" && pedido.grafica === true;
  const indice = Math.max(0, Math.min(todas.length - 1, Math.round(Number(pedido.pagina) || 0)));
  const paginas = formato === "pdf" ? (paraGrafica ? todas.map((p) => paginaParaGrafica(p, p.impressao?.dpi ?? 150)) : todas) : [todas[indice]];
  if (!paginas.some((pagina) => pagina.pecas.some((p) => !p.oculta))) throw new ArquivoRecusado("A arte está vazia. Ponha pelo menos um texto, uma forma ou uma imagem.", 400);

  /* O PDF é foto: no mínimo 2× (texto nítido no zoom e na impressão). O teto vale folha por folha. */
  const pedida = formato === "pdf" ? Math.max(2, Number(pedido.escala) || 2) : Number(pedido.escala);
  const escalaDa = (p: { largura: number; altura: number }) => {
    let e = [1, 2, 3].includes(pedida) ? pedida : 1;
    while (e > 1 && (Math.max(p.largura, p.altura) * e > MAIOR_LADO || p.largura * p.altura * e * e > MAXIMO_DE_PIXELS)) e -= 1;
    return e;
  };
  const escala = escalaDa(paginas[0]);

  let html: string;
  try {
    html = await htmlComAsImagens(paginas, { escala: formato === "pdf" ? Math.max(...paginas.map(escalaDa)) : escala, semFundo: transparente });
  } catch (falha) {
    if (falha instanceof ImagemRecusada) throw new ArquivoRecusado(falha.message, 400);
    throw falha;
  }

  let bruto: Buffer;
  try {
    if (formato === "pdf") {
      const fotos = await fotografarFolhas(html, paginas.map((p) => ({ largura: p.largura, altura: p.altura, escala: escalaDa(p) })));
      /* Folha no tamanho real: pixels ÷ ppp na de impressão; nas outras, 96 px por polegada (como o Chrome mede). */
      const folhas = await Promise.all(
        fotos.map(async (foto, i) => {
          const pagina = paginas[i];
          const pontosPorPixel = 72 / (pagina.impressao?.dpi ?? 96);
          const { data, info } = await sharp(foto).flop(espelhar).flatten({ background: "#FFFFFF" }).jpeg({ quality: 92, chromaSubsampling: "4:4:4" }).toBuffer({ resolveWithObject: true });
          return { jpeg: data, largura: info.width, altura: info.height, larguraPt: pagina.largura * pontosPorPixel, alturaPt: pagina.altura * pontosPorPixel };
        }),
      );
      bruto = pdfDeFotos(folhas);
    } else {
      bruto = await imprimirImagem(html, paginas[0].largura, paginas[0].altura, { escala, transparente });
      if (espelhar) bruto = await sharp(bruto).flop().png().toBuffer();
    }
  } catch (falha) {
    if (falha instanceof ChromeAusente) throw new ArquivoRecusado("O gerador de artes não está disponível agora. Tente de novo em instantes.", 503);
    if (falha instanceof BannerDemorou) throw new ArquivoRecusado("A arte demorou demais para ficar pronta. Tente de novo (ou baixe numa qualidade menor).", 504);
    if (falha instanceof GeradorOcupado) throw new ArquivoRecusado("O gerador está ocupado com outras artes. Tente de novo em um minuto.", 429);
    console.error("[artes] arquivo", falha instanceof Error ? falha.message : falha);
    throw new ArquivoRecusado("Não deu para gerar a arte agora. Tente de novo em instantes.", 500);
  }

  if (formato === "png") return { conteudo: await sharp(bruto).png({ compressionLevel: 8 }).withMetadata({ density: 72 * escala }).toBuffer(), tipo: "image/png", escala };
  if (formato === "jpg")
    return {
      conteudo: await sharp(bruto).flatten({ background: "#FFFFFF" }).jpeg({ quality: qualidade, mozjpeg: true, chromaSubsampling: qualidade >= 90 ? "4:4:4" : "4:2:0" }).withMetadata({ density: 72 * escala }).toBuffer(),
      tipo: "image/jpeg",
      escala,
    };
  return { conteudo: bruto, tipo: "application/pdf", escala };
}
