/*
  A logo e as fontes da loja, buscadas no servidor para o catálogo em PDF.

  A logo é baixada uma vez: dela sai a análise (clara, escura ou chapada, e as
  cores) e a versão PNG que vai dentro do PDF.

  As fontes são as da personalização da loja. O Google recusa família com o
  nome errado e responde erro em vez de folha, então cada folha é conferida
  antes: se a família não existir, o catálogo usa a dupla padrão em vez de
  sair com a letra do sistema.
*/

import sharp from "sharp";
import {
  analisarPixelsDaLogo,
  familiaDaFonte,
  folhaDoGoogle,
  FONTE_DO_TEXTO_PADRAO,
  FONTE_DO_TITULO_PADRAO,
  PESOS_DO_TEXTO,
  PESOS_DO_TITULO,
  pilhaDaFonte,
  type AnaliseDaLogo,
} from "../marca.ts";
import { baixar } from "./rede.ts";

/* ----------------------------------------------------------------- logo */

export type LogoDaLoja = { analise: AnaliseDaLogo; embutida: string } | null;

const logosLembradas = new Map<string, { quando: number; logo: LogoDaLoja }>();
const VALIDADE_DA_LOGO_MS = 10 * 60 * 1000;

export async function logoDaLoja(url: string | null): Promise<LogoDaLoja> {
  if (!url) return null;
  const lembrada = logosLembradas.get(url);
  if (lembrada && Date.now() - lembrada.quando < VALIDADE_DA_LOGO_MS) return lembrada.logo;
  const bruto = await baixar(url, { limite: 15 * 1024 * 1024 });
  let logo: LogoDaLoja = null;
  if (bruto) {
    try {
      const { data, info } = await sharp(bruto, { failOn: "none" })
        .ensureAlpha()
        .resize(96, 96, { fit: "inside" })
        .raw()
        .toBuffer({ resolveWithObject: true });
      const png = await sharp(bruto, { failOn: "none" })
        .resize({ width: 900, height: 300, fit: "inside", withoutEnlargement: true })
        .png()
        .toBuffer();
      logo = { analise: analisarPixelsDaLogo(data, info.width, info.height), embutida: `data:image/png;base64,${png.toString("base64")}` };
    } catch {
      logo = null;
    }
  }
  logosLembradas.set(url, { quando: Date.now(), logo });
  if (logosLembradas.size > 300) logosLembradas.clear();
  return logo;
}

/* --------------------------------------------------------------- fontes */

type FolhaConferida = { url: string; pesos: string | null };

const folhasLembradas = new Map<string, { quando: number; folha: FolhaConferida | null }>();
const VALIDADE_DA_FOLHA_MS = 24 * 60 * 60 * 1000;
/* O Google escolhe o formato da fonte pelo navegador que pede. */
const COMO_O_CHROME = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0 Safari/537.36";

/* true = folha boa; false = o Google recusou; null = não deu para saber. */
async function folhaResponde(url: string): Promise<boolean | null> {
  const controle = new AbortController();
  const relogio = setTimeout(() => controle.abort(), 6000);
  try {
    const resposta = await fetch(url, { signal: controle.signal, headers: { "user-agent": COMO_O_CHROME } });
    const tipo = resposta.headers.get("content-type") || "";
    await resposta.body?.cancel().catch(() => undefined);
    return resposta.ok && tipo.includes("text/css");
  } catch {
    return null;
  } finally {
    clearTimeout(relogio);
  }
}

/*
  A folha de uma família com os pesos que o desenho usa. Família de um peso
  só (Anton, Bebas Neue) o Google recusa com pesos: aí vai sem peso nenhum.
*/
async function folhaDaFamilia(familia: string, pesos: string): Promise<FolhaConferida | null> {
  const chave = `${familia}|${pesos}`;
  const lembrada = folhasLembradas.get(chave);
  if (lembrada && Date.now() - lembrada.quando < VALIDADE_DA_FOLHA_MS) return lembrada.folha;
  let folha: FolhaConferida | null = null;
  for (const tentativa of [pesos, "400;700", null]) {
    const url = folhaDoGoogle(familia, tentativa);
    const responde = await folhaResponde(url);
    if (responde === true) {
      folha = { url, pesos: tentativa };
      break;
    }
    /* Sem rede para conferir: vai como pedida, e o Chrome tenta sozinho. */
    if (responde === null) {
      return { url: folhaDoGoogle(familia, pesos), pesos };
    }
  }
  folhasLembradas.set(chave, { quando: Date.now(), folha });
  return folha;
}

export async function fontesDaLoja(chaveDoTitulo: string | null, chaveDoTexto: string | null) {
  const pedidaTitulo = familiaDaFonte(chaveDoTitulo);
  const pedidaTexto = familiaDaFonte(chaveDoTexto);
  let titulo = pedidaTitulo || FONTE_DO_TITULO_PADRAO;
  let texto = pedidaTexto || FONTE_DO_TEXTO_PADRAO;
  let [folhaTitulo, folhaTexto] = await Promise.all([folhaDaFamilia(titulo, PESOS_DO_TITULO), folhaDaFamilia(texto, PESOS_DO_TEXTO)]);
  if (!folhaTitulo && titulo !== FONTE_DO_TITULO_PADRAO) {
    titulo = FONTE_DO_TITULO_PADRAO;
    folhaTitulo = await folhaDaFamilia(titulo, PESOS_DO_TITULO);
  }
  if (!folhaTexto && texto !== FONTE_DO_TEXTO_PADRAO) {
    texto = FONTE_DO_TEXTO_PADRAO;
    folhaTexto = await folhaDaFamilia(texto, PESOS_DO_TEXTO);
  }
  return {
    titulo: pilhaDaFonte(titulo, FONTE_DO_TITULO_PADRAO),
    texto: pilhaDaFonte(texto, FONTE_DO_TEXTO_PADRAO),
    folhas: [folhaTitulo?.url, folhaTexto?.url].filter((url): url is string => Boolean(url)),
    /* Fonte de um peso só: sem isto o navegador "engorda" a letra na marra. */
    soPesoNormal: Boolean((folhaTitulo && folhaTitulo.pesos === null) || (folhaTexto && folhaTexto.pesos === null)),
  };
}
