import { randomBytes } from "node:crypto";

import type { Page } from "puppeteer";

import { abrirChrome, ChromeAusente } from "@/lib/catalogo-pdf/servidor/imprimir";
import { resolveCompanyPublicProfileId } from "@/lib/company-context";

import { COMO_ENTREGAR_O_ARQUIVO, entregarArquivo } from "../arquivos";
import { contatosNovos, recusaDeContatos } from "../contatos";
import type { ContextoDoAssistente } from "../contexto";
import type { Campos } from "../esquema";
import { DESCRICAO_DO_CAMPO_DE_IMAGEM, receberImagem } from "../imagens";
import { falha, pode, recusar } from "./comum";
import { arquivoFinal, imagemDoBanner, logoDoBanner } from "./gerador-de-banner-imagem";
import {
  contraste,
  coresDoBanner,
  FONTE_PADRAO_DO_TEXTO,
  FONTE_PADRAO_DO_TITULO,
  FORMATOS,
  hexValido,
  htmlDoBanner,
  lerFormato,
  lerModelo,
  MODELOS,
  NOMES_DOS_FORMATOS,
  type ChaveDoFormato,
  type ImagemDoBanner,
  type LogoDoBanner,
} from "./gerador-de-banner-modelos";
import { chaveDaFonte, nomeDaFonte } from "./loja-e-documentos-aparencia";
import { ErroDaFerramenta, type Ferramenta, type FerramentaDeLeitura } from "./tipos";

/*
 * gerar_banner: o assistente monta um banner pronto (carrossel, banner do
 * Vendor, faixa de seção, story ou post) com as cores, a fonte e o logo da
 * loja, a partir de um título e, se quiser, de uma foto. Não mexe na loja: o
 * banner vira um envio já recebido (assistentes_envios, migration 192) que as
 * outras ferramentas usam como "envio:<codigo>" depois que a pessoa aprova a
 * prévia.
 *
 * O desenho mora em gerador-de-banner-modelos.ts (HTML puro, testado sem
 * banco). Aqui: conferir, juntar as imagens, imprimir com o Chrome da casa e
 * guardar.
 */

/* ================================================================ impressão */

export class BannerDemorou extends Error {}
export class GeradorOcupado extends Error {}

/* O banner inteiro (abrir o Chrome, fontes, foto, captura) tem um minuto (o PDF, mais por folha). */
const TEMPO_MS = 60_000;
/* Um banner por vez no servidor (cada Chrome pesa centenas de MB); até 3 esperando. */
const ESPERANDO_NO_MAXIMO = 3;

declare global {
  var __filaDosBanners: { ultima: Promise<unknown>; esperando: number } | undefined;
}

function naFila<T>(trabalho: () => Promise<T>): Promise<T> {
  globalThis.__filaDosBanners ??= { ultima: Promise.resolve(), esperando: 0 };
  const fila = globalThis.__filaDosBanners;
  if (fila.esperando >= ESPERANDO_NO_MAXIMO) return Promise.reject(new GeradorOcupado("fila cheia"));
  fila.esperando += 1;
  const vez = fila.ultima.then(
    () => trabalho(),
    () => trabalho()
  );
  const fim = vez.finally(() => {
    fila.esperando -= 1;
  });
  fila.ultima = fim.catch(() => undefined);
  return fim;
}

/* A página só busca as fontes do Google; as imagens já vêm dentro dela (data:). */
function pedidoDoBannerPermitido(url: string) {
  if (url.startsWith("data:") || url.startsWith("about:")) return true;
  try {
    const { protocol, hostname } = new URL(url);
    return protocol === "https:" && (hostname === "fonts.googleapis.com" || hostname === "fonts.gstatic.com");
  } catch {
    return false;
  }
}

async function protegerPagina(pagina: Page) {
  await pagina.setRequestInterception(true);
  pagina.on("request", (pedido) => {
    if (pedidoDoBannerPermitido(pedido.url())) pedido.continue().catch(() => undefined);
    else pedido.abort().catch(() => undefined);
  });
  await pagina.setJavaScriptEnabled(true);
}

/*
 * O Chrome fica aberto entre um pedido e outro (abrir um Chrome novo a cada
 * foto ou download custava segundos): cada pedido trabalha numa aba isolada
 * (contexto próprio, sem cookies nem cache dos outros), que fecha no fim, deu
 * certo ou não. Caiu, abre de novo no próximo; parado 5 minutos, fecha para
 * não ocupar memória.
 */
