/*
 * O jeito curto do editor de artes pelo assistente (rodar: npx tsx --test src/lib/assistentes/ferramentas/editor-de-artes-documento.test.ts).
 */
import assert from "node:assert/strict";
import { test } from "node:test";
import { EFEITOS_DE_ENTRADA, TRANSICOES } from "@/lib/banner-editor/animacao";
import { audiosDoProjeto } from "@/lib/banner-editor/cena";
import { paginasDoDocumento, type PecaDeImagem, type PecaDeQr, type PecaDeTexto } from "@/lib/banner-editor/documento";
import { crc16, pixCopiaECola } from "@/lib/banner-editor/pix-copia-e-cola";
import {
  ErroNaArte,
  aplicarOperacoes,
  comEnderecos,
  documentoDoAssistente,
  enderecoDaCasa,
  enderecosDeFora,
  pecasDoAssistente,
  poseNoInstante,
  resumoDaArte,
  tamanhoDaArte,
  textosDaArte,
} from "./editor-de-artes-documento";
import { PARTES_DO_GUIA, guiaDoEditor } from "./editor-de-artes-guia";

const POST = { largura: 1080, altura: 1080 };
const DEPOSITO = "https://ptpstwltddugnnlewvbw.supabase.co/storage/v1/object/public/products";
const foto = `${DEPOSITO}/x/foto.webp`;

test("tamanho pelo formato ou livre; fora do limite recusa", () => {
  assert.deepEqual(tamanhoDaArte("story", undefined, undefined), { largura: 1080, altura: 1920, formato: "story" });
  assert.deepEqual(tamanhoDaArte(undefined, 1200, 628), { largura: 1200, altura: 628, formato: null });
  assert.throws(() => tamanhoDaArte(undefined, 50, 50), ErroNaArte);
});

test("texto no jeito curto: cor, degradê, caixa atrás, marca-texto e sombra", () => {
  const [t] = pecasDoAssistente({ tipo: "texto", texto: "OLÁ", x: 10, y: 20, w: 500, h: 120, fonte: "anton", tamanho: 90, cor: "#FF0000", fundo: { forma: "pilula", cor: "#000000" }, sombra: "suave" }, POST) as PecaDeTexto[];
  assert.equal(t.tipo, "texto");
  assert.equal(t.pintura.cor, "#FF0000");
  assert.equal(t.fonte, "anton");
  assert.equal(t.fundo?.forma, "pilula");
  assert.ok(t.sombra && t.sombra.desfoque > 0);
  assert.deepEqual([t.x, t.y, t.w, t.h], [10, 20, 500, 120]);
  const [d] = pecasDoAssistente({ tipo: "texto", texto: "X", degrade: { cor: "#111111", cor2: "#222222", angulo: 90 }, destaque: { cor: "#C6F432" } }, POST) as PecaDeTexto[];
  assert.equal(d.pintura.tipo, "linear");
  assert.equal(d.pintura.cor2, "#222222");
  assert.equal(d.destaque?.pintura.cor, "#C6F432");
});

test("imagem, traço em px e peça em 3D", () => {
  const [i] = pecasDoAssistente({ tipo: "imagem", imagem: foto, x: 0, y: 0, w: 400, h: 300, ajuste: "conter", raio: 20, giro3d: [10, -20] }, POST) as PecaDeImagem[];
  assert.equal(i.src, foto);
  assert.equal(i.ajuste, "conter");
  assert.equal(i.raio, 20);
  assert.ok(i.distorcao && i.distorcao.giro3d?.[1] === -20);
  assert.ok(i.distorcao.cantos.some(([x, y], k) => Math.abs(x - [0, 1, 1, 0][k]) > 0.01 || Math.abs(y - [0, 0, 1, 1][k]) > 0.01), "os cantos mudam com a perspectiva");
  const [tr] = pecasDoAssistente({ tipo: "traco", pontos: [[100, 100], [300, 200]], espessura: 10 }, POST);
  assert.equal(tr.tipo, "traco");
  assert.equal(tr.x, 90);
  assert.equal(tr.y, 90);
  assert.throws(() => pecasDoAssistente({ tipo: "imagem" }, POST), ErroNaArte);
  assert.throws(() => pecasDoAssistente({ tipo: "nada" }, POST), /não existe/);
});

test("elemento pronto encaixa na caixa e o atraso empurra os tempos", () => {
  const sem = pecasDoAssistente({ elemento: "v-notificacao", caixa: { x: 100, y: 100, w: 800, h: 200 } }, POST);
  const com = pecasDoAssistente({ elemento: "v-notificacao", caixa: { x: 100, y: 100, w: 800, h: 200 }, atraso: 1 }, POST);
  assert.ok(sem.length > 1);
  const inicio = (lista: typeof sem) => Math.min(...lista.filter((p) => p.animacao).map((p) => p.animacao!.inicio));
  assert.ok(Math.abs(inicio(com) - inicio(sem) - 1) < 1e-6);
  assert.ok(sem.every((p) => p.x >= 99 && p.x + p.w <= 901));
  assert.throws(() => pecasDoAssistente({ elemento: "nao-existe" }, POST), /procurar_no_editor/);
});

