/*
 * "Colocar dentro" como o PowerClip (rodar: npx tsx --test src/lib/banner-editor/conteudo-dentro.test.ts).
 */
import assert from "node:assert/strict";
import { test } from "node:test";
import { acompanharRecipiente, colocarDentroDe, comConteudos, comConteudosJuntos, conteudosDe, encaixarNoRecipiente, porQueNaoRecebe, recipienteAberto } from "./conteudo-dentro";
import type { DocumentoDoBanner, Peca } from "./documento";

const perto = (a: number, b: number, folga = 1e-6) => Math.abs(a - b) <= folga;

const base = { rot: 0, opacidade: 1, nome: "" };
const forma = (id: string, x: number, y: number, w: number, h: number, extra: Partial<Peca> = {}) =>
  ({ ...base, id, tipo: "forma", forma: "retangulo", x, y, w, h, raio: 0, pintura: { tipo: "solida", cor: "#000000" }, borda: null, sombra: null, ...extra }) as unknown as Peca;
const foto = (id: string, x: number, y: number, w: number, h: number, extra: Partial<Peca> = {}) =>
  ({ ...base, id, tipo: "imagem", src: "https://x/y.jpg", x, y, w, h, raio: 0, ajuste: "cobrir", borda: null, sombra: null, filtros: { brilho: 100, contraste: 100, saturacao: 100, desfoque: 0 }, ...extra }) as unknown as Peca;
const arte = (pecas: Peca[]) => ({ largura: 1000, altura: 1000, fundo: { tipo: "cor", cor: "#FFFFFF" }, pecas }) as unknown as DocumentoDoBanner;

test("em cima do recipiente, a foto entra onde está (como no Corel)", () => {
  const r = forma("r", 100, 100, 400, 200);
  const f = foto("f", 450, 250, 300, 300, { rot: 12 });
  assert.equal(encaixarNoRecipiente([f], r)[0], f);
});

test("toda fora e desalinhada, vai para o meio sem mudar de tamanho nem de giro", () => {
  const r = forma("r", 100, 100, 400, 200);
  const [pequena] = encaixarNoRecipiente([foto("f", 800, 700, 100, 50)], r);
  assert.ok(perto(pequena.w, 100) && perto(pequena.h, 50), `${pequena.w}×${pequena.h}`);
  assert.ok(perto(pequena.x + pequena.w / 2, 300) && perto(pequena.y + pequena.h / 2, 200));
  const [grande] = encaixarNoRecipiente([foto("g", 800, 700, 900, 600)], r);
  assert.ok(perto(grande.w, 900) && perto(grande.x + grande.w / 2, 300) && perto(grande.y + grande.h / 2, 200));
  const [estrela] = encaixarNoRecipiente([forma("e", 700, 650, 40, 40, { rot: 20 })], r);
  assert.ok(perto(estrela.w, 40) && estrela.rot === 20 && perto(estrela.x + 20, 300) && perto(estrela.y + 20, 200));
});

test("toda fora mas alinhada (topo, base, lado, meio), leva o alinhamento", () => {
  const r = forma("r", 100, 100, 400, 200);
  /* À direita, com o topo na altura do topo do recipiente: entra encostada no topo, no meio da largura. */
  const [topo] = encaixarNoRecipiente([foto("t", 700, 100, 500, 300)], r);
  assert.ok(perto(topo.y, 100) && perto(topo.x + topo.w / 2, 300), `${topo.x},${topo.y}`);
  /* Embaixo, alinhada à direita: entra encostada na direita, no meio da altura. */
  const [direita] = encaixarNoRecipiente([forma("d", 300, 500, 200, 80)], r);
  assert.ok(perto(direita.x + direita.w, 500) && perto(direita.y + 40, 200), `${direita.x},${direita.y}`);
  /* À esquerda, com a base alinhada. */
  const [base] = encaixarNoRecipiente([forma("b", -200, 260, 60, 40)], r);
  assert.ok(perto(base.y + base.h, 300) && perto(base.x + 30, 300), `${base.x},${base.y}`);
  /* Acima, com o meio alinhado na horizontal. */
  const [meio] = encaixarNoRecipiente([forma("m", 280, -100, 40, 40)], r);
  assert.ok(perto(meio.x + 20, 300) && perto(meio.y + 20, 200));
});

test("girado, a conta vale nos eixos do recipiente", () => {
  const r = forma("r", 100, 100, 400, 200, { rot: 90 });
  const [c] = encaixarNoRecipiente([foto("f", 900, 900, 600, 600)], r);
  assert.ok(perto(c.x + c.w / 2, 300) && perto(c.y + c.h / 2, 200) && c.rot === 0);
});