type Navegador = Awaited<ReturnType<typeof abrirChrome>>;
type Aba = Pick<Navegador, "newPage">;
const PARADO_MS = 5 * 60_000;

declare global {
  var __chromeDosBanners: { navegador: Promise<Navegador> | null; relogio: ReturnType<typeof setTimeout> | null } | undefined;
}

async function chromeAberto(): Promise<Navegador> {
  globalThis.__chromeDosBanners ??= { navegador: null, relogio: null };
  const guardado = globalThis.__chromeDosBanners;
  if (guardado.relogio) clearTimeout(guardado.relogio);
  guardado.relogio = null;
  if (guardado.navegador) {
    const navegador = await guardado.navegador.catch(() => null);
    if (navegador?.connected) return navegador;
  }
  guardado.navegador = abrirChrome();
  const navegador = await guardado.navegador.catch((erro) => {
    guardado.navegador = null;
    throw erro;
  });
  navegador.once("disconnected", () => {
    if (globalThis.__chromeDosBanners?.navegador) globalThis.__chromeDosBanners.navegador = null;
  });
  return navegador;
}

function fecharQuandoParado() {
  const guardado = globalThis.__chromeDosBanners;
  if (!guardado) return;
  if (guardado.relogio) clearTimeout(guardado.relogio);
  guardado.relogio = setTimeout(() => {
    const aberto = guardado.navegador;
    guardado.navegador = null;
    guardado.relogio = null;
    void aberto?.then((n) => n.close()).catch(() => undefined);
  }, PARADO_MS);
  guardado.relogio.unref?.();
}

/* O Chrome na fila, numa aba isolada com o tempo dado; a aba fecha no fim (deu certo ou não). */
function comChrome<T>(trabalho: (navegador: Aba) => Promise<T>, tempoMs = TEMPO_MS): Promise<T> {
  return naFila(async () => {
    const navegador = await chromeAberto();
    const contexto = await navegador.createBrowserContext();
    let relogio: ReturnType<typeof setTimeout> | undefined;
    try {
      return await Promise.race([
        trabalho(contexto),
        new Promise<never>((_, falhar) => {
          relogio = setTimeout(() => falhar(new BannerDemorou("demorou")), tempoMs);
        }),
      ]);
    } finally {
      if (relogio) clearTimeout(relogio);
      await contexto.close().catch(() => undefined);
      fecharQuandoParado();
    }
  });
}

/* Abre o HTML e espera as fontes e o ajuste do texto (o modelo marca `body[data-pronto]`). */
async function abrirPronta(navegador: Aba, html: string, largura: number, altura: number, escala: number) {
  const pagina = await navegador.newPage();
  await protegerPagina(pagina);
  await pagina.setViewport({ width: largura, height: altura, deviceScaleFactor: Math.max(1, Math.min(4, escala)) });
  await pagina.setContent(html, { waitUntil: "load", timeout: 30_000 });
  /* Sem o sinal (o script falhou), fotografa assim mesmo: o CSS já limita o texto. */
  await pagina.waitForSelector("body[data-pronto]", { timeout: 20_000 }).catch(() => undefined);
  return pagina;
}

/**
 * Fotografa um HTML no tamanho exato (largura × altura) e devolve o PNG.
 * Sem Chrome lança ChromeAusente; passou de um minuto, BannerDemorou; fila
 * cheia, GeradorOcupado.
 */
/*
 * Opções do editor de artes (out/2026): `escala` multiplica os pixels (arte
 * em 2× ou 3×, nítida para impressão) e `transparente` fotografa sem o fundo
 * branco da página (PNG com fundo transparente).
 */
export type OpcoesDaImpressao = { escala?: number; transparente?: boolean };

export async function imprimirImagem(html: string, largura: number, altura: number, opcoes: OpcoesDaImpressao = {}): Promise<Buffer> {
  return comChrome(async (navegador) => {
    const pagina = await abrirPronta(navegador, html, largura, altura, opcoes.escala || 1);
    const png = await pagina.screenshot({ type: "png", clip: { x: 0, y: 0, width: largura, height: altura }, captureBeyondViewport: false, omitBackground: opcoes.transparente === true });
    return Buffer.from(png);
  });
}

