/*
 * O "Redimensionar" inteligente do editor de artes (como o Magic Resize do
 * Canva): a arte vai para outro tamanho ou proporção e se REORGANIZA, não só
 * encolhe.
 *
 * 1. Arte que nasceu de um modelo pronto: o modelo é montado de novo no
 *    tamanho novo (ele sabe se arrumar em faixa, quadrado e story) e tudo o
 *    que a pessoa mudou vem junto: textos, trechos formatados, cores, fontes,
 *    foto, fundo, o que escondeu e o que apagou. Peças que ela pôs por conta
 *    própria entram pelo passo 2.
 * 2. Arte livre: a composição é lida por partes.
 *    - fundo (o que cobre a arte) estica até a borda nova;
 *    - faixas (de ponta a ponta) continuam de ponta a ponta, presas na borda
 *      de onde estavam, e o texto repetido delas se completa;
 *    - decorações grandes acompanham o meio;
 *    - o conteúdo vira blocos (a foto com o brilho e o anel atrás dela é um
 *      bloco só; botão com o texto em cima também). Ficando em pé, as colunas
 *      viram uma pilha centrada; ficando deitada, o texto vai para a esquerda
 *      e a foto para a direita; na mesma proporção, o conjunto cresce até
 *      ocupar o espaço novo com a mesma margem.
 * Depois, no editor, o texto que passar da caixa diminui até caber
 * (encaixarTextos, que mede no próprio palco).
 */
import { escalarPeca, novoId, paginasDoDocumento, type DocumentoDoBanner, type Peca, type PecaDeTexto } from "./documento";
import { MODELOS_DO_EDITOR, type ModeloDoEditor } from "./modelos";
import { cobre, completarRepeticao, ehFaixa, intersecao, longeDoConteudo, noMesmoLugar, reorganizarPecas } from "./reorganizar";

/* ================================================== arte que veio de modelo */

const SO_LUGAR = new Set(["id", "tipo", "nome", "x", "y", "w", "h", "rot"]);

/*
 * Leva para a peça nova (no lugar que o modelo deu) só o que a PESSOA mudou:
 * a peça de agora comparada com a mesma peça do modelo no tamanho de antes.
 * O que ela não mexeu fica como o modelo faz no tamanho novo (inclusive as
 * quebras de linha que mudam de um formato para outro). O que ela mexeu vem
 * na escala nova (letra maior que o modelo continua maior na mesma medida).
 */
/* A letra como era antes de o editor encolher sozinho (o encaixe não conta como mudança da pessoa). */
function semEncaixe(p: Peca): Peca {
  if (p.tipo !== "texto" || !p.encaixe || p.encaixe >= 1) return p;
  const f = 1 / p.encaixe;
  return {
    ...p,
    tamanho: p.tamanho * f,
    html: p.html ? p.html.replace(/font-size:([\d.]+)px/g, (_, v) => `font-size:${Math.round(Number(v) * f * 10) / 10}px`) : p.html,
    encaixe: undefined,
  };
}

function vestirPelaDiferenca(nova: Peca, antigaBruta: Peca, original: Peca): Peca {
  const antiga = semEncaixe(antigaBruta);
  if (nova.tipo !== antiga.tipo || original.tipo !== antiga.tipo) return vestir(nova, antiga);
  const f =
    nova.tipo === "texto" && original.tipo === "texto"
      ? nova.tamanho / Math.max(1, original.tamanho)
      : Math.min(nova.w / Math.max(1, original.w), nova.h / Math.max(1, original.h));
  const escalada = escalarPeca(antiga, f) as unknown as Record<string, unknown>;
  const a = antiga as unknown as Record<string, unknown>;
  const o = original as unknown as Record<string, unknown>;
  const saida: Record<string, unknown> = { ...(nova as unknown as Record<string, unknown>), id: antiga.id };
  const mudou = (k: string) => JSON.stringify(a[k] ?? null) !== JSON.stringify(o[k] ?? null);
  for (const k of Object.keys(a)) {
    if (SO_LUGAR.has(k) || k === "texto" || k === "html" || k === "encaixe") continue;
    if (mudou(k)) saida[k] = escalada[k];
  }
  /* Texto e trechos andam juntos. */
  if (nova.tipo === "texto" && (mudou("texto") || mudou("html"))) {
    saida.texto = escalada.texto;
    saida.html = escalada.html ?? null;
    if (nova.nome === "Faixa") return completarRepeticao(saida as unknown as PecaDeTexto);
  }
  return saida as unknown as Peca;
}