test("documento com páginas, fundo, cena, transição e áudio do catálogo", () => {
  const doc = documentoDoAssistente(
    {
      paginas: [
        { fundo: "#000000", duracao: 2, pecas: [{ tipo: "texto", texto: "A", animacao: { entrada: "chicote", inicio: 0.2 } }] },
        { fundo: { cor: "#3B1FE0", cor2: "#7C3AED" }, duracao: 3, transicao: "circulo", pecas: [{ tipo: "forma", forma: "estrela", cor: "#FFD400" }] },
      ],
      audios: [{ som: "swoosh-rapido", inicio: 1.9 }],
    },
    POST,
  );
  const paginas = paginasDoDocumento(doc);
  assert.equal(paginas.length, 2);
  assert.equal(paginas[1].transicao?.tipo, "circulo");
  assert.equal(audiosDoProjeto(paginas)[0].inicio, 1.9);
  const texto = paginas[0].pecas.find((p) => p.tipo === "texto")!;
  assert.equal(texto.animacao?.entrada, "chicote");
  assert.ok((texto.animacao?.duracao ?? 0) > 0, "a duração da entrada vem do padrão");
  const resumo = resumoDaArte(doc);
  assert.equal(resumo.duracao_total, 5);
  assert.throws(() => documentoDoAssistente({ paginas: [{ pecas: [] }], audios: [{ som: "nao-existe" }] }, POST), /catálogo/);
});

test("operações: cor nova vence a pintura, imagem nova vence o endereço, duplicar remapeia", () => {
  const doc = documentoDoAssistente({ paginas: [{ fundo: "#FFFFFF", pecas: [{ id: "f1", tipo: "forma", forma: "circulo", cor: "#000000" }, { id: "t1", tipo: "texto", texto: "Oi", degrade: { cor: "#111111", cor2: "#999999" } }, { id: "i1", tipo: "imagem", imagem: foto, dentroDe: "f1" }] }] }, POST);
  const outra = `${DEPOSITO}/x/outra.webp`;
  const { doc: mexido, feitos } = aplicarOperacoes(doc, [
    { op: "alterar_peca", pagina: 1, id: "t1", campos: { cor: "#FF0000", texto: "Tchau" } },
    { op: "alterar_peca", pagina: 1, id: "i1", campos: { imagem: outra } },
    { op: "duplicar_pagina", pagina: 1 },
    { op: "alterar_pagina", pagina: 2, fundo: "#000000", duracao: 4, transicao: "virar" },
  ]);
  assert.equal(feitos.length, 4);
  const [p1, p2] = paginasDoDocumento(mexido);
  const t1 = p1.pecas.find((p) => p.id === "t1") as PecaDeTexto;
  assert.equal(t1.pintura.tipo, "solida");
  assert.equal(t1.pintura.cor, "#FF0000");
  assert.equal(t1.texto, "Tchau");
  assert.equal((p1.pecas.find((p) => p.id === "i1") as PecaDeImagem).src, outra);
  /* A cópia tem ids novos e a foto aponta para a forma nova. */
  const ids1 = new Set(p1.pecas.map((p) => p.id));
  assert.ok(p2.pecas.every((p) => !ids1.has(p.id) || p.bloqueada));
  const fotoDaCopia = p2.pecas.find((p) => p.tipo === "imagem") as PecaDeImagem;
  const formaDaCopia = p2.pecas.find((p) => p.tipo === "forma" && p.nome !== "Fundo" && p.w < 1080);
  assert.equal(fotoDaCopia.dentroDe, formaDaCopia?.id);
  /* O fundo velho saiu: um fundo só, o novo. */
  const fundos = p2.pecas.filter((p) => p.tipo === "forma" && p.x <= 0 && p.y <= 0 && p.w >= 1080 && p.h >= 1080);
  assert.equal(fundos.length, 1);
  assert.equal(p2.transicao?.tipo, "virar");
  assert.throws(() => aplicarOperacoes(doc, [{ op: "voar" }]), /operacoes\[1\]/);
  assert.throws(() => aplicarOperacoes(doc, [{ op: "alterar_peca", pagina: 1, id: "zz", campos: {} }]), /ver_arte/);
});

