/*
 * O engorde exato (nov/2026): o contorno de qualquer peça (o arredondado da
 * letra, o contorno duplo, o "Contorno por fora", o contorno de adesivo da
 * foto) sai da própria silhueta deslocada em volta, em círculo, como a soma
 * da peça com um disco (o Contorno do Corel com cantos redondos). Nada de
 * desfoque com corte (deixava a forma de nuvem e a borda em escadinha) e
 * nada de cópias pelo text-shadow (as quinas retas das cópias formavam
 * degraus e a borda endurecia).
 *
 * Como fica nítido em qualquer zoom: só há deslocamentos e "o maior valor"
 * (feBlend lighten) num canal de cor; a borda suave (antisserrilhada) que o
 * Chrome desenha na resolução da tela passa intacta. O alfa vira cor no
 * começo (fundo preto opaco, peça branca) e volta a ser alfa no fim.
 *
 * Por que em camadas (um elemento por passo, um dentro do outro): dentro de
 * um filtro só, o Chrome refaz o resultado do meio a cada vez que ele é
 * usado (com passos encadeados a conta explode e a tela congela); já o
 * conteúdo de um elemento é pintado uma vez só e vira a fonte do filtro do
 * elemento de fora. Os passos crescem (cada um no máximo a largura mínima do
 * desenho já engordado, para não abrir buraco): poucos passos e poucas
 * cópias, e a quina viva vira curva lisa já no primeiro passo pequeno.
 *
 * Tudo é string pura (o mesmo HTML no palco e no Chrome do servidor).
 */

const n = (v: number) => Math.round(v * 1000) / 1000;

/* A tolerância do degrau entre duas cópias (px da arte): abaixo de 0,1 px não aparece nem com zoom grande. */
const TOLERANCIA = 0.1;
/* O primeiro passo é pequeno (as quinas vivas pedem cópias bem juntas); depois cresce até 3 vezes o já feito. */
const PRIMEIRO_PASSO = 1.2;

export type PassoDoEngorde = { raio: number; pontas: number; giro: number };

/** Os passos do engorde de `r` px: cada um com o raio e quantas cópias em volta. */
export function passosDoEngorde(r: number): PassoDoEngorde[] {
  const passos: PassoDoEngorde[] = [];
  let feito = 0;
  while (r - feito > 0.05 && passos.length < 9) {
    const raio = Math.min(r - feito, PRIMEIRO_PASSO + 2 * feito);
    /* O degrau entre cópias vizinhas é ~corda²/(8 × raio da curva que já existe): com a curva maior, cópias mais espaçadas. */
    const corda = Math.max(0.45, Math.sqrt(8 * TOLERANCIA * Math.max(feito, 0.25)));
    const pontas = Math.max(6, Math.min(72, Math.ceil((2 * Math.PI * raio) / corda)));
    passos.push({ raio, pontas, giro: passos.length % 2 ? Math.PI / pontas : 0 });
    feito += raio;
  }
  return passos;
}

const CAIXA = "position:absolute;left:0;top:0;width:100%;height:100%;";
/* Alfa → cor: a peça branca no fundo preto opaco (o fundo cobre a região toda do filtro). */
const ALFA_EM_COR = "0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 1";
/* Cor → alfa (o vermelho vira o alfa). */
const COR_EM_ALFA = "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0";

/* As cópias de um passo (um ou mais círculos): o maior (engordar) ou o menor (afinar) entre a fonte e cada cópia deslocada. */
function copias(circulos: PassoDoEngorde[], modo: "lighten" | "darken") {
  let s = "";
  let acc = "SourceGraphic";
  let i = 0;
  for (const passo of circulos)
    for (let k = 0; k < passo.pontas; k++, i++) {
      const a = passo.giro + (k / passo.pontas) * Math.PI * 2;
      s += `<feOffset in="SourceGraphic" dx="${n(Math.cos(a) * passo.raio)}" dy="${n(Math.sin(a) * passo.raio)}" result="o${i}"/><feBlend in="o${i}" in2="${acc}" mode="${modo}" result="b${i}"/>`;
      acc = `b${i}`;
    }
  return { s, acc };
}

