/*
 * O tratamento da foto no navegador (tratamento-da-foto.ts faz as contas):
 * lê os pixels da foto de antes, troca as cores e faz a retícula, e devolve
 * o arquivo (SVG da retícula em vetor, PNG do pontilhado, WebP/PNG da troca
 * de cor). Também a paleta das bolinhas (lembrada por foto) e o PNG para
 * gravação a laser (preto e branco puro, com o DPI gravado).
 */
import { paletaDosPixels, pngComDpi, pontilharPixels, reticulaEmPixels, reticulaEmSvg, trocarCoresNosPixels, type PaletaDaFoto, type TratamentoDaFoto } from "@/lib/banner-editor/tratamento-da-foto";

const fotos = new Map<string, Promise<HTMLImageElement>>();

/** A foto carregada (com permissão de ler os pixels), lembrada pelo endereço. */
export function carregarFoto(src: string): Promise<HTMLImageElement> {
  let pronta = fotos.get(src);
  if (!pronta) {
    pronta = new Promise<HTMLImageElement>((ok, falha) => {
      const img = new Image();
      img.crossOrigin = "anonymous";
      img.decoding = "async";
      img.onload = () => ok(img);
      img.onerror = () => falha(new Error("Não deu para abrir a foto."));
      img.src = src;
    });
    pronta.catch(() => fotos.delete(src));
    fotos.set(src, pronta);
    if (fotos.size > 30) fotos.delete(fotos.keys().next().value as string);
  }
  return pronta;
}

/* O tamanho de verdade (SVG sem tamanho fica com 512 de lado). */
const tamanhoDa = (img: HTMLImageElement) => ({ w: img.naturalWidth || img.width || 512, h: img.naturalHeight || img.height || 512 });

/** Os pixels da foto com o lado maior até `maxLado` (sem aumentar), ou com a `largura` pedida. */
export async function pixelsDaFoto(src: string, maxLado: number, largura?: number) {
  const img = await carregarFoto(src);
  const natural = tamanhoDa(img);
  const k = largura ? largura / natural.w : Math.min(1, maxLado / Math.max(natural.w, natural.h));
  const w = Math.max(1, Math.round(natural.w * k));
  const h = Math.max(1, Math.round(natural.h * k));
  const tela = document.createElement("canvas");
  tela.width = w;
  tela.height = h;
  const ctx = tela.getContext("2d", { willReadFrequently: true })!;
  ctx.imageSmoothingQuality = "high";
  ctx.drawImage(img, 0, 0, w, h);
  return { dados: ctx.getImageData(0, 0, w, h), natural };
}

const paletas = new Map<string, Promise<PaletaDaFoto>>();
/** As cores que compõem a foto (as bolinhas da barra), lembradas pelo endereço. */
export function paletaDaFoto(src: string): Promise<PaletaDaFoto> {
  let pronta = paletas.get(src);
  if (!pronta) {
    pronta = pixelsDaFoto(src, 180).then(({ dados }) => paletaDosPixels(dados.data));
    pronta.catch(() => paletas.delete(src));
    paletas.set(src, pronta);
  }
  return pronta;
}

/** A cor de um ponto da foto (x, y de 0 a 1), lida na foto de antes. */
export async function corNoPonto(src: string, x: number, y: number) {
  const { dados } = await pixelsDaFoto(src, 900);
  const px = Math.max(0, Math.min(dados.width - 1, Math.floor(x * dados.width)));
  const py = Math.max(0, Math.min(dados.height - 1, Math.floor(y * dados.height)));
  const o = (py * dados.width + px) * 4;
  return { r: dados.data[o], g: dados.data[o + 1], b: dados.data[o + 2], a: dados.data[o + 3] };
}

function paraArquivo(tela: HTMLCanvasElement, tipo: string, qualidade?: number) {
  return new Promise<Blob>((ok, falha) => tela.toBlob((b) => (b ? ok(b) : falha(new Error("Não deu para gerar a imagem."))), tipo, qualidade));
}