test("imagens de fora: acha, troca e as da casa viram endereço", () => {
  const bruto = { paginas: [{ pecas: [{ tipo: "imagem", imagem: "envio:ABCDEFGHJKLM" }, { tipo: "imagem", imagem: foto }, { tipo: "imagem", imagem: "foto:caneca-roxo" }] }] };
  const fora = enderecosDeFora(bruto, (s) => s.startsWith(DEPOSITO));
  assert.deepEqual([...fora].sort(), ["envio:ABCDEFGHJKLM", "foto:caneca-roxo"]);
  assert.match(enderecoDaCasa("foto:caneca-roxo")!, /editor-modelos\/ramos\/caneca-roxo\.webp$/);
  assert.match(enderecoDaCasa("figurinha:gato")!, /gato\.webp$/);
  assert.equal(enderecoDaCasa("https://x.com/a.png"), null);
  const trocado = comEnderecos(bruto, new Map([["envio:ABCDEFGHJKLM", `${DEPOSITO}/novo.webp`]]));
  assert.equal(trocado.paginas[0].pecas[0].imagem, `${DEPOSITO}/novo.webp`);
});

test("pose no instante: a peça que entra em 1 s não aparece antes", () => {
  const doc = documentoDoAssistente({ paginas: [{ fundo: "#FFFFFF", duracao: 3, pecas: [{ id: "a", tipo: "forma", forma: "retangulo", cor: "#000000", x: 100, y: 100, w: 200, h: 200, animacao: { entrada: "surgir", inicio: 1, duracao: 0.4 } }] }] }, POST);
  const pagina = paginasDoDocumento(doc)[0];
  assert.ok(!poseNoInstante(pagina, 0.5).pecas.some((p) => p.id === "a"));
  assert.ok(poseNoInstante(pagina, 2).pecas.some((p) => p.id === "a"));
});

test("QR do Pix pela chave: o código do Banco Central, trocar o Pix e chave que não confere", () => {
  /* O exemplo do manual do Pix (BR Code estático): o CRC tem de dar 1D3D. */
  const ref = pixCopiaECola({ chave: "123e4567-e12b-12d1-a456-426655440000", nome: "Fulano de Tal", cidade: "BRASILIA" });
  assert.ok(ref.ok);
  assert.equal(ref.ok && ref.conteudo, "00020126580014br.gov.bcb.pix0136123e4567-e12b-12d1-a456-4266554400005204000053039865802BR5913Fulano de Tal6008BRASILIA62070503***63041D3D");
  const [qr] = pecasDoAssistente({ tipo: "qr", pix: { chave: "(87) 98870-1216", nome: "Gráfica São João", cidade: "Petrolândia", valor: "49,90" }, x: 700, y: 700, w: 240, h: 240 }, POST) as PecaDeQr[];
  assert.equal(qr.tipo, "qr");
  assert.match(qr.conteudo, /^000201.*br\.gov\.bcb\.pix0114\+5587988701216.*540549\.90.*5916Grafica Sao Joao6011Petrolandia.*6304[0-9A-F]{4}$/);
  assert.equal(qr.conteudo.slice(-4), crc16(qr.conteudo.slice(0, -4)));
  const doc = documentoDoAssistente({ paginas: [{ pecas: [{ id: "q1", tipo: "qr", conteudo: "https://wa.me/5587988701216" }] }] }, POST);
  const { doc: mexido } = aplicarOperacoes(doc, [{ op: "alterar_peca", pagina: 1, id: "q1", campos: { pix: { chave: "fulano@email.com", nome: "Fulano", cidade: "Recife" } } }]);
  const trocado = paginasDoDocumento(mexido)[0].pecas.find((p) => p.id === "q1") as PecaDeQr;
  assert.match(trocado.conteudo, /fulano@email\.com/);
  assert.ok(!("pix" in trocado));
  assert.throws(() => pecasDoAssistente({ tipo: "qr", pix: { chave: "529.982.247-24", nome: "A", cidade: "B" } }, POST), /CPF/);
  assert.throws(() => pecasDoAssistente({ tipo: "qr", pix: { chave: "52998224725", nome: "A", cidade: "B" } }, POST), /CPF ou um celular/);
  assert.throws(() => pecasDoAssistente({ tipo: "qr", pix: { chave: "fulano@email.com", cidade: "B" } }, POST), /nome/);
});

test("os textos da arte e o guia completo", () => {
  const doc = documentoDoAssistente({ paginas: [{ pecas: [{ tipo: "texto", texto: "Promo" }, { tipo: "qr", conteudo: "abc" }] }] }, POST);
  assert.match(textosDaArte(doc), /Promo[\s\S]*abc/);
  for (const parte of PARTES_DO_GUIA) assert.ok(guiaDoEditor(parte).length > 800, parte);
  const animacao = guiaDoEditor("animacao");
  for (const e of EFEITOS_DE_ENTRADA) assert.ok(animacao.includes(`- ${e.chave}:`), e.chave);
  for (const t of TRANSICOES) assert.ok(animacao.includes(`- ${t.chave}:`), t.chave);
});
