/*
 * Partículas animadas (a textura que se mexe no vídeo): confete, neve,
 * brilhos, bolhas, corações, faíscas e fogos. Cada partícula nasce de um
 * sorteio fixo (a semente é o id da peça), então o mesmo instante dá sempre o
 * mesmo desenho: o palco toca, o MP4 grava e a foto parada (SVG, no servidor)
 * mostra o instante 1,2 s — tudo pela mesma conta, `particulasNoTempo`.
 *
 * Na peça de textura: `quantidade` = quantas, `tamanho` = o tamanho de cada
 * uma (px), `cor` = a cor principal (o confete e os fogos misturam com uma
 * paleta), `centroX/centroY` = de onde saem as faíscas, `velocidade` = 0,2 a 3.
 */

export const ESTILOS_DE_PARTICULAS = ["confete", "neve", "brilhos", "bolhas", "coracoes", "faiscas", "fogos"] as const;
export type EstiloDeParticulas = (typeof ESTILOS_DE_PARTICULAS)[number];
export const ehParticula = (estilo: string): estilo is EstiloDeParticulas => (ESTILOS_DE_PARTICULAS as readonly string[]).includes(estilo);

export type FormaDaParticula = "retangulo" | "circulo" | "triangulo" | "estrela" | "coracao" | "anel" | "risco";
/** Uma partícula no instante: centro (px da caixa), tamanho, giro (graus), cor, transparência e, no risco, o comprimento e o rumo. */
export type Particula = { x: number; y: number; r: number; giro: number; cor: string; alfa: number; forma: FormaDaParticula; comprimento?: number };

type Campos = { id: string; estilo: EstiloDeParticulas; cor: string; quantidade: number; tamanho: number; centroX: number; centroY: number; velocidade?: number; w: number; h: number };

const PALETA = ["#F43F5E", "#FBBF24", "#22C55E", "#3B82F6", "#A855F7", "#F97316", "#06B6D4"];

