/*
  A lembrança da loja: o que as páginas leram do banco, guardado por dois
  minutos na memória do processo. Fica num arquivo sem dependência nenhuma
  para poder ser testado de verdade (src/lib/lembranca.test.ts); quem usa é
  lib/loja/dados-da-loja.ts.

  Mora na memória do processo, não no cache do Next. O `unstable_cache` serve
  o dado velho na primeira visita depois do aviso e só renova por trás: o
  lojista salvava, recarregava e via a loja antiga, e só na segunda recarga
  via a nova. Aqui o aviso apaga a lembrança e a próxima visita já vai ao
  banco.
*/

export const LEMBRANCA_DA_LOJA_S = 120;

type Lembranca = { valor: unknown; expiraEm: number };

/* No globalThis de propósito: o Next carrega este módulo duas vezes no mesmo
   processo (uma para as páginas, outra para as rotas de API). Sem isso, o
   aviso apagava a lembrança de uma cópia e as páginas liam a outra. */
const memoria = globalThis as unknown as {
  __lembrancasDaLoja?: Map<string, Lembranca>;
  __buscandoLoja?: Map<string, Promise<unknown>>;
  __avisosDaLoja?: Map<string, number>;
};
const lembrancas = (memoria.__lembrancasDaLoja ??= new Map<string, Lembranca>());
const buscando = (memoria.__buscandoLoja ??= new Map<string, Promise<unknown>>());
/* Quantos avisos cada loja já recebeu. Serve para uma busca que saiu ANTES do
   aviso não guardar, DEPOIS dele, o retrato velho que trouxe do banco. */
const avisos = (memoria.__avisosDaLoja ??= new Map<string, number>());
/* Teto de entradas em memória: acima disso a mais antiga sai. */
const LIMITE_DE_ENTRADAS = 2000;

/* A chave começa pelo slug: "aurora" ou "aurora/produto/…". O slug é sempre
   minúsculo no banco; o endereço que o visitante digitou pode não ser. */
function lojaDaChave(chave: string) {
  return String(chave || "").split("/")[0].trim().toLowerCase();
}

/**
 * Guarda o resultado de `buscar` por LEMBRANCA_DA_LOJA_S segundos sob uma
 * chave. A chave começa pelo slug da loja ("aurora" ou "aurora/produto/…"),
 * para o aviso do admin apagar tudo da loja de uma vez. Uma busca de cada
 * vez por chave: quem chega enquanto o banco responde espera a mesma
 * promessa. Nulo não fica guardado (o produto pode nascer daqui a pouco).
 */
export function lembrar<T>(chave: string, buscar: () => Promise<T>): Promise<T> {
  const agora = Date.now();
  const guardada = lembrancas.get(chave);
  if (guardada && guardada.expiraEm > agora) return Promise.resolve(guardada.valor as T);

  const emAndamento = buscando.get(chave);
  if (emAndamento) return emAndamento as Promise<T>;

  const loja = lojaDaChave(chave);
  const avisosNaPartida = avisos.get(loja) ?? 0;
  const promessa: Promise<T> = buscar()
    .then((valor) => {
      /* A loja foi avisada enquanto o banco respondia: o que chegou pode ser
         de antes da mudança. Vai para quem pediu, mas não fica guardado —
         senão o retrato velho voltava para a memória por mais dois minutos. */
      const mudouNoCaminho = (avisos.get(loja) ?? 0) !== avisosNaPartida;
      if (!mudouNoCaminho && valor !== null && valor !== undefined) {
        if (lembrancas.size >= LIMITE_DE_ENTRADAS) {
          const maisAntiga = lembrancas.keys().next().value;
          if (maisAntiga !== undefined) lembrancas.delete(maisAntiga);
        }
        lembrancas.delete(chave);
        lembrancas.set(chave, { valor, expiraEm: Date.now() + LEMBRANCA_DA_LOJA_S * 1000 });
      }
      return valor;
    })
    .finally(() => {
      if (buscando.get(chave) === promessa) buscando.delete(chave);
    });
  buscando.set(chave, promessa);
  return promessa;
}

/**
 * Some com tudo que é da loja (vitrine e produtos): a próxima visita vai ao
 * banco. As buscas em andamento também saem da fila, para quem chegar depois
 * do aviso não pegar carona numa resposta que saiu antes dele.
 */
export function esquecerLoja(slug: string) {
  const loja = lojaDaChave(slug);
  if (!loja) return;
  avisos.set(loja, (avisos.get(loja) ?? 0) + 1);
  for (const mapa of [lembrancas, buscando] as Array<Map<string, unknown>>) {
    for (const chave of Array.from(mapa.keys())) {
      if (lojaDaChave(chave) === loja) mapa.delete(chave);
    }
  }
}
