/*
 * Retícula Viva (nov/2026, do vídeo que o Natan mandou: o "degradê livre"
 * do Illustrator com a retícula colorida por cima). Pontos de cor soltos na
 * peça, cada um com o seu alcance (o círculo tracejado); as bolinhas crescem
 * perto de cada ponto e somem longe dele, na cor do ponto (perto de dois,
 * na mistura dos dois). O "papel" é o que aparece entre as bolinhas: a
 * própria cor da peça (sem papel) ou uma cor. Vale para qualquer peça
 * (texto, forma, foto, ícone, desenho): a retícula fica dentro do desenho
 * dela. Liga com um clique (um pronto) e se ajusta na própria peça
 * (arrastar os pontos, puxar o alcance, clicar para pôr outro).
 *
 * Tudo aqui é conta pura (sem documento.ts, para não dar volta de import):
 * os pontos ficam em fração da caixa da peça e o alcance em fração do lado
 * menor; o tamanho da célula, em px da arte.
 */

export type FormaDoPontoVivo = "pontos" | "quadrados" | "losangos" | "linhas";
export type MisturaDaReticulaViva = "normal" | "multiplicar" | "tela" | "sobrepor";
export type PontoVivo = { x: number; y: number; cor: string; alcance: number };
export type ReticulaViva = {
  pontos: PontoVivo[];
  /* O tamanho de cada célula da grade (px da arte). */
  tamanho: number;
  /* O giro da grade (graus). */
  angulo: number;
  forma: FormaDoPontoVivo;
  /* Entre as bolinhas: null = a própria cor da peça aparece. */
  papel: string | null;
  /* Como a retícula entra no desenho (na foto, "multiplicar" escurece só onde tem bolinha). */
  mistura?: MisturaDaReticulaViva;
};

export const LIMITE_DE_PONTOS_VIVOS = 8;