/* Sorteio fixo (mulberry32) a partir do id. */
function sorteio(semente: string) {
  let a = 2166136261;
  for (let i = 0; i < semente.length; i += 1) a = Math.imul(a ^ semente.charCodeAt(i), 16777619);
  return () => {
    a |= 0;
    a = (a + 0x6d2b79f5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
const frac = (v: number) => v - Math.floor(v);

/** As partículas da peça em `t` segundos (sempre as mesmas para o mesmo instante). */
export function particulasNoTempo(c: Campos, t: number): Particula[] {
  const n = Math.max(1, Math.min(400, Math.round(c.quantidade)));
  const tam = Math.max(2, c.tamanho || Math.min(c.w, c.h) * 0.025);
  const vel = Math.max(0.2, Math.min(3, c.velocidade ?? 1));
  const tt = t * vel;
  const sorte = sorteio(`${c.id}:${c.estilo}`);
  const lista: Particula[] = [];
  const cores = (i: number) => (c.estilo === "confete" || c.estilo === "fogos" ? (i % 3 === 0 ? c.cor : PALETA[i % PALETA.length]) : c.cor);

  if (c.estilo === "fogos") {
    /* Rajadas: cada uma explode num lugar e numa hora, com as faíscas abrindo e caindo. */
    const rajadas = Math.max(1, Math.round(n / 24));
    const porRajada = Math.max(8, Math.round(n / rajadas));
    for (let k = 0; k < rajadas; k += 1) {
      const [bx, by, fase, cor] = [0.15 + sorte() * 0.7, 0.12 + sorte() * 0.45, sorte(), cores(k)];
      const ciclo = 1.8;
      const vida = frac(tt / ciclo + fase);
      const raio = Math.min(c.w, c.h) * (0.18 + 0.1 * sorte());
      for (let i = 0; i < porRajada; i += 1) {
        const ang = (i / porRajada) * Math.PI * 2 + sorte() * 0.2;
        const abre = 1 - Math.pow(1 - vida, 3);
        const queda = vida * vida * raio * 0.35;
        lista.push({ x: bx * c.w + Math.cos(ang) * raio * abre, y: by * c.h + Math.sin(ang) * raio * abre + queda, r: tam * (1 - vida * 0.6), giro: (ang * 180) / Math.PI, cor, alfa: Math.max(0, 1 - vida * 1.1), forma: "risco", comprimento: tam * 3 * (1 - vida) });
      }
    }
    return lista;
  }

  for (let i = 0; i < n; i += 1) {
    const [a, b, fase, giro0, giroV, balanco] = [sorte(), sorte(), sorte(), sorte() * 360, (sorte() - 0.5) * 540, sorte()];
    const escala = 0.6 + sorte() * 0.8;
    switch (c.estilo) {
      case "confete": {
        const p = frac(tt / (2.4 + balanco * 1.6) + fase);
        const forma: FormaDaParticula = i % 3 === 0 ? "circulo" : i % 3 === 1 ? "retangulo" : "triangulo";
        lista.push({ x: a * c.w + Math.sin(tt * 2 + fase * 9) * tam * 2, y: -tam * 2 + p * (c.h + tam * 4), r: tam * escala, giro: giro0 + giroV * tt, cor: cores(i), alfa: 1, forma });
        break;
      }
      case "neve": {
        const p = frac(tt / (6 + balanco * 5) + fase);
        lista.push({ x: a * c.w + Math.sin(tt * 0.9 + fase * 7) * tam * 3, y: -tam + p * (c.h + tam * 2), r: tam * 0.5 * escala, giro: 0, cor: c.cor, alfa: 0.55 + 0.4 * b, forma: "circulo" });
        break;
      }
      case "brilhos": {
        const p = frac(tt / (1.2 + balanco * 1.4) + fase);
        const pisca = Math.sin(Math.PI * p);
        lista.push({ x: a * c.w, y: b * c.h, r: tam * escala * (0.3 + 0.9 * pisca), giro: giro0 + tt * 40, cor: c.cor, alfa: pisca, forma: "estrela" });
        break;
      }
      case "bolhas": {
        const p = frac(tt / (4 + balanco * 3) + fase);
        lista.push({ x: a * c.w + Math.sin(tt * 1.6 + fase * 9) * tam * 1.5, y: c.h + tam - p * (c.h + tam * 2), r: tam * escala, giro: 0, cor: c.cor, alfa: Math.min(1, (1 - p) * 3) * 0.85, forma: "anel" });
        break;
      }
      case "coracoes": {
        const p = frac(tt / (3.5 + balanco * 2) + fase);
        lista.push({ x: a * c.w + Math.sin(tt * 1.3 + fase * 9) * tam * 2, y: c.h + tam - p * (c.h + tam * 2), r: tam * escala * (0.8 + 0.2 * Math.sin(tt * 4 + fase * 6)), giro: Math.sin(tt * 2 + fase * 5) * 15, cor: c.cor, alfa: Math.min(1, (1 - p) * 2.5), forma: "coracao" });
        break;
      }
      case "faiscas": {
        /* Saem do centro em leque e caem com o peso. */
        const p = frac(tt / (0.9 + balanco * 0.8) + fase);
        const ang = -Math.PI / 2 + (a - 0.5) * Math.PI * 1.4;
        const forca = Math.min(c.w, c.h) * (0.25 + 0.35 * b);
        const [ox, oy] = [(c.centroX / 100) * c.w, (c.centroY / 100) * c.h];
        const vx = Math.cos(ang) * forca;
        const vy = Math.sin(ang) * forca;
        const [x, y] = [ox + vx * p, oy + vy * p + forca * 1.2 * p * p];
        lista.push({ x, y, r: tam * 0.4 * escala, giro: (Math.atan2(vy + forca * 2.4 * p, vx) * 180) / Math.PI, cor: c.cor, alfa: 1 - p, forma: "risco", comprimento: tam * 2.5 * (1 - p * 0.5) });
        break;
      }
    }
  }
  return lista;
}

/* ------------------------------------------------------------ desenhos */

const ESTRELA = (r: number) => {
  const pts: string[] = [];
  for (let i = 0; i < 8; i += 1) {
    const rr = i % 2 ? r * 0.28 : r;
    const a = (i / 8) * Math.PI * 2 - Math.PI / 2;
    pts.push(`${(Math.cos(a) * rr).toFixed(2)},${(Math.sin(a) * rr).toFixed(2)}`);
  }
  return pts.join(" ");
};
const CORACAO = (r: number) => `M0 ${(r * 0.9).toFixed(1)}C${(-r * 1.6).toFixed(1)} ${(-r * 0.1).toFixed(1)} ${(-r * 0.7).toFixed(1)} ${(-r * 1.2).toFixed(1)} 0 ${(-r * 0.45).toFixed(1)}C${(r * 0.7).toFixed(1)} ${(-r * 1.2).toFixed(1)} ${(r * 1.6).toFixed(1)} ${(-r * 0.1).toFixed(1)} 0 ${(r * 0.9).toFixed(1)}Z`;

/** A foto parada das partículas (SVG do tamanho da caixa), para o editor e o servidor. */
export function svgDasParticulas(lista: Particula[], w: number, h: number): string {
  const n = (v: number) => (Math.round(v * 10) / 10).toString();
  const corpo = lista
    .filter((p) => p.alfa > 0.02)
    .map((p) => {
      const t = `transform="translate(${n(p.x)} ${n(p.y)}) rotate(${n(p.giro)})" opacity="${n(p.alfa)}"`;
      switch (p.forma) {
        case "circulo":
          return `<circle ${t} r="${n(p.r)}" fill="${p.cor}"/>`;
        case "anel":
          return `<g ${t}><circle r="${n(p.r)}" fill="${p.cor}" fill-opacity="0.12" stroke="${p.cor}" stroke-width="${n(Math.max(1, p.r * 0.12))}"/><circle cx="${n(-p.r * 0.35)}" cy="${n(-p.r * 0.35)}" r="${n(p.r * 0.18)}" fill="#FFFFFF" fill-opacity="0.8"/></g>`;
        case "retangulo":
          return `<rect ${t} x="${n(-p.r * 0.45)}" y="${n(-p.r)}" width="${n(p.r * 0.9)}" height="${n(p.r * 2)}" rx="${n(p.r * 0.15)}" fill="${p.cor}"/>`;
        case "triangulo":
          return `<path ${t} d="M0 ${n(-p.r)}L${n(p.r)} ${n(p.r * 0.8)}L${n(-p.r)} ${n(p.r * 0.8)}Z" fill="${p.cor}"/>`;
        case "estrela":
          return `<polygon ${t} points="${ESTRELA(p.r)}" fill="${p.cor}"/>`;
        case "coracao":
          return `<path ${t} d="${CORACAO(p.r)}" fill="${p.cor}"/>`;
        case "risco":
          return `<line ${t} x1="${n(-(p.comprimento ?? p.r * 3) / 2)}" y1="0" x2="${n((p.comprimento ?? p.r * 3) / 2)}" y2="0" stroke="${p.cor}" stroke-width="${n(Math.max(1, p.r))}" stroke-linecap="round"/>`;
      }
    })
    .join("");
  return `<svg width="100%" height="100%" viewBox="0 0 ${n(w)} ${n(h)}" preserveAspectRatio="none" style="display:block;overflow:visible" aria-hidden="true">${corpo}</svg>`;
}

/** As partículas no canvas (no espaço da caixa, já posto pelo chamador). */
export function desenharParticulas(ctx: OffscreenCanvasRenderingContext2D | CanvasRenderingContext2D, lista: Particula[]) {
  for (const p of lista) {
    if (p.alfa <= 0.02) continue;
    ctx.save();
    ctx.globalAlpha *= Math.min(1, p.alfa);
    ctx.translate(p.x, p.y);
    ctx.rotate((p.giro * Math.PI) / 180);
    ctx.fillStyle = p.cor;
    ctx.strokeStyle = p.cor;
    ctx.beginPath();
    switch (p.forma) {
      case "circulo":
        ctx.arc(0, 0, p.r, 0, Math.PI * 2);
        ctx.fill();
        break;
      case "anel":
        ctx.arc(0, 0, p.r, 0, Math.PI * 2);
        ctx.globalAlpha *= 0.12;
        ctx.fill();
        ctx.globalAlpha /= 0.12;
        ctx.lineWidth = Math.max(1, p.r * 0.12);
        ctx.stroke();
        ctx.beginPath();
        ctx.fillStyle = "#FFFFFF";
        ctx.globalAlpha *= 0.8;
        ctx.arc(-p.r * 0.35, -p.r * 0.35, p.r * 0.18, 0, Math.PI * 2);
        ctx.fill();
        break;
      case "retangulo":
        ctx.roundRect(-p.r * 0.45, -p.r, p.r * 0.9, p.r * 2, p.r * 0.15);
        ctx.fill();
        break;
      case "triangulo":
        ctx.moveTo(0, -p.r);
        ctx.lineTo(p.r, p.r * 0.8);
        ctx.lineTo(-p.r, p.r * 0.8);
        ctx.closePath();
        ctx.fill();
        break;
      case "estrela":
        ESTRELA(p.r)
          .split(" ")
          .forEach((par, i) => {
            const [x, y] = par.split(",").map(Number);
            if (i) ctx.lineTo(x, y);
            else ctx.moveTo(x, y);
          });
        ctx.closePath();
        ctx.fill();
        break;
      case "coracao":
        ctx.fill(new Path2D(CORACAO(p.r)));
        break;
      case "risco": {
        const l = p.comprimento ?? p.r * 3;
        ctx.lineWidth = Math.max(1, p.r);
        ctx.lineCap = "round";
        ctx.moveTo(-l / 2, 0);
        ctx.lineTo(l / 2, 0);
        ctx.stroke();
        break;
      }
    }
    ctx.restore();
  }
}

/** O instante da foto parada (o editor, o PNG, a miniatura). */
export const INSTANTE_DA_FOTO = 1.2;
