"use client";

/*
 * Os pedidos da tela da sala para as rotas /api/live/<sala>/..., sempre com a
 * credencial de quem entrou ("id.token") no cabeçalho.
 */

export class ErroDaSala extends Error {
  readonly status: number;
  readonly motivo: string | null;
  readonly extra: Record<string, unknown>;
  constructor(mensagem: string, status: number, extra: Record<string, unknown> = {}) {
    super(mensagem);
    this.status = status;
    this.motivo = typeof extra.motivo === "string" ? extra.motivo : null;
    this.extra = extra;
  }
}

export class ApiDaSala {
  readonly salaId: string;
  credencial: string;
  /* Os avisos assinados que voltam nas respostas: a sala repassa pelo canal na hora. */
  private aoAvisos: ((avisos: Array<{ d: string; s: string }>) => void) | null = null;

  /* Quem vai repassar os avisos; devolve o "parar de ouvir". */
  ouvirAvisos(ouvinte: (avisos: Array<{ d: string; s: string }>) => void): () => void {
    this.aoAvisos = ouvinte;
    return () => {
      if (this.aoAvisos === ouvinte) this.aoAvisos = null;
    };
  }

  constructor(salaId: string, credencial: string) {
    this.salaId = salaId;
    this.credencial = credencial;
  }

  async pedir<T = Record<string, unknown>>(
    caminho: string,
    corpo?: unknown,
    opcoes: { metodo?: "GET" | "POST"; consulta?: Record<string, string>; binario?: Blob | ArrayBuffer; manter?: boolean; prazoMs?: number } = {},
  ): Promise<T> {
    const metodo = opcoes.metodo ?? "POST";
    const busca = opcoes.consulta ? `?${new URLSearchParams(opcoes.consulta).toString()}` : "";
    const resposta = await fetch(`/api/live/${this.salaId}/${caminho}${busca}`, {
      method: metodo,
      headers: {
        "x-live-quem": this.credencial,
        ...(opcoes.binario ? { "content-type": "application/octet-stream" } : metodo === "POST" ? { "content-type": "application/json" } : {}),
      },
      body: metodo === "GET" ? undefined : opcoes.binario ?? JSON.stringify(corpo ?? {}),
      cache: "no-store",
      keepalive: opcoes.manter,
      signal: AbortSignal.timeout(opcoes.prazoMs ?? 20000),
    });
    const json = (await resposta.json().catch(() => null)) as (Record<string, unknown> & { error?: string }) | null;
    if (!resposta.ok) throw new ErroDaSala(json?.error || "Não foi possível agora. Tente de novo.", resposta.status, json ?? {});
    if (json && Array.isArray(json.avisos) && json.avisos.length) this.aoAvisos?.(json.avisos as Array<{ d: string; s: string }>);
    return (json ?? {}) as T;
  }
}

/* Entrar na sala (antes de ter credencial). */
export async function entrarNaSala(salaId: string, corpo: Record<string, unknown>) {
  const resposta = await fetch(`/api/live/${salaId}/entrar`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(corpo),
    cache: "no-store",
  });
  const json = (await resposta.json().catch(() => null)) as (Record<string, unknown> & { error?: string }) | null;
  if (!resposta.ok) throw new ErroDaSala(json?.error || "Não foi possível entrar agora.", resposta.status, json ?? {});
  return { ...(json ?? {}), recebidaEm: Date.now() } as Record<string, unknown>;
}