/**
 * O vídeo animado: uma foto por camada (o fundo e cada peça ou grupo), cada
 * uma sozinha, com o resto escondido e sem o fundo branco da página (PNG
 * transparente). A página abre uma vez só; as camadas trocam escondendo as outras.
 */
export async function fotografarCamadas(html: string, largura: number, altura: number, escala: number, camadas: Array<{ ids: string[]; fundo: boolean }>): Promise<Buffer[]> {
  return comChrome(async (navegador) => {
    const pagina = await abrirPronta(navegador, html, largura, altura, escala);
    const fotos: Buffer[] = [];
    for (const camada of camadas) {
      await pagina.evaluate((ids: string[], fundo: boolean) => {
        const mostrar = new Set(ids);
        /* display, não visibility: com visibility o desfoque e a textura de uma peça escondida ainda pintavam um véu na foto. As peças são todas absolutas, então nada anda. */
        document.querySelectorAll<HTMLElement>("section [data-peca]").forEach((el) => {
          el.style.display = mostrar.has(el.dataset.peca || "") ? "" : "none";
        });
        document.querySelectorAll<HTMLElement>("section [data-fundo]").forEach((el) => {
          el.style.display = fundo ? "" : "none";
        });
      }, camada.ids, camada.fundo);
      fotos.push(Buffer.from(await pagina.screenshot({ type: "png", clip: { x: 0, y: 0, width: largura, height: altura }, captureBeyondViewport: false, omitBackground: !camada.fundo })));
    }
    return fotos;
  }, 150_000);
}

/**
 * Uma foto (PNG) de cada folha de um HTML com várias seções, uma embaixo da
 * outra (htmlDasPaginas): é o PDF do editor. O PDF do próprio Chrome vira
 * imagem toda peça com mesclagem, filtro ou efeito, em pedaços enormes (um
 * pôster de uma página dava 140 MB); a foto da folha inteira tem o mesmo
 * desenho do PNG e um tamanho previsível. Tempo: um minuto mais 20 s por folha
 * a mais (até 4 min).
 */
/* Cada folha na escala dela (uma faixa larga desce para caber no teto sem derrubar as outras). */
export async function fotografarFolhas(html: string, folhas: Array<{ largura: number; altura: number; escala: number }>): Promise<Buffer[]> {
  const tempo = Math.min(240_000, TEMPO_MS + 20_000 * Math.max(0, folhas.length - 1));
  return comChrome(async (navegador) => {
    const pagina = await abrirPronta(navegador, html, folhas[0].largura, folhas[0].altura, folhas[0].escala);
    const fotos: Buffer[] = [];
    let topo = 0;
    let atual = folhas[0].escala;
    for (const folha of folhas) {
      if (folha.escala !== atual) {
        await pagina.setViewport({ width: folhas[0].largura, height: folhas[0].altura, deviceScaleFactor: Math.max(1, Math.min(4, folha.escala)) });
        atual = folha.escala;
      }
      fotos.push(Buffer.from(await pagina.screenshot({ type: "png", clip: { x: 0, y: topo, width: folha.largura, height: folha.altura }, captureBeyondViewport: true })));
      topo += folha.altura;
    }
    return fotos;
  }, tempo);
}

/**
 * As folhas (fotografarFolhas) e, junto, quanto falta para cada texto caber
 * na caixa dele — a mesma conta do editor (texto-que-cabe.ts): largura sem
 * quebrar palavra, altura como desenhada. `fator` é o maior fator da letra
 * (até 1) com que o texto cabe, achado na mesma busca binária do botão
 * "Diminuir a letra". Serve ao assistente do editor de artes, que acerta o
 * tamanho da letra antes de devolver a arte.
 */
