/*
 * Retícula Viva (rodar: npx tsx --test src/lib/banner-editor/reticula-viva.test.ts).
 */
import assert from "node:assert/strict";
import { test } from "node:test";
import { LIMITE_DE_PONTOS_VIVOS, PRONTOS_DA_RETICULA_VIVA, enderecoDaReticulaViva, reticulaVivaLimpa, reticulaVivaPronta, svgDaReticulaViva, type ReticulaViva } from "./reticula-viva";
import { conteudoDaPeca, documentoLimpo, escalarPeca, solida, type PecaDeForma, type PecaDeTexto } from "./documento";

const viva = (extra: Partial<ReticulaViva> = {}): ReticulaViva => ({ pontos: [{ x: 0.2, y: 0.5, cor: "#E40FD3", alcance: 0.6 }], tamanho: 20, angulo: 0, forma: "pontos", papel: "#1405C8", ...extra });
const forma = (extra: Partial<PecaDeForma> = {}): PecaDeForma => ({ id: "f1", tipo: "forma", x: 0, y: 0, w: 400, h: 200, rot: 0, opacidade: 1, forma: "hexagono", pintura: solida("#1405C8"), raio: 0, borda: { cor: "#16C5C8", largura: 10 }, sombra: null, pontas: 5, profundidade: 0.45, ...extra });

test("limpeza: pontos e números dentro dos limites; sem pontos, nada", () => {
  const r = reticulaVivaLimpa({ pontos: Array.from({ length: 12 }, () => ({ x: 9, y: -9, cor: "vermelho", alcance: 99 })), tamanho: 999, angulo: -20, forma: "estrelas", papel: "#abcdef", mistura: "x" })!;
  assert.equal(r.pontos.length, LIMITE_DE_PONTOS_VIVOS);
  assert.deepEqual(r.pontos[0], { x: 1.5, y: -0.5, cor: "#7C3AED", alcance: 2 });
  assert.equal(r.tamanho, 400);
  assert.equal(r.angulo, 0);
  assert.equal(r.forma, "pontos");
  assert.equal(r.papel, "#ABCDEF");
  assert.equal("mistura" in r, false);
  assert.equal(reticulaVivaLimpa({ pontos: [] }), null);
  assert.equal(reticulaVivaLimpa("x"), null);
  assert.equal(reticulaVivaLimpa({ pontos: [{ x: 0.5, y: 0.5, cor: "#111111", alcance: 0.5 }], papel: null })!.papel, null, "sem papel: a cor da peça aparece");
});

test("o desenho: bolinha grande perto do ponto, pequena longe, nenhuma fora do alcance", () => {
  const svg = svgDaReticulaViva(viva(), 400, 200);
  assert.match(svg, /<rect width="400" height="200" fill="#1405C8"\/>/, "o papel");
  const raios = [...svg.matchAll(/<circle cx="([\d.]+)" cy="([\d.]+)" r="([\d.]+)"\/>/g)].map((m) => ({ x: Number(m[1]), y: Number(m[2]), r: Number(m[3]) }));
  assert.ok(raios.length > 20);
  const perto = raios.filter((c) => Math.hypot(c.x - 80, c.y - 100) < 25);
  const longe = raios.filter((c) => Math.hypot(c.x - 80, c.y - 100) > 90);
  assert.ok(Math.max(...perto.map((c) => c.r)) > Math.max(...longe.map((c) => c.r)) * 1.5, "cresce perto do ponto");
  assert.ok(raios.every((c) => Math.hypot(c.x - 80, c.y - 100) < 0.6 * 200 + 20), "nada fora do alcance");
  assert.match(svg, /fill="#e010d0"|fill="#e810d0"|fill="#e008d0"/i, "na cor do ponto (agrupada de 8 em 8)");
});

