/*
  "A loja mudou": o aviso que sai sozinho quando o admin grava, direto do
  navegador, qualquer coisa que a vitrine mostra.

  A loja virtual guarda o retrato de cada vitrine por dois minutos
  (apps/public/src/lib/loja/dados-da-loja.ts). As rotas de produto já avisavam
  a loja para esquecer o retrato, mas quase todo o resto do catálogo (tema,
  cores, banners, seções, stories, categorias, cupons) é gravado pelo
  navegador, sem passar por rota nenhuma. O lojista trocava o tema, abria a
  loja, via o antigo e concluía que não tinha salvado.

  Em vez de lembrar de avisar em cada botão de cada tela (e esquecer na
  próxima tela nova), o aviso mora no único lugar por onde toda gravação do
  navegador passa: o `fetch` do cliente do Supabase (lib/supabase.ts). Gravou
  numa tabela da vitrine e deu certo, a loja é avisada. Várias gravações em
  sequência (reordenar vinte banners) viram um aviso só.

  O aviso em si é um POST sem corpo para /api/loja/avisar, que descobre a loja
  de quem está logado e assina o pedido no servidor.
*/

/* As tabelas que a loja lê para montar vitrine e página de produto. */
export const TABELAS_DA_VITRINE: ReadonlySet<string> = new Set([
  "profiles",
  "products",
  "product_attributes",
  "product_price_tiers",
  "product_images",
  "product_option_stocks",
  "product_combos",
  "product_combo_items",
  "product_optional_services",
  "product_reviews",
  "optional_services",
  "service_groups",
  "categories",
  "catalog_sections",
  "catalog_section_products",
  "catalog_banners",
  "catalog_coupons",
  "catalog_story_groups",
  "catalog_story_items",
  /* O cabeçalho, o menu, o rodapé e as páginas extras (migration 197). */
  "catalog_navegacao",
  "catalog_paginas",
]);

const METODOS_QUE_GRAVAM: ReadonlySet<string> = new Set(["POST", "PATCH", "PUT", "DELETE"]);

/**
 * A tabela da vitrine que este pedido gravou, ou null. Leitura, login, envio
 * de arquivo e função do banco (/rest/v1/rpc/...) não contam.
 */
export function tabelaDaVitrineGravada(url: string, metodo: string | null | undefined): string | null {
  if (!METODOS_QUE_GRAVAM.has(String(metodo || "GET").toUpperCase())) return null;
  const tabela = /\/rest\/v1\/([a-z0-9_]+)(?:[?#]|$)/i.exec(String(url || ""))?.[1]?.toLowerCase();
  return tabela && TABELAS_DA_VITRINE.has(tabela) ? tabela : null;
}

/* Espera curta depois da última gravação: o aviso tem de chegar DEPOIS dela,
   e uma sequência de gravações vira um aviso só. */
const ESPERA_MS = 250;

let relogio: ReturnType<typeof setTimeout> | null = null;
let escutandoSaida = false;

function pedirAviso(): Promise<boolean> {
  /* keepalive: o aviso sobrevive a quem salva e já sai da página. */
  return fetch("/api/loja/avisar", { method: "POST", keepalive: true, cache: "no-store" })
    .then((resposta) => (resposta.ok ? resposta.json() : null))
    .then((corpo: { loja?: unknown } | null) => corpo?.loja === true)
    .catch(() => false);
}

/** Avisa a loja daqui a pouco; chamadas em sequência viram uma só. */
export function avisarALojaLogo() {
  if (typeof window === "undefined") return;
  if (relogio) clearTimeout(relogio);
  relogio = setTimeout(() => {
    relogio = null;
    void pedirAviso();
  }, ESPERA_MS);

  if (escutandoSaida) return;
  escutandoSaida = true;
  /* Fechou a aba com aviso na fila: manda já, em vez de perder. */
  window.addEventListener("pagehide", () => {
    if (!relogio) return;
    clearTimeout(relogio);
    relogio = null;
    void pedirAviso();
  });
}

/**
 * Avisa a loja agora e espera a resposta. Para a tela que quer dizer "salvo"
 * só depois de a loja ter ouvido. `false` = a loja não respondeu a tempo (a
 * mudança aparece sozinha em até dois minutos); nunca lança, e nunca segura
 * a tela por mais que `limiteMs`: passado o prazo o aviso segue por trás.
 */
export function avisarALojaAgora(limiteMs = 3000): Promise<boolean> {
  if (typeof window === "undefined") return Promise.resolve(false);
  if (relogio) {
    clearTimeout(relogio);
    relogio = null;
  }
  return Promise.race([
    pedirAviso(),
    new Promise<boolean>((resolve) => setTimeout(() => resolve(false), limiteMs)),
  ]);
}

/**
 * O `fetch` do cliente do Supabase no navegador. Devolve a resposta intacta e
 * sem atraso; só olha o endereço e o método depois que a gravação deu certo.
 * Nada aqui pode derrubar um pedido ao banco, por isso o try/catch em volta.
 */
export const fetchQueAvisaALoja: typeof fetch = async (input, init) => {
  const resposta = await fetch(input, init);
  try {
    if (resposta.ok && typeof window !== "undefined") {
      const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
      const metodo = init?.method ?? (typeof input === "object" && "method" in input ? input.method : "GET");
      if (tabelaDaVitrineGravada(url, metodo)) avisarALojaLogo();
    }
  } catch {
    /* o aviso é cortesia; a gravação já aconteceu */
  }
  return resposta;
};