export type FaltaDoTexto = { id: string; largura: number; altura: number; razao: number; fator: number };
export async function fotografarEMedirFolhas(html: string, folhas: Array<{ largura: number; altura: number; escala: number }>): Promise<{ fotos: Buffer[]; faltas: FaltaDoTexto[] }> {
  const tempo = Math.min(240_000, TEMPO_MS + 20_000 * Math.max(0, folhas.length - 1));
  return comChrome(async (navegador) => {
    const pagina = await abrirPronta(navegador, html, folhas[0].largura, folhas[0].altura, folhas[0].escala);
    const faltas = (await pagina.evaluate(() => {
      const falta = (caixa: HTMLElement, campo: HTMLElement) => {
        const estilo = getComputedStyle(caixa);
        const largura = caixa.clientWidth - parseFloat(estilo.paddingLeft) - parseFloat(estilo.paddingRight);
        const altura = caixa.clientHeight - parseFloat(estilo.paddingTop) - parseFloat(estilo.paddingBottom);
        const antes = campo.style.overflowWrap;
        campo.style.overflowWrap = "normal";
        const precisaLargura = campo.scrollWidth;
        campo.style.overflowWrap = antes;
        const precisaAltura = campo.offsetHeight;
        return { largura: precisaLargura - largura, altura: precisaAltura - altura, razao: Math.max(precisaLargura / Math.max(1, largura), precisaAltura / Math.max(1, altura)) };
      };
      return [...document.querySelectorAll<HTMLElement>("[data-peca] [data-texto]")].flatMap((campo) => {
        const caixa = campo.parentElement;
        const id = campo.closest<HTMLElement>("[data-peca]")?.dataset.peca;
        if (!caixa || !id) return [];
        const medida = falta(caixa, campo);
        if (medida.largura <= 1 && medida.altura <= 1) return [{ id, ...medida, fator: 1 }];
        const comTamanho = [campo, ...campo.querySelectorAll<HTMLElement>("[style*='font-size']")];
        const antes = comTamanho.map((el) => el.style.fontSize);
        const base = comTamanho.map((el) => parseFloat(getComputedStyle(el).fontSize) || 0);
        const cabe = (f: number) => {
          comTamanho.forEach((el, i) => {
            if (base[i]) el.style.fontSize = `${base[i] * f}px`;
          });
          const m = falta(caixa, campo);
          return m.largura <= 1 && m.altura <= 1;
        };
        let baixo = Math.max(0.02, 6 / Math.max(6, base[0] || 6));
        let alto = 1;
        for (let i = 0; i < 14; i += 1) {
          const meio = (baixo + alto) / 2;
          if (cabe(meio)) baixo = meio;
          else alto = meio;
        }
        comTamanho.forEach((el, i) => {
          el.style.fontSize = antes[i];
        });
        return [{ id, ...medida, fator: baixo }];
      });
    })) as FaltaDoTexto[];
    const fotos: Buffer[] = [];
    let topo = 0;
    for (const folha of folhas) {
      fotos.push(Buffer.from(await pagina.screenshot({ type: "png", clip: { x: 0, y: topo, width: folha.largura, height: folha.altura }, captureBeyondViewport: true })));
      topo += folha.altura;
    }
    return { fotos, faltas };
  }, tempo);
}

/* ================================================================== guardar */

const BALDE = "assistentes-arquivos";
const LETRAS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";

/* 12 letras sorteadas, sem 0/O e 1/l/I (como os links curtos). */
function codigoDoEnvio() {
  let codigo = "";
  for (const byte of randomBytes(12)) codigo += LETRAS[byte % LETRAS.length];
  return codigo;
}

async function guardarComoEnvio(
  ctx: ContextoDoAssistente,
  arquivo: { conteudo: Buffer; tipo: string; extensao: string },
  formato: ChaveDoFormato,
  largura: number,
  altura: number
) {
  return guardarImagemComoEnvio(ctx, arquivo, { paraQue: `banner ${formato}`, nome: "banner", largura, altura });
}

/**
 * Guarda uma imagem gerada aqui (banner, imagem de produto) como um envio já
 * recebido (assistentes_envios, origem "gerada"): as outras ferramentas usam
 * o código "envio:..." como se a pessoa tivesse enviado o arquivo.
 */
