/*
 * O desenho de cada peça, guardado pela própria peça (as peças nunca são
 * alteradas no lugar: mudou, é outro objeto). Assim o React recebe o MESMO
 * objeto `{ __html }` quando nada mudou e não regrava o HTML. Sem isto, cada
 * desenho novo do palco reescrevia todas as peças: pesado, e o nó clicado
 * sumia entre o apertar e o soltar (o duplo clique se perdia).
 */
import { caixaDaPeca, camadaDoFundo, conteudoDaPeca, exibicaoDaPeca, usaSilhueta, type ContagemDaPagina, type FundoDoBanner, type Peca } from "@/lib/banner-editor/documento";
import { versaoDasSilhuetas } from "@/lib/banner-editor/silhuetas-prontas";
import { versaoDosVetores } from "@/lib/banner-editor/vetores";

/*
 * O que chega depois refaz o desenho da peça (`versao`): o SVG com cor
 * trocada (peça com vetor) e a silhueta em curvas dos contornos (a peça com
 * contorno arredondado, duplo, por fora ou de adesivo).
 */
const versaoDa = (p: Peca) => (p.tipo === "imagem" && p.vetor ? versaoDosVetores() : 0) * 1e6 + (usaSilhueta(p) ? versaoDasSilhuetas() : 0);
const daPeca = new WeakMap<Peca, { __html: string; versao: number }>();
const doFundo = new WeakMap<FundoDoBanner, Map<string, { __html: string }>>();

export function htmlDaPeca(p: Peca) {
  const versao = versaoDa(p);
  let guardado = daPeca.get(p);
  if (!guardado || guardado.versao !== versao) {
    guardado = { __html: conteudoDaPeca(p), versao };
    daPeca.set(p, guardado);
  }
  return guardado;
}

/*
 * Como a peça aparece (com a casca do "colocar dentro de" e o recheio do texto):
 * guardado pela peça e pelas que mexem nela (o recipiente ou o recheio), para
 * só refazer quando uma delas mudar.
 */
const exibida = new WeakMap<Peca, { ligadas: Peca[]; conta: string; versao: number; estilo: Record<string, string>; html: { __html: string } } | { ligadas: Peca[]; conta: string; versao: number; nada: true }>();
/* Quem mexe em quem ("dentro de"), contado uma vez por lista de peças: antes cada peça percorria a lista toda (421 × 421 a cada quadro no calendário). */
const ligacoesDaLista = new WeakMap<Peca[], Map<string, Peca[]>>();
const SEM_LIGADAS: Peca[] = [];
function ligadasDa(p: Peca, pecas: Peca[]) {
  let mapa = ligacoesDaLista.get(pecas);
  if (!mapa) {
    mapa = new Map();
    const porId = new Map(pecas.map((q) => [q.id, q]));
    for (const q of pecas) {
      const r = q.dentroDe ? porId.get(q.dentroDe) : undefined;
      if (!r) continue;
      mapa.set(q.id, [...(mapa.get(q.id) || []), r]);
      mapa.set(r.id, [...(mapa.get(r.id) || []), q]);
    }
    /* Na ordem da lista, como o filtro de antes (a comparação do guardado depende da ordem). */
    for (const [id, lista] of mapa) mapa.set(id, [...new Set(lista)].sort((a, b) => pecas.indexOf(a) - pecas.indexOf(b)));
    ligacoesDaLista.set(pecas, mapa);
  }
  return mapa.get(p.id) ?? SEM_LIGADAS;
}
export function exibicaoGuardada(p: Peca, pecas: Peca[], contagem?: ContagemDaPagina | null) {
  const ligadas = ligadasDa(p, pecas);
  /* O contador ("01/05") muda com a página: entra na chave só da peça que tem o contador. */
  const temContador = p.tipo === "texto" && /\{(pagina|total)\}/.test(p.texto + (p.html || ""));
  const conta = temContador && contagem ? `${contagem.pagina}/${contagem.total}` : "";
  const guardada = exibida.get(p);
  const versao = versaoDa(p);
  if (guardada && guardada.versao === versao && guardada.conta === conta && guardada.ligadas.length === ligadas.length && guardada.ligadas.every((q, i) => q === ligadas[i])) return "nada" in guardada ? null : guardada;
  /* Sem recipiente, recheio nem contador (o comum): a caixa de sempre e o desenho já guardado. */
  const ex = ligadas.length || conta ? exibicaoDaPeca(p, pecas, undefined, contagem) : { estilo: caixaDaPeca(p) as Record<string, string>, html: "" };
  const nova = ex ? { ligadas, conta, versao, estilo: ex.estilo, html: ligadas.length || conta ? { __html: ex.html } : htmlDaPeca(p) } : { ligadas, conta, versao, nada: true as const };
  exibida.set(p, nova);
  return "nada" in nova ? null : nova;
}

export function htmlDoFundo(fundo: FundoDoBanner, largura: number, altura: number) {
  let porTamanho = doFundo.get(fundo);
  if (!porTamanho) {
    porTamanho = new Map();
    doFundo.set(fundo, porTamanho);
  }
  const chave = `${largura}x${altura}`;
  let guardado = porTamanho.get(chave);
  if (!guardado) {
    guardado = { __html: camadaDoFundo(fundo, largura, altura) };
    porTamanho.set(chave, guardado);
  }
  return guardado;
}