function telaComPixels(dados: Uint8ClampedArray, w: number, h: number) {
  const tela = document.createElement("canvas");
  tela.width = w;
  tela.height = h;
  tela.getContext("2d")!.putImageData(new ImageData(new Uint8ClampedArray(dados), w, h), 0, 0);
  return tela;
}

/**
 * O arquivo do tratamento. `final` lê a foto maior (o arquivo que fica na
 * arte); sem ele, uma prévia leve para mexer nos controles ao vivo.
 */
export async function gerarTratamento(t: TratamentoDaFoto, final: boolean): Promise<Blob> {
  const m = t.meioTom;
  if (m && reticulaEmPixels(m.estilo)) {
    /* O pontilhado: cada pixel do arquivo é tinta ou papel (a largura é a pedida). */
    const largura = final ? m.celulas : Math.min(m.celulas, 520);
    const { dados } = await pixelsDaFoto(t.de, Infinity, largura);
    if (t.trocas?.length) trocarCoresNosPixels(dados.data, t.trocas);
    return paraArquivo(telaComPixels(pontilharPixels(dados.data, dados.width, dados.height, m), dados.width, dados.height), "image/png");
  }
  const { dados, natural } = await pixelsDaFoto(t.de, m ? (final ? 1600 : 640) : final ? 2400 : 760);
  if (t.trocas?.length) trocarCoresNosPixels(dados.data, t.trocas);
  if (m) return new Blob([reticulaEmSvg(dados.data, dados.width, dados.height, m, natural)], { type: "image/svg+xml" });
  const tela = telaComPixels(dados.data, dados.width, dados.height);
  /* WebP guarda a transparência e fica leve; o navegador que não faz WebP devolve PNG. */
  return paraArquivo(tela, "image/webp", 0.94);
}

/**
 * O PNG para a gravação a laser: a imagem no tamanho pedido (pixels),
 * em cima de branco, cada pixel preto ou branco (sem cinza), com o DPI
 * gravado no arquivo. `pixelado`: o pontilhado aumenta sem suavizar.
 */
export async function pngParaLaser(src: string, largura: number, altura: number, dpi: number, pixelado = false): Promise<Blob> {
  const img = await carregarFoto(src);
  const w = Math.max(1, Math.round(largura));
  const h = Math.max(1, Math.round(altura));
  const tela = document.createElement("canvas");
  tela.width = w;
  tela.height = h;
  const ctx = tela.getContext("2d", { willReadFrequently: true })!;
  ctx.fillStyle = "#FFFFFF";
  ctx.fillRect(0, 0, w, h);
  /* O pontilhado aumenta pixel por pixel (suavizar borraria os pontos). */
  ctx.imageSmoothingEnabled = !pixelado;
  ctx.imageSmoothingQuality = "high";
  ctx.drawImage(img, 0, 0, w, h);
  const pixels = ctx.getImageData(0, 0, w, h);
  const d = pixels.data;
  for (let o = 0; o < d.length; o += 4) {
    const v = 0.2126 * d[o] + 0.7152 * d[o + 1] + 0.0722 * d[o + 2] < 128 ? 0 : 255;
    d[o] = v;
    d[o + 1] = v;
    d[o + 2] = v;
    d[o + 3] = 255;
  }
  ctx.putImageData(pixels, 0, 0);
  const png = new Uint8Array(await (await paraArquivo(tela, "image/png")).arrayBuffer());
  return new Blob([pngComDpi(png, dpi) as BlobPart], { type: "image/png" });
}

/** Baixa um arquivo com o nome dado. */
export function baixarArquivo(arquivo: Blob, nome: string) {
  const endereco = URL.createObjectURL(arquivo);
  const link = document.createElement("a");
  link.href = endereco;
  link.download = nome;
  document.body.appendChild(link);
  link.click();
  link.remove();
  window.setTimeout(() => URL.revokeObjectURL(endereco), 30_000);
}