export async function guardarImagemComoEnvio(
  ctx: ContextoDoAssistente,
  arquivo: { conteudo: Buffer; tipo: string; extensao: string },
  detalhes: { paraQue: string; nome: string; largura: number; altura: number }
) {
  const codigo = codigoDoEnvio();
  const nome = `${detalhes.nome}.${arquivo.extensao}`;
  const caminho = `${ctx.chave.companyId}/geradas/${codigo}/${nome}`;
  const deposito = ctx.admin.storage.from(BALDE);
  const { error } = await deposito.upload(caminho, arquivo.conteudo, { contentType: arquivo.tipo, upsert: false });
  if (error) falha("guardar a imagem", error);
  const { error: erroDaLinha } = await ctx.admin.from("assistentes_envios").insert({
    codigo,
    company_id: ctx.chave.companyId,
    usuario_id: ctx.user.id,
    origem: "gerada",
    para_que: detalhes.paraQue,
    caminho,
    nome,
    tipo: arquivo.tipo,
    tamanho: arquivo.conteudo.length,
    largura: detalhes.largura,
    altura: detalhes.altura,
    recebido_em: new Date().toISOString(),
  });
  if (erroDaLinha) {
    await deposito.remove([caminho]).catch(() => undefined);
    falha("guardar a imagem", erroDaLinha);
  }
  return codigo;
}

/* ================================================================ ferramenta */

const ONDE_USAR: Record<ChaveDoFormato, string> = {
  carrossel: "adicionar_banner (carrossel do topo da loja)",
  "carrossel vendor": "adicionar_banner (primeiro banner do modelo Vendor)",
  "banner duplo": "adicionar_banner (2º ou 3º banner do modelo Vendor)",
  "faixa de seção": "criar_secao (seção de banner de imagem)",
  story: "adicionar_story",
  quadrado: "nenhuma ferramenta da loja: é um post; a pessoa baixa pelo link da prévia e posta",
};

const LISTA_DE_FORMATOS = NOMES_DOS_FORMATOS.map((nome) => `"${nome}" ${FORMATOS[nome].largura}×${FORMATOS[nome].altura} (${FORMATOS[nome].uso})`).join("; ");

const CAMPOS: Campos = {
  titulo: { tipo: "texto", obrigatorio: true, max: 60, descricao: "O texto principal do banner (até 60 letras). Curto vende mais: 3 a 6 palavras." },
  subtitulo: { tipo: "texto", max: 120, descricao: "Uma linha de apoio abaixo do título (até 120 letras). Opcional." },
  texto_do_botao: { tipo: "texto", max: 24, descricao: 'O texto do botão desenhado no banner, como "Comprar agora" (até 24 letras). Opcional: sem ele, não há botão.' },
  selo: { tipo: "texto", max: 16, descricao: 'Um selo em destaque, como "-30%", "10% OFF" ou "NOVO" (até 16 letras). Opcional; no modelo de oferta, sem selo sai "OFERTA".' },
  formato: { tipo: "texto", max: 40, descricao: `O tamanho do banner. Opções: ${LISTA_DE_FORMATOS}. Padrão: "carrossel".` },
  modelo: { tipo: "texto", max: 40, descricao: `O desenho. Opções: ${MODELOS.map((nome) => `"${nome}"`).join(", ")}. Padrão: "imagem à direita".` },
  imagem: { tipo: "texto", max: 2000, descricao: `${DESCRICAO_DO_CAMPO_DE_IMAGEM} Opcional: sem imagem, o banner sai só com texto e formas.` },
  usar_logo: { tipo: "booleano", descricao: "Põe o logo da loja no banner (padrão: sim). Sem logo cadastrado, entra o nome da loja." },
  cor_de_fundo: { tipo: "texto", max: 9, descricao: "Cor de fundo, como #E11D48. Padrão: a cor principal da loja (branco no modelo minimalista)." },
  cor_do_texto: { tipo: "texto", max: 9, descricao: "Cor do texto. Padrão: branco ou preto, o que se lê melhor sobre o fundo." },
  cor_de_destaque: { tipo: "texto", max: 9, descricao: "Cor do botão e do selo. Padrão: amarelo sobre fundo escuro ou forte, e a cor da loja ou preto sobre fundo claro." },
  fonte: { tipo: "texto", max: 40, descricao: 'A fonte do título, pela chave ou pelo nome ("anton", "Bebas Neue"...), da mesma lista da aparência da loja (ver_aparencia_da_loja). Padrão: a fonte de títulos da loja.' },
};

function textoDoBanner(valor: unknown) {
  return String(valor ?? "")
    .replace(/\s+/g, " ")
    .trim();
}

type PerfilDaLoja = {
  primary_color?: string | null;
  catalog_font_heading?: string | null;
  catalog_font_body?: string | null;
  catalog_logo_url?: string | null;
  logo_url?: string | null;
  catalog_name?: string | null;
  store_name?: string | null;
};

