/*
 * Os avisos que o servidor manda para a sala (assinados em `sinal.ts`) e a
 * leitura deles no navegador. Arquivo sem nada de servidor: os dois lados usam.
 */

export type TipoDeTrilha = "cam" | "mic" | "tela" | "tela-audio";
export const TIPOS_DE_TRILHA: readonly TipoDeTrilha[] = ["cam", "mic", "tela", "tela-audio"];

export type PapelNaSala = "apresentador" | "convidado" | "aluno" | "visitante";

export type PessoaNoPalco = {
  id: string;
  nome: string;
  papel: PapelNaSala;
  foto: string | null;
  /* As trilhas que esta pessoa está mandando agora (tipo → nome da trilha no SFU;
     nome novo = trilha nova, a tela puxa de novo). */
  trilhas: Partial<Record<TipoDeTrilha, string>>;
};

export type Oferta = {
  id: string;
  tipo: "cupom" | "produto" | "link";
  titulo: string;
  detalhe: string | null;
  codigo: string | null;
  imagem: string | null;
  preco: number | null;
  precoDe: number | null;
  /* Para onde o botão leva (página do produto, link). */
  destino: string | null;
  /* Quando a oferta sai sozinha da tela (ISO). */
  ate: string | null;
};

export type Fixado = { texto: string; link: string | null; rotuloDoLink: string | null };

export type MaoLevantada = { id: string; nome: string; em: string };

export type EstadoPublico = {
  /* Cresce a cada mudança: a tela ignora o que chegar fora de ordem. */
  v: number;
  status: "agendada" | "ao_vivo" | "encerrada" | "cancelada";
  iniciadaEm: string | null;
  encerradaEm: string | null;
  palco: PessoaNoPalco[];
  /* Quem pode falar agora (além de quem apresenta). */
  falando: string[];
  maos: MaoLevantada[];
  oferta: Oferta | null;
  /* Liveshop: o produto que está sendo mostrado. */
  foco: Oferta | null;
  fixado: Fixado | null;
  gravando: boolean;
  pessoas: number;
  chat: "aberto" | "perguntas" | "desligado";
};

export type MensagemDaSala = {
  id: string;
  autor: string;
  nome: string;
  papel: PapelNaSala;
  foto: string | null;
  texto: string;
  pergunta: boolean;
  em: string;
  /* Só na tela de quem mandou, enquanto a mensagem não volta do servidor. */
  pendente?: boolean;
};

export type EventoDaSala =
  | { t: "estado"; estado: EstadoPublico }
  | { t: "msg"; msg: MensagemDaSala }
  | { t: "msg-fora"; id: string }
  | { t: "fala"; participante: string; pode: boolean }
  | { t: "fora"; participante: string };

export type EventoRecebido = EventoDaSala & { ts: number; sala: string };

/* O texto que vai assinado: o evento mais a hora do servidor. */
export function montarEvento(evento: EventoDaSala & { sala?: string }, agora = Date.now()): string {
  return JSON.stringify({ ...evento, ts: agora });
}

/*
 * Lê um aviso que já teve a assinatura conferida. Recusa o que é de outra
 * sala e o que é velho demais (alguém repetindo um aviso antigo gravado).
 */
export function lerEvento(texto: string, sala: string, horaDoServidor: number, toleranciaMs = 3 * 60 * 1000): EventoRecebido | null {
  let bruto: unknown;
  try {
    bruto = JSON.parse(texto);
  } catch {
    return null;
  }
  if (!bruto || typeof bruto !== "object") return null;
  const e = bruto as Record<string, unknown>;
  if (e.sala !== sala) return null;
  const ts = Number(e.ts);
  if (!Number.isFinite(ts) || Math.abs(horaDoServidor - ts) > toleranciaMs) return null;
  if (!["estado", "msg", "msg-fora", "fala", "fora"].includes(String(e.t))) return null;
  return e as unknown as EventoRecebido;
}