test("colocar dentro põe logo acima do recipiente e marca dentroDe", () => {
  const pecas = [forma("r", 0, 0, 200, 200), forma("meio", 0, 0, 10, 10), foto("f", 600, 600, 100, 50)];
  const lista = colocarDentroDe(pecas, ["f"], "r");
  assert.deepEqual(lista.map((p) => p.id), ["r", "f", "meio"]);
  assert.equal(lista[1].dentroDe, "r");
  assert.deepEqual(conteudosDe("r", lista).map((p) => p.id), ["f"]);
  assert.equal(recipienteAberto(["f"], lista)?.id, "r");
  assert.equal(recipienteAberto(["f", "meio"], lista), null);
});

test("o recipiente que anda leva o conteúdo; com os dois mexidos, não anda duas vezes", () => {
  const antes = arte(colocarDentroDe([forma("r", 100, 100, 200, 200), foto("f", 0, 0, 100, 100)], ["f"], "r"));
  const f0 = antes.pecas[1];
  const andou = comConteudosJuntos(antes, { ...antes, pecas: antes.pecas.map((p) => (p.id === "r" ? { ...p, x: 150, y: 90 } : p)) });
  assert.equal(andou.pecas[1].x, f0.x + 50);
  assert.equal(andou.pecas[1].y, f0.y - 10);
  const juntos = comConteudosJuntos(antes, { ...antes, pecas: antes.pecas.map((p) => ({ ...p, x: p.x + 5 })) });
  assert.equal(juntos.pecas[1].x, f0.x + 5);
});

test("o recipiente que cresce e gira leva o conteúdo no mesmo ponto relativo", () => {
  const r0 = { x: 0, y: 0, w: 100, h: 100, rot: 0 };
  const c = foto("f", 50, 25, 40, 20);
  const maior = acompanharRecipiente(c, r0, { x: 0, y: 0, w: 200, h: 200, rot: 0 });
  assert.ok(perto(maior.w, 80) && perto(maior.h, 40) && perto(maior.x, 100) && perto(maior.y, 50));
  const girado = acompanharRecipiente(c, r0, { ...r0, rot: 90 });
  /* O meio (70, 35) gira 90° em volta de (50, 50): vira (65, 70). */
  assert.ok(perto(girado.x + girado.w / 2, 65) && perto(girado.y + girado.h / 2, 70), `${girado.x},${girado.y}`);
  assert.equal(girado.rot, 90);
});

test("esticado de um lado, o conteúdo que cobria só cresce o que precisa (e não cresce à toa ao voltar)", () => {
  let r = { x: 0, y: 0, w: 100, h: 100, rot: 0 };
  let c = foto("f", 0, 0, 100, 100);
  for (const w of [130, 160, 200]) {
    const novo = { ...r, w };
    c = acompanharRecipiente(c, r, novo);
    r = novo;
  }
  assert.ok(perto(c.w, 200) && perto(c.h, 200), `${c.w}×${c.h}`);
  for (const w of [150, 100]) {
    const novo = { ...r, w };
    c = acompanharRecipiente(c, r, novo);
    r = novo;
  }
  assert.ok(c.w <= 200 + 1e-6 && c.w >= 100 && c.h >= 100, `${c.w}×${c.h}`);
});

test("apagar o recipiente leva o conteúdo; frente leva os dois juntos", () => {
  const antes = arte(colocarDentroDe([forma("r", 0, 0, 100, 100), forma("o", 0, 0, 50, 50), foto("f", 0, 0, 10, 10)], ["f"], "r"));
  const apagado = comConteudosJuntos(antes, { ...antes, pecas: antes.pecas.filter((p) => p.id !== "r") });
  assert.deepEqual(apagado.pecas.map((p) => p.id), ["o"]);
  const naFrente = comConteudosJuntos(antes, { ...antes, pecas: [antes.pecas[2], antes.pecas[0], antes.pecas[1]] });
  assert.deepEqual(naFrente.pecas.map((p) => p.id), ["o", "r", "f"]);
  const semNada = arte([forma("a", 0, 0, 1, 1)]);
  const mexida = { ...semNada, pecas: [{ ...semNada.pecas[0], x: 5 }] };
  assert.equal(comConteudosJuntos(semNada, mexida), mexida);
});

test("quem recebe: texto só uma foto; peça com conteúdo não entra em outra; copiar leva o conteúdo", () => {
  const texto = { ...base, id: "t", tipo: "texto", texto: "OI", x: 0, y: 0, w: 100, h: 50 } as unknown as Peca;
  assert.equal(porQueNaoRecebe(texto, [foto("f", 0, 0, 1, 1)], []), null);
  assert.ok(porQueNaoRecebe(texto, [forma("a", 0, 0, 1, 1)], []));
  const pecas = colocarDentroDe([forma("r", 0, 0, 100, 100), foto("f", 0, 0, 10, 10), forma("s", 0, 0, 10, 10)], ["f"], "r");
  assert.ok(porQueNaoRecebe(forma("x", 0, 0, 1, 1), [pecas[0]], pecas));
  assert.deepEqual(comConteudos([pecas[0]], pecas).map((p) => p.id), ["r", "f"]);
});