/* Copia da peça antiga para a nova tudo o que é jeito (texto, cor, fonte...) e fica com o lugar da nova. */
function vestir(nova: Peca, antiga: Peca): Peca {
  if (nova.tipo !== antiga.tipo) return nova;
  const lugar = { x: nova.x, y: nova.y, w: nova.w, h: nova.h, rot: nova.rot };
  if (nova.tipo === "texto" && antiga.tipo === "texto") {
    const f = nova.tamanho / Math.max(1, antiga.tamanho);
    const estilo = escalarPeca(antiga, f);
    const vestida: PecaDeTexto = { ...estilo, ...lugar, id: antiga.id, tamanho: nova.tamanho, alinhamento: nova.alinhamento, vertical: nova.vertical };
    /* Faixa com texto repetido: o texto da pessoa, repetido até a largura nova. */
    return nova.nome === "Faixa" ? completarRepeticao(vestida) : vestida;
  }
  const f = Math.min(nova.w / Math.max(1, antiga.w), nova.h / Math.max(1, antiga.h));
  return { ...escalarPeca(antiga, f), ...lugar, id: antiga.id } as Peca;
}

const chaveDaPeca = (p: Peca) => `${p.tipo}:${p.nome || ""}`;

function porChave(pecas: Peca[]) {
  const mapa = new Map<string, Peca[]>();
  for (const p of pecas) mapa.set(chaveDaPeca(p), [...(mapa.get(chaveDaPeca(p)) || []), p]);
  return mapa;
}

function remontar(doc: DocumentoDoBanner, modelo: ModeloDoEditor, W2: number, H2: number): DocumentoDoBanner {
  const origem = doc.modelo!;
  /* A foto e o logo de agora (a pessoa pode ter trocado ou tirado). */
  const foto = doc.pecas.find((p) => p.tipo === "imagem" && p.nome === "Foto");
  const logo = doc.pecas.find((p) => p.tipo === "imagem" && p.papel === "logo");
  const pedido = { cor: origem.cor, proporcaoDoLogo: origem.proporcaoDoLogo, logo: logo && logo.tipo === "imagem" ? logo.src : null, foto: foto && foto.tipo === "imagem" ? foto.src : null };
  /* Num carrossel, a folha desta página (as outras ficam onde estão). */
  const folha = (d: DocumentoDoBanner) => {
    const folhas = paginasDoDocumento(d);
    return folhas[Math.min(origem.pagina ?? 0, folhas.length - 1)];
  };
  const nova = folha(modelo.montar({ ...pedido, largura: W2, altura: H2 }));
  /* O modelo no tamanho de antes diz o que a pessoa apagou (o que o modelo tinha e a arte não tem) e o que ela mudou. */
  const doTamanhoDeAntes = porChave(folha(modelo.montar({ ...pedido, largura: doc.largura, altura: doc.altura })).pecas);
  const antigas = porChave(doc.pecas);
  const usadas = new Set<string>();
  const mudadasSemLugar: Peca[] = [];
  const vez = new Map<string, number>();
  const pecas: Peca[] = [];
  for (const p of nova.pecas) {
    const chave = chaveDaPeca(p);
    const lista = antigas.get(chave);
    if (!lista) {
      /* O modelo também tinha essa peça e a pessoa tirou: continua fora. */
      if (doTamanhoDeAntes.has(chave)) continue;
      pecas.push(p);
      continue;
    }
    const i = vez.get(chave) ?? 0;
    vez.set(chave, i + 1);
    const antiga = lista[Math.min(i, lista.length - 1)];
    const originais = doTamanhoDeAntes.get(chave);
    const original = originais?.[Math.min(i, originais.length - 1)];
    const vestida = original ? vestirPelaDiferenca(p, antiga, original) : vestir(p, antiga);
    if (usadas.has(antiga.id)) vestida.id = novoId(p.tipo[0]);
    usadas.add(antiga.id);
    pecas.push(vestida);
  }
  /* Peça que a pessoa mudou e que o modelo não usa neste formato (o detalhe do rodapé no banner largo): vem como solta, para não perder o texto. */
  const chavesNovas = new Set(nova.pecas.map(chaveDaPeca));
  for (const [chave, lista] of antigas) {
    if (chavesNovas.has(chave) || !doTamanhoDeAntes.has(chave)) continue;
    const originais = doTamanhoDeAntes.get(chave)!;
    lista.forEach((antiga, i) => {
      const original = originais[Math.min(i, originais.length - 1)];
      if (antiga.tipo === "texto" && original.tipo === "texto" && (antiga.texto !== original.texto || (antiga.html || null) !== (original.html || null))) {
        usadas.delete(antiga.id);
        mudadasSemLugar.push(antiga);
      }
    });
  }

  /* O que a pessoa pôs por conta própria vem por cima, no mesmo lugar relativo (o modelo já arrumou o resto em volta). */
  const proprias = [...mudadasSemLugar, ...doc.pecas.filter((p) => !usadas.has(p.id) && !(doTamanhoDeAntes.has(chaveDaPeca(p)) && antigas.has(chaveDaPeca(p))))];
  const ocupado = pecas.filter((p) => !cobre(p, W2, H2) && !ehFaixa(p, W2, H2) && p.opacidade >= 0.35 && (p.tipo === "imagem" || p.tipo === "texto" || p.tipo === "icone"));
  const ultimoTexto = [...ocupado].filter((p) => p.tipo === "texto").sort((a, b) => b.y + b.h - (a.y + a.h))[0];
  const margem = Math.min(W2, H2) * 0.04;
  const soltas = proprias.map((p) => {
    const solta = noMesmoLugar(p, doc.largura, doc.altura, W2, H2);
    if (mudadasSemLugar.includes(p) && ultimoTexto && solta.tipo === "texto") {
      const y = ultimoTexto.y + ultimoTexto.h + margem;
      const naColuna = { ...solta, x: ultimoTexto.x, y, w: Math.max(ultimoTexto.w, solta.w), alinhamento: ultimoTexto.tipo === "texto" ? ultimoTexto.alinhamento : solta.alinhamento } as Peca;
      if (y + solta.h <= H2 - margem && !ocupado.some((o) => intersecao(naColuna, o) > 0)) return naColuna;
    }
    return longeDoConteudo(solta, ocupado, W2, H2);
  });
  return { ...nova, pecas: [...pecas, ...soltas], modelo: { ...origem, logo: pedido.logo, foto: pedido.foto }, numeracao: doc.numeracao ?? nova.numeracao };
}