const gerarBanner: FerramentaDeLeitura = {
  tipo: "leitura",
  nome: "gerar_banner",
  titulo: "Gerar banner",
  descricao:
    "Cria a imagem de um banner pronto, com as cores, a fonte e o logo da loja: carrossel do topo, banners do modelo Vendor, faixa de seção, story ou post quadrado, em 5 modelos de desenho. Não muda nada na loja: devolve um link de prévia para mostrar à pessoa e um código \"envio:...\" para usar em adicionar_banner, criar_secao ou adicionar_story depois que ela aprovar. Se ela pedir ajustes, gere de novo com os campos mudados.",
  campos: CAMPOS,
  permissoes: ["catalog.banners.write", "marketing.manage"],
  /* A imagem pode vir de um link de fora. */
  abertoAoMundo: true,
  async executar(ctx, args) {
    if (!pode(ctx, "catalog.banners.write") && !pode(ctx, "marketing.manage")) {
      recusar("Esta pessoa não tem permissão para criar banners. Quem libera é o dono da empresa, no painel, em Funcionários.");
    }

    const formato = lerFormato(args.formato);
    if (!formato) recusar(`Formato não reconhecido. Use um destes: ${LISTA_DE_FORMATOS}.`);
    const modelo = lerModelo(args.modelo);
    if (!modelo) recusar(`Modelo não reconhecido. Use um destes: ${MODELOS.join(", ")}.`);
    const { largura, altura } = FORMATOS[formato];

    const titulo = textoDoBanner(args.titulo);
    const subtitulo = textoDoBanner(args.subtitulo);
    const botao = textoDoBanner(args.texto_do_botao);
    const selo = textoDoBanner(args.selo);
    if (!titulo) recusar("Falta o título do banner.");
    /* O banner vai para o cliente: nada de link, telefone, e-mail ou chave Pix escritos pelo assistente. */
    const contatos = contatosNovos(null, [titulo, subtitulo, botao, selo].join("\n"));
    if (contatos.length) recusar(recusaDeContatos(contatos, "em uma arte feita por ela e enviada em Catálogo › Banners"));

    const cores: Record<string, string | null> = {};
    for (const campo of ["cor_de_fundo", "cor_do_texto", "cor_de_destaque"] as const) {
      if (args[campo] === undefined) continue;
      const cor = hexValido(args[campo]);
      if (!cor) recusar(`"${campo}": "${String(args[campo])}" não é uma cor. Use o código da cor, como #E11D48.`);
      cores[campo] = cor;
    }
    let fonte: string | null = null;
    if (args.fonte !== undefined) {
      fonte = chaveDaFonte(args.fonte);
      if (!fonte) recusar(`A fonte "${String(args.fonte)}" não está na lista. Veja as fontes em ver_aparencia_da_loja.`);
    }

    const { data, error } = await ctx.supabase
      .from("profiles")
      .select("primary_color,catalog_font_heading,catalog_font_body,catalog_logo_url,logo_url,catalog_name,store_name")
      .eq("id", resolveCompanyPublicProfileId(ctx.empresa, ctx.user.id))
      .maybeSingle();
    if (error) falha("ler a aparência da loja", error);
    const perfil = (data ?? {}) as PerfilDaLoja;
    const avisos: string[] = [];

    let imagem: ImagemDoBanner | null = null;
    let origemDaImagem: string | null = null;
    if (args.imagem !== undefined) {
      const recebida = await receberImagem(ctx, args.imagem, { minimo: 200 });
      origemDaImagem = recebida.origem;
      try {
        imagem = await imagemDoBanner(recebida.conteudo, FORMATOS[formato].disposicao === "faixa" ? 700 : 1920);
      } catch (erro) {
        console.error("[mcp] banner: preparar imagem", erro instanceof Error ? erro.message : erro);
        recusar("Não deu para usar essa imagem no banner. Tente outra (PNG, JPG ou WebP).");
      }
      if (Math.min(recebida.largura, recebida.altura) < 500 && FORMATOS[formato].disposicao !== "faixa") {
        avisos.push(`A imagem é pequena (${recebida.largura} × ${recebida.altura}) e pode ficar um pouco borrada no banner.`);
      }
    }

    const usarLogo = args.usar_logo !== false;
    let logo: LogoDoBanner | null = null;
    const enderecoDoLogo = perfil.catalog_logo_url || perfil.logo_url || "";
    if (usarLogo && /^https:\/\//i.test(enderecoDoLogo)) {
      try {
        const recebido = await receberImagem(ctx, enderecoDoLogo, { minimo: 16 });
        logo = await logoDoBanner(recebido.conteudo);
      } catch (erro) {
        if (!(erro instanceof ErroDaFerramenta)) console.error("[mcp] banner: logo", erro instanceof Error ? erro.message : erro);
        avisos.push("O logo da loja não abriu (ou é SVG); o nome da loja entrou no lugar.");
      }
    }
    const nomeDaLoja = perfil.catalog_name || perfil.store_name || null;

    const paleta = coresDoBanner({
      modelo,
      corDaLoja: perfil.primary_color,
      fundo: cores.cor_de_fundo,
      texto: cores.cor_do_texto,
      destaque: cores.cor_de_destaque,
    });
    if (contraste(paleta.texto, paleta.fundo) < 3) avisos.push("A cor do texto tem pouco contraste com o fundo: pode ficar difícil de ler.");
    if (contraste(paleta.destaque, paleta.fundo) < 1.5) avisos.push("A cor de destaque quase some no fundo: o botão e o selo vão aparecer pouco.");

    const fonteDoTitulo = fonte || chaveDaFonte(perfil.catalog_font_heading) || FONTE_PADRAO_DO_TITULO;
    const fonteDoTexto = chaveDaFonte(perfil.catalog_font_body) || FONTE_PADRAO_DO_TEXTO;

    const html = htmlDoBanner({
      formato,
      modelo,
      titulo,
      subtitulo,
      botao,
      selo,
      imagem,
      logo,
      nomeDaLoja,
      usarLogo,
      cores: paleta,
      fonteDoTitulo,
      fonteDoTexto,
    });

    let png: Buffer;
    try {
      png = await imprimirImagem(html, largura, altura);
    } catch (erro) {
      if (erro instanceof ChromeAusente) recusar("O gerador de banners não está disponível neste servidor agora. Avise o suporte da Ktalogu.");
      if (erro instanceof BannerDemorou) recusar("O banner demorou demais para ficar pronto. Tente de novo em instantes.");
      if (erro instanceof GeradorOcupado) recusar("O gerador de banners está ocupado com outros pedidos. Tente de novo em um minuto.");
      console.error("[mcp] banner", erro instanceof Error ? erro.message : erro);
      recusar("Não foi possível gerar o banner agora. Tente de novo em instantes.");
    }

    const paraRedeSocial = formato === "story" || formato === "quadrado";
    const final = await arquivoFinal(png, paraRedeSocial);
    const codigo = await guardarComoEnvio(ctx, final, formato, largura, altura);
    const previa = await entregarArquivo(ctx, {
      conteudo: final.conteudo,
      nome: `banner-${formato.normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/\s+/g, "-")}.${final.extensao}`,
      tipo: final.tipo,
    });

    return {
      dados: {
        previa_do_banner: previa.link,
        vale_ate: previa.vale_ate,
        codigo_para_usar: `envio:${codigo}`,
        formato,
        tamanho: `${largura} × ${altura} px`,
        arquivo: `${final.extensao.toUpperCase()}, ${previa.tamanho_kb} KB`,
        modelo,
        cores: { fundo: paleta.fundo, texto: paleta.texto, destaque: paleta.destaque },
        fonte_do_titulo: nomeDaFonte(fonteDoTitulo),
        ...(origemDaImagem ? { imagem: origemDaImagem } : {}),
        ...(avisos.length ? { avisos } : {}),
        proximo_passo: `Mostre a prévia à pessoa (o link abre a imagem). Se ela aprovar, use codigo_para_usar em ${ONDE_USAR[formato]}. Se pedir mudanças (texto, cor, modelo, imagem), chame gerar_banner de novo com os campos ajustados. O código vale 24 horas.`,
        como_entregar: COMO_ENTREGAR_O_ARQUIVO,
      },
      resumo: `Gerou um banner (${formato}, modelo ${modelo}): "${titulo}"`,
    };
  },
};

export const FERRAMENTAS_DO_GERADOR_DE_BANNER: Ferramenta[] = [gerarBanner];
