/*
 * As silhuetas em curvas já calculadas (nov/2026). O contorno arredondado da
 * letra, o contorno duplo, o "Contorno por fora" e o contorno de adesivo da
 * foto saem, no editor, como um traço SVG em volta da silhueta da peça
 * (vetor de verdade: nítido em qualquer zoom e quase sem custo). A silhueta
 * é calculada no navegador (silhueta.ts: o desenho da peça numa tela, a
 * borda achada e virada curva) e guardada aqui pela chave do desenho.
 *
 * Este arquivo é só o registro (sem DOM, sem import): documento.ts consulta
 * e, enquanto a silhueta não chega, desenha o engorde (engorde.ts). Quem
 * calcula se registra (o editor); no servidor não há quem calcule e vale
 * sempre o engorde exato.
 */

/* `d`: os contornos (fora e furos, em sentidos opostos: regra nonzero); `dSemFuros`: só os de fora (os furos tapados). Na caixa da peça (px). */
export type Silhueta = { d: string; dSemFuros: string };
/* `dono`: a peça (um pedido por peça: o novo troca o que ainda não começou). */
export type PedidoDeSilhueta = { dono: string; chave: string; html: string; w: number; h: number; fontes: string[] };

type Calculo = (pedido: PedidoDeSilhueta) => Promise<Silhueta | null>;

const prontas = new Map<string, Silhueta | null>();
const fila = new Map<string, PedidoDeSilhueta>();
const emCurso = new Set<string>();
const ouvintes = new Set<() => void>();
let calcular: Calculo | null = null;
let versao = 0;
let rodando = false;
const LIMITE = 400;

/** Muda a cada silhueta nova (o desenho guardado das peças confere). */
export const versaoDasSilhuetas = () => versao;

/** Avisa quando chega silhueta nova (o editor redesenha). */
export function aoChegarSilhueta(f: () => void) {
  ouvintes.add(f);
  return () => {
    ouvintes.delete(f);
  };
}

/** O editor registra quem calcula (silhueta.ts). */
export function registrarCalculoDaSilhueta(f: Calculo) {
  calcular = f;
}

/** A chave do desenho (o HTML e a caixa): FNV-1a de 32 bits em duas voltas. */
export function chaveDaSilhueta(html: string, w: number, h: number) {
  const texto = `${Math.round(w * 10)}x${Math.round(h * 10)}|${html}`;
  let a = 0x811c9dc5;
  let b = 0x01000193;
  for (let i = 0; i < texto.length; i++) {
    const c = texto.charCodeAt(i);
    a = Math.imul(a ^ c, 0x01000193);
    b = Math.imul(b ^ c, 0x5bd1e995);
  }
  return `${(a >>> 0).toString(36)}${(b >>> 0).toString(36)}${texto.length.toString(36)}`;
}

/*
 * A silhueta do desenho: pronta, "esperando" (o editor está calculando; o
 * desenho usa o engorde rápido) ou "sem" (ninguém calcula aqui, ou deu
 * errado: o engorde exato).
 */
export function silhuetaDe(pedido: PedidoDeSilhueta): Silhueta | "esperando" | "sem" {
  if (prontas.has(pedido.chave)) return prontas.get(pedido.chave) ?? "sem";
  if (!calcular) return "sem";
  if (!emCurso.has(pedido.chave)) {
    fila.set(pedido.dono, pedido);
    agendar();
  }
  return "esperando";
}

/** A silhueta para quem precisa dela agora (separar o contorno): espera o cálculo. */
export async function silhuetaAgora(pedido: PedidoDeSilhueta): Promise<Silhueta | null> {
  if (prontas.has(pedido.chave)) return prontas.get(pedido.chave) ?? null;
  if (!calcular) return null;
  const s = await calcular(pedido).catch(() => null);
  guardar(pedido.chave, s);
  return s;
}

function guardar(chave: string, s: Silhueta | null) {
  prontas.set(chave, s);
  if (prontas.size > LIMITE) prontas.delete(prontas.keys().next().value!);
  versao += 1;
  ouvintes.forEach((f) => f());
}

/* Um de cada vez, depois que a tela respira (vários pedidos seguidos da mesma peça viram um). */
function agendar() {
  if (rodando) return;
  rodando = true;
  setTimeout(async () => {
    while (fila.size && calcular) {
      const [dono, pedido] = fila.entries().next().value!;
      fila.delete(dono);
      if (prontas.has(pedido.chave)) continue;
      emCurso.add(pedido.chave);
      const s = await calcular(pedido).catch((erro) => {
        /* Sem a silhueta, a peça fica com o engorde (o mesmo desenho, mais pesado): o aviso ajuda a achar a causa. */
        console.warn("silhueta em curvas:", erro);
        return null;
      });
      emCurso.delete(pedido.chave);
      guardar(pedido.chave, s);
    }
    rodando = false;
  }, 60);
}

/* Para os testes do editor (o estado do registro no console). */
if (typeof window !== "undefined") (window as unknown as { __silhuetas?: unknown }).__silhuetas = { prontas, fila, emCurso, calculando: () => Boolean(calcular), versao: () => versao };