/*
 * Arte feita por um modelo antes de a origem ser guardada (out/2026): o
 * modelo é reconhecido pelas peças com nome (Título, Chamada, Faixa...). Só
 * vale quando quase todas batem com um modelo só.
 */
function adivinharOrigem(doc: DocumentoDoBanner, corDaLoja: string): DocumentoDoBanner["modelo"] {
  const nomes = new Set(doc.pecas.filter((p) => p.nome && p.nome !== "Fundo" && p.nome !== "Textura" && p.nome !== "Logo").map(chaveDaPeca));
  if (nomes.size < 3) return null;
  const foto = doc.pecas.find((p) => p.tipo === "imagem" && p.nome === "Foto");
  let melhor: { chave: string; nota: number } | null = null;
  for (const m of MODELOS_DO_EDITOR) {
    const doModelo = new Set(
      m
        .montar({ largura: doc.largura, altura: doc.altura, cor: corDaLoja, logo: null, foto: foto && foto.tipo === "imagem" ? foto.src : null })
        .pecas.filter((p) => p.nome && p.nome !== "Fundo" && p.nome !== "Textura")
        .map(chaveDaPeca),
    );
    if (!doModelo.size) continue;
    const comuns = [...nomes].filter((n) => doModelo.has(n)).length;
    const nota = comuns / new Set([...nomes, ...doModelo]).size;
    if (!melhor || nota > melhor.nota) melhor = { chave: m.chave, nota };
  }
  if (!melhor || melhor.nota < 0.6) return null;
  const logo = doc.pecas.find((p) => p.tipo === "imagem" && p.papel === "logo");
  return { chave: melhor.chave, cor: corDaLoja, logo: logo && logo.tipo === "imagem" ? logo.src : null, proporcaoDoLogo: logo ? logo.w / Math.max(1, logo.h) : null, foto: foto && foto.tipo === "imagem" ? foto.src : null };
}

/** A arte em outro tamanho, reorganizada (modelo remontado ou composição relida). */
export function redimensionarComInteligencia(doc: DocumentoDoBanner, largura: number, altura: number, corDaLoja = "#6D28D9"): DocumentoDoBanner {
  if (largura === doc.largura && altura === doc.altura) return doc;
  const origem = doc.modelo ?? adivinharOrigem(doc, corDaLoja);
  const modelo = origem ? MODELOS_DO_EDITOR.find((m) => m.chave === origem.chave) : null;
  if (origem && modelo) return remontar({ ...doc, modelo: origem }, modelo, largura, altura);
  return { ...doc, largura, altura, pecas: reorganizarPecas(doc.pecas, doc.largura, doc.altura, largura, altura) };
}