test("formas do ponto, ângulo e a guarda pelo tamanho", () => {
  assert.match(svgDaReticulaViva(viva({ forma: "quadrados" }), 200, 200), /<polygon points=/);
  assert.match(svgDaReticulaViva(viva({ forma: "linhas", angulo: 30 }), 200, 200), /<polygon points=/);
  assert.doesNotMatch(svgDaReticulaViva(viva({ papel: null }), 200, 200), /<rect/);
  const r = viva();
  assert.equal(enderecoDaReticulaViva(r, 400, 200), enderecoDaReticulaViva(r, 400, 200), "guardado pela retícula e pelo tamanho");
  assert.match(enderecoDaReticulaViva(r, 400, 200), /^data:image\/svg\+xml,/);
  /* Arte gigante: a célula cresce (no máximo ~110 no lado maior) para não pesar. */
  const grande = svgDaReticulaViva(viva({ tamanho: 2, pontos: [{ x: 0.5, y: 0.5, cor: "#111111", alcance: 2 }] }), 4000, 4000);
  assert.ok((grande.match(/<circle/g) || []).length < 110 * 110 * 1.2);
});

test("os prontos: na medida da peça (célula proporcional) e a foto multiplica", () => {
  for (const p of PRONTOS_DA_RETICULA_VIVA) assert.ok(reticulaVivaLimpa(reticulaVivaPronta(p.chave, 600, 300)), p.chave);
  assert.equal(reticulaVivaPronta("magenta-e-azul", 600, 300).tamanho, Math.round(300 * 0.045));
  assert.equal(reticulaVivaPronta("na-foto", 600, 300).mistura, "multiplicar");
  assert.equal(reticulaVivaPronta("nao-existe", 100, 100).papel, PRONTOS_DA_RETICULA_VIVA[0].papel);
});

test("no documento: forma com filtro recortado pela silhueta e a borda por cima; texto pelas letras; escala e limpeza", () => {
  const p = forma({ reticulaViva: viva() });
  const html = conteudoDaPeca(p);
  /* As bolinhas em vetor: o feImage aponta para o grupo inline (a imagem data: no filtro saía pixelada no zoom e no Baixar). */
  assert.match(html, /<g id="rvf1\.b"><rect [^>]*\/><path fill="#[0-9A-F]{6}" d="M/i, "o miolo da retícula inline (papel e caminhos)");
  assert.match(html, /<filter id="rvf1"[^>]*>[\s\S]*<feImage[^>]*href="#rvf1\.b"/);
  assert.doesNotMatch(html, /<feImage[^>]*href="data:/, "nada de imagem data: no filtro");
  assert.match(html, /operator="in"/, "recortada pela silhueta da peça");
  assert.ok(html.indexOf("filter:url(#rvf1)") < html.lastIndexOf('stroke="#16C5C8"'), "a borda volta por cima da retícula");
  const misturada = conteudoDaPeca(forma({ reticulaViva: viva({ mistura: "multiplicar" }) }));
  assert.match(misturada, /mix-blend-mode:multiply/);
  assert.match(misturada, /isolation:isolate/);
  const texto: PecaDeTexto = { id: "t1", tipo: "texto", x: 0, y: 0, w: 400, h: 160, rot: 0, opacidade: 1, texto: "VIVA", fonte: "archivo-black", tamanho: 90, peso: 900, pintura: solida("#111111"), alinhamento: "centro", vertical: "meio", italico: false, caixaAlta: false, riscado: false, sublinhado: false, espaco: 0, linha: 1, fundo: null, contorno: null, sombra: null, reticulaViva: viva() };
  assert.match(conteudoDaPeca(texto), /background-clip:text[^"]*"[^>]*><div[^>]*>VIVA/, "a retícula pintada pelas letras");
  assert.equal(escalarPeca(p, 2).reticulaViva!.tamanho, 40, "a célula cresce junto");
  const doc = documentoLimpo({ largura: 500, altura: 500, fundo: { tipo: "cor", cor: "#FFFFFF" }, pecas: [p, { ...forma({ id: "f2" }), reticulaViva: { pontos: [] } }] }, 500, 500);
  assert.ok(doc.pecas[0].reticulaViva);
  assert.equal(doc.pecas[1].reticulaViva, undefined);
});