export type OpcoesDoEngorde = {
  /* Prefixo dos ids dos filtros (único na arte). */
  id: string;
  /* Quanto engorda (px da arte). */
  raio: number;
  /* O desenho cujo alfa engorda (texto, forma, foto...). */
  conteudo: string;
  /* Primitivas SVG que deixam o resultado "tinta" (feFlood de uma cor ou feImage do degradê). */
  tinta: string;
  /* A caixa da peça (para a região do filtro em fração dela). */
  w: number;
  h: number;
  /* 0 a 1 (a força do contorno). */
  opacidade?: number;
  /* Fecha os buracos que sobram dentro (engorda mais e afina de volta): o raio do fechamento. */
  fechar?: number;
  /* Só o anel: tira o próprio desenho do miolo (letra vazada, contorno separado "só a borda"). */
  anel?: boolean;
  /* O rápido, de um passo só (o editor, só enquanto a silhueta em curvas não chega): poucas cópias, sem fechar os buracos. */
  rapido?: boolean;
};

/**
 * O contorno engordado pronto: os filtros (defs, para um <svg> escondido) e o
 * HTML (camadas absolutas do tamanho da peça) com só o contorno pintado, para
 * ir atrás do desenho. Sem raio, nada.
 */
export function engordeExato(o: OpcoesDoEngorde): { defs: string; html: string } {
  const fechar = Math.max(0, o.fechar ?? 0);
  if (!(o.raio > 0.05) && !fechar) return { defs: "", html: "" };
  const crescer: PassoDoEngorde[][] = o.rapido
    ? [[{ raio: o.raio, pontas: 16, giro: 0 }, { raio: o.raio * 0.55, pontas: 10, giro: Math.PI / 10 }]]
    : passosDoEngorde(Math.max(0, o.raio) + fechar).map((p) => [p]);
  const afinar = fechar > 0.05 && !o.rapido ? passosDoEngorde(fechar).map((p) => [p]) : [];
  /* A região do filtro: a caixa mais o engorde e uma folga (texto que passa da caixa). */
  const total = Math.max(0, o.raio) + fechar + 6;
  const fx = total / Math.max(1, o.w) + 0.08;
  const fy = total / Math.max(1, o.h) + 0.08;
  const regiao = `x="${n(-fx)}" y="${n(-fy)}" width="${n(1 + 2 * fx)}" height="${n(1 + 2 * fy)}" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB"`;
  const defs: string[] = [];
  const forca = Math.max(0, Math.min(1, o.opacidade ?? 1));
  const pintar = (entrada: string, matriz = COR_EM_ALFA) =>
    `<feColorMatrix in="${entrada}" type="matrix" values="${matriz}" result="forma"/>${o.tinta}<feComposite in="tinta" in2="forma" operator="in" result="pintado"/>${forca < 1 ? `<feComponentTransfer in="pintado"><feFuncA type="linear" slope="${n(forca)}"/></feComponentTransfer>` : ""}`;

  /* O começo: o alfa vira cor (um elemento só para isso: as cópias do passo seguinte leem a fonte pronta). */
  defs.push(`<filter id="${o.id}a" ${regiao}><feColorMatrix in="SourceAlpha" type="matrix" values="${ALFA_EM_COR}"/></filter>`);
  let html = `<div style="${CAIXA}filter:url(#${o.id}a)">${o.conteudo}</div>`;
  const passos = [...crescer.map((p) => ({ p, modo: "lighten" as const })), ...afinar.map((p) => ({ p, modo: "darken" as const }))];
  passos.forEach(({ p, modo }, k) => {
    const { s, acc } = copias(p, modo);
    /* No último passo: pinta (o vermelho vira o alfa); no anel, deixa o engordado só no vermelho para a camada de cima tirar o desenho. */
    const fim = k < passos.length - 1 ? "" : o.anel ? `<feColorMatrix in="${acc}" type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"/>` : pintar(acc);
    defs.push(`<filter id="${o.id}${k}" ${regiao}>${s}${fim}</filter>`);
    html = `<div style="${CAIXA}filter:url(#${o.id}${k})">${html}</div>`;
  });
  if (o.anel) {
    /* O anel: o desenho de novo, branco, por cima (o maior valor de cada canal); o vermelho menos o verde sobra só fora do desenho. */
    defs.push(`<filter id="${o.id}m" ${regiao}>${pintar("SourceGraphic", "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 -1 0 0 0")}</filter>`);
    html = `<div style="${CAIXA}isolation:isolate;filter:url(#${o.id}m)">${html}<div style="${CAIXA}mix-blend-mode:lighten;filter:brightness(0) invert(1)">${o.conteudo}</div></div>`;
  }
  return { defs: defs.join(""), html };
}

/** A tinta de uma cor só. */
export const tintaLisa = (cor: string) => `<feFlood flood-color="${cor}" result="tinta"/>`;