const hex = (c: unknown, padrao: string) => (typeof c === "string" && /^#[0-9a-f]{6}$/i.test(c) ? c.toUpperCase() : padrao);
const entre = (v: unknown, min: number, max: number, padrao: number) => {
  const x = Number(v);
  return Number.isFinite(x) ? Math.max(min, Math.min(max, x)) : padrao;
};
const n = (v: number) => Math.round(v * 10) / 10;

/** A retícula viva conferida (vinda do banco ou do assistente); null = sem. */
export function reticulaVivaLimpa(bruto: unknown): ReticulaViva | null {
  const r = bruto as Record<string, unknown> | null;
  if (!r || typeof r !== "object" || !Array.isArray(r.pontos)) return null;
  const pontos = (r.pontos as unknown[])
    .slice(0, LIMITE_DE_PONTOS_VIVOS)
    .map((p) => {
      const q = (p ?? {}) as Record<string, unknown>;
      return { x: entre(q.x, -0.5, 1.5, 0.5), y: entre(q.y, -0.5, 1.5, 0.5), cor: hex(q.cor, "#7C3AED"), alcance: entre(q.alcance, 0.05, 2, 0.5) };
    });
  if (!pontos.length) return null;
  const forma = r.forma === "quadrados" || r.forma === "losangos" || r.forma === "linhas" ? r.forma : "pontos";
  const mistura = r.mistura === "multiplicar" || r.mistura === "tela" || r.mistura === "sobrepor" ? r.mistura : undefined;
  return {
    pontos,
    tamanho: entre(r.tamanho, 2, 400, 18),
    angulo: entre(r.angulo, 0, 180, 45),
    forma,
    papel: r.papel === null || r.papel === undefined ? null : hex(r.papel, "#FFFFFF"),
    ...(mistura ? { mistura } : {}),
  };
}

/* A cor em números e de volta (a mistura das cores dos pontos). */
const rgb = (c: string) => [1, 3, 5].map((i) => parseInt(c.slice(i, i + 2), 16));
/* De 8 em 8 por canal: as bolinhas de cor parecida caem no mesmo grupo (o SVG fica bem menor). */
const comoHex = (v: number[]) => `#${v.map((x) => Math.max(0, Math.min(255, Math.round(x / 8) * 8)).toString(16).padStart(2, "0")).join("")}`;

/** O quanto o ponto pega em cada lugar: 1 em cima dele, some suave até o alcance. */
function forcaDoPonto(distancia: number, raio: number) {
  const t = distancia / Math.max(1e-6, raio);
  if (t >= 1) return 0;
  const s = 1 - t * t;
  return s * s;
}

/** Uma bolinha da retícula, já arredondada como no SVG: círculo (cx, cy, r) ou polígono (os cantos). */
type BolinhaViva = { cor: string; circulo?: [number, number, number]; cantos?: Array<[number, number]> };

/*
 * As bolinhas (ou quadrados, losangos, linhas) na grade girada; cada uma do
 * tamanho da força dos pontos ali e da cor misturada deles. A grade tem no
 * máximo ~110 células no lado maior (arte grande ganha célula maior, para não
 * pesar).
 */
function bolinhasDaReticulaViva(r: ReticulaViva, w: number, h: number): BolinhaViva[] {
  const largura = Math.max(1, w);
  const altura = Math.max(1, h);
  const menor = Math.min(largura, altura);
  const celula = Math.max(r.tamanho, Math.max(largura, altura) / 110, 3);
  const a = (r.angulo * Math.PI) / 180;
  const u = [Math.cos(a), Math.sin(a)];
  const v = [-Math.sin(a), Math.cos(a)];
  const meio = [largura / 2, altura / 2];
  const passos = Math.ceil(Math.hypot(largura, altura) / 2 / celula) + 1;
  const pontos = r.pontos.map((p) => ({ x: p.x * largura, y: p.y * altura, raio: Math.max(1, p.alcance * menor), cor: rgb(p.cor) }));
  const girado = (cx: number, cy: number, x: number, y: number): [number, number] => [n(cx + x * u[0] + y * v[0]), n(cy + x * u[1] + y * v[1])];
  const lista: BolinhaViva[] = [];
  for (let i = -passos; i <= passos; i++) {
    for (let j = -passos; j <= passos; j++) {
      const cx = meio[0] + (i * u[0] + j * v[0]) * celula;
      const cy = meio[1] + (i * u[1] + j * v[1]) * celula;
      if (cx < -celula || cy < -celula || cx > largura + celula || cy > altura + celula) continue;
      let soma = 0;
      const tinta = [0, 0, 0];
      for (const p of pontos) {
        const f = forcaDoPonto(Math.hypot(cx - p.x, cy - p.y), p.raio);
        if (!f) continue;
        soma += f;
        tinta[0] += p.cor[0] * f;
        tinta[1] += p.cor[1] * f;
        tinta[2] += p.cor[2] * f;
      }
      const cobre = Math.min(1, soma);
      if (cobre < 0.004) continue;
      const cor = comoHex(tinta.map((c) => c / soma));
      /* Com tudo coberto, as bolinhas se encostam e viram cor cheia (como no vídeo). */
      const k = Math.sqrt(cobre);
      if (r.forma === "pontos") lista.push({ cor, circulo: [n(cx), n(cy), n(celula * 0.72 * k)] });
      else if (r.forma === "linhas") {
        const meia = (celula * 1.02 * k) / 2;
        const comprida = celula * 0.52;
        const cantos: Array<[number, number]> = [
          [-comprida, -meia],
          [comprida, -meia],
          [comprida, meia],
          [-comprida, meia],
        ];
        lista.push({ cor, cantos: cantos.map(([x, y]) => girado(cx, cy, x, y)) });
      } else {
        const lado = (r.forma === "quadrados" ? celula * 0.52 : celula * 0.74) * k;
        const cantos: Array<[number, number]> = r.forma === "quadrados" ? [[-lado, -lado], [lado, -lado], [lado, lado], [-lado, lado]] : [[0, -lado], [lado, 0], [0, lado], [-lado, 0]];
        lista.push({ cor, cantos: cantos.map(([x, y]) => girado(cx, cy, x, y)) });
      }
    }
  }
  return lista;
}

/* Pela cor, na ordem em que cada cor aparece (onde duas cores se encostam, a de cima é sempre a mesma). */
function porCor(lista: BolinhaViva[]) {
  const mapa = new Map<string, BolinhaViva[]>();
  for (const b of lista) {
    const l = mapa.get(b.cor);
    if (l) l.push(b);
    else mapa.set(b.cor, [b]);
  }
  return mapa;
}
const papelDa = (r: ReticulaViva, largura: number, altura: number) => (r.papel ? `<rect width="${n(largura)}" height="${n(altura)}" fill="${r.papel}"/>` : "");

/**
 * A retícula em SVG (do tamanho da peça): o papel e as bolinhas, agrupadas
 * pela cor. É o fundo do TEXTO (background-image recortado pelas letras: o
 * Chrome desenha fundo SVG como vetor, nítido em qualquer zoom) e as amostras
 * do painel.
 */
export function svgDaReticulaViva(r: ReticulaViva, w: number, h: number): string {
  const largura = Math.max(1, w);
  const altura = Math.max(1, h);
  const grupos = [...porCor(bolinhasDaReticulaViva(r, w, h))]
    .map(([cor, bs]) => `<g fill="${cor}">${bs.map((b) => (b.circulo ? `<circle cx="${b.circulo[0]}" cy="${b.circulo[1]}" r="${b.circulo[2]}"/>` : `<polygon points="${b.cantos!.map(([x, y]) => `${x},${y}`).join(" ")}"/>`)).join("")}</g>`)
    .join("");
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${n(largura)}" height="${n(altura)}" viewBox="0 0 ${n(largura)} ${n(altura)}">${papelDa(r, largura, altura)}${grupos}</svg>`;
}

/* Número curto no caminho (12.0 -> 12, 0.5 -> .5, -0.5 -> -.5) e o par x,y (o sinal de menos já separa). */
const curto = (v: number) => String(n(v)).replace(/^(-?)0\./, "$1.");
const par = (x: number, y: number) => {
  const sy = curto(y);
  return `${curto(x)}${sy.startsWith("-") ? "" : ","}${sy}`;
};
/*
 * As bolinhas de uma cor num caminho só: o círculo em 2 arcos no mesmo
 * sentido (as que se encostam se juntam sem fresta), o polígono como
 * subcaminho; cada uma começa com "m" a partir do começo da anterior.
 */
function caminhoDasBolinhas(lista: BolinhaViva[]) {
  let d = "";
  let ax = 0;
  let ay = 0;
  lista.forEach((b, i) => {
    const [x0, y0] = b.circulo ? [n(b.circulo[0] - b.circulo[2]), b.circulo[1]] : b.cantos![0];
    d += i ? `m${par(x0 - ax, y0 - ay)}` : `M${par(x0, y0)}`;
    if (b.circulo) {
      const raio = curto(b.circulo[2]);
      const diametro = curto(2 * b.circulo[2]);
      d += `a${raio} ${raio} 0 1 0 ${diametro} 0a${raio} ${raio} 0 1 0 -${diametro} 0z`;
    } else {
      let px = x0;
      let py = y0;
      const lados = b.cantos!.slice(1).map(([x, y]) => {
        const passo = par(x - px, y - py);
        px = x;
        py = y;
        return passo;
      });
      d += `l${lados.join(" ")}z`;
    }
    ax = x0;
    ay = y0;
  });
  return d;
}

/*
 * A retícula como MIOLO de SVG (o papel e os caminhos), para ir dentro do
 * próprio filtro da peça (feImage apontando para ele): o Chrome desenha o
 * grupo como vetor na escala em que a peça é pintada (zoom do palco, Baixar
 * 2x/3x/4x). Com a imagem data: no feImage ele rasterizava a retícula no
 * tamanho da peça e esticava: bolinha pixelada no zoom (lab de 02/11 em
 * .cache/banner/qualidade/lab, nitidez 0,78-0,95 do vetor; com o miolo, 1,00).
 * Um caminho por cor EM CADA BLOCO de 256 px: um <circle> por bolinha dava
 * até 9 mil nós (arrastar um ponto ~100 ms por passo); um caminho só por cor
 * era percorrido inteiro em cada ladrilho (3x mais caro a 400%).
 */
const BLOCO_DA_RETICULA = 256;
const miolos = new WeakMap<ReticulaViva, Map<string, string>>();
export function mioloDaReticulaViva(r: ReticulaViva, w: number, h: number): string {
  const chave = `${Math.round(w)}x${Math.round(h)}`;
  let porTamanho = miolos.get(r);
  if (!porTamanho) {
    porTamanho = new Map();
    miolos.set(r, porTamanho);
  }
  const guardado = porTamanho.get(chave);
  if (guardado !== undefined) return guardado;
  const saida: string[] = [papelDa(r, Math.max(1, w), Math.max(1, h))];
  for (const [cor, bs] of porCor(bolinhasDaReticulaViva(r, w, h))) {
    const blocos = new Map<string, BolinhaViva[]>();
    for (const b of bs) {
      const [x, y] = b.circulo ?? b.cantos![0];
      const k = `${Math.floor(x / BLOCO_DA_RETICULA)},${Math.floor(y / BLOCO_DA_RETICULA)}`;
      const l = blocos.get(k);
      if (l) l.push(b);
      else blocos.set(k, [b]);
    }
    for (const l of blocos.values()) saida.push(`<path fill="${cor}" d="${caminhoDasBolinhas(l)}"/>`);
  }
  const miolo = saida.join("");
  porTamanho.set(chave, miolo);
  if (porTamanho.size > 6) porTamanho.delete(porTamanho.keys().next().value!);
  return miolo;
}

/* A retícula guardada pela própria retícula e pelo tamanho (mexer na peça sem mudar o tamanho não refaz as contas). */
const guardadas = new WeakMap<ReticulaViva, Map<string, string>>();
/** O SVG como endereço (data:) para o fundo do texto, as amostras do painel e a volta do Firefox (feImage sem elemento). */
export function enderecoDaReticulaViva(r: ReticulaViva, w: number, h: number) {
  const chave = `${Math.round(w)}x${Math.round(h)}`;
  let porTamanho = guardadas.get(r);
  if (!porTamanho) {
    porTamanho = new Map();
    guardadas.set(r, porTamanho);
  }
  let endereco = porTamanho.get(chave);
  if (!endereco) {
    endereco = `data:image/svg+xml,${encodeURIComponent(svgDaReticulaViva(r, w, h))}`;
    porTamanho.set(chave, endereco);
    if (porTamanho.size > 6) porTamanho.delete(porTamanho.keys().next().value!);
  }
  return endereco;
}

/** Os prontos (um clique liga): o tamanho da célula sai do lado menor da peça. */
export const PRONTOS_DA_RETICULA_VIVA: Array<{ chave: string; nome: string; papel: string | null; pontos: PontoVivo[]; forma?: FormaDoPontoVivo; angulo?: number; celula?: number; mistura?: MisturaDaReticulaViva }> = [
  {
    chave: "magenta-e-azul",
    nome: "Magenta e azul",
    papel: "#1405C8",
    pontos: [
      { x: 0.28, y: 0.74, cor: "#E40FD3", alcance: 0.55 },
      { x: 0.76, y: 0.22, cor: "#E40FD3", alcance: 0.55 },
    ],
  },
  { chave: "classico", nome: "Clássico", papel: "#FFFFFF", pontos: [{ x: 0.74, y: 0.42, cor: "#111111", alcance: 0.75 }] },
  {
    chave: "por-do-sol",
    nome: "Pôr do sol",
    papel: "#FFE8C7",
    pontos: [
      { x: 0.2, y: 0.8, cor: "#FF3D6E", alcance: 0.7 },
      { x: 0.82, y: 0.2, cor: "#FF8A00", alcance: 0.6 },
    ],
  },
  {
    chave: "neon",
    nome: "Neon",
    papel: "#0B0618",
    pontos: [
      { x: 0.25, y: 0.3, cor: "#00E5FF", alcance: 0.6 },
      { x: 0.75, y: 0.7, cor: "#FF2D87", alcance: 0.6 },
    ],
  },
  { chave: "na-cor-da-peca", nome: "Na cor da peça", papel: null, pontos: [{ x: 0.7, y: 0.35, cor: "#111111", alcance: 0.8 }] },
  { chave: "quadrados", nome: "Quadrados", papel: "#FFF7D6", forma: "quadrados", angulo: 0, pontos: [{ x: 0.3, y: 0.3, cor: "#7C3AED", alcance: 0.8 }] },
  { chave: "na-foto", nome: "Na foto", papel: null, mistura: "multiplicar", celula: 0.03, pontos: [{ x: 0.8, y: 0.25, cor: "#E40FD3", alcance: 0.7 }, { x: 0.2, y: 0.85, cor: "#1405C8", alcance: 0.6 }] },
];

/** O pronto montado para a peça (a célula proporcional ao lado menor dela). */
export function reticulaVivaPronta(chave: string, w: number, h: number): ReticulaViva {
  const pronto = PRONTOS_DA_RETICULA_VIVA.find((p) => p.chave === chave) ?? PRONTOS_DA_RETICULA_VIVA[0];
  return {
    pontos: pronto.pontos.map((p) => ({ ...p })),
    tamanho: Math.max(4, Math.round(Math.min(w, h) * (pronto.celula ?? 0.045))),
    angulo: pronto.angulo ?? 45,
    forma: pronto.forma ?? "pontos",
    papel: pronto.papel,
    ...(pronto.mistura ? { mistura: pronto.mistura } : {}),
  };
}
