import type { TipoDeTrilha } from "@/lib/live/evento";
import { TIPOS_DE_TRILHA } from "@/lib/live/evento";
import { ehDaEquipe, nomeDaTrilha, podePublicar } from "@/lib/live/regras";
import { lerCorpo, rotaDeQuemEsta } from "@/lib/live/servidor/rota";
import { cortarTrilhas, ErroDaLive, pessoasOnline, sessaoDoSfu, publicarEstado } from "@/lib/live/servidor/sala";
import { configuracaoDoSfu, fecharTrilhas, novaSessao, novasTrilhas } from "@/lib/live/sfu";

export const runtime = "nodejs";

/*
 * Mandar câmera, microfone ou tela. A tela manda a oferta WebRTC e diz o que
 * vai em cada "mid"; o servidor confere se esta pessoa pode mandar aquilo e só
 * então repassa ao SFU.
 *
 * Uma tela por vez, como no Meet: quem começa a apresentar assume, e a tela
 * de quem apresentava antes é fechada (a tela dele percebe e para sozinha).
 * Na reunião, ninguém manda nada antes de quem organiza chegar.
 */
export async function POST(req: Request, { params }: { params: Promise<{ salaId: string }> }) {
  const { salaId } = await params;
  return rotaDeQuemEsta(req, salaId, async ({ admin, sala, eu }) => {
    const cfg = configuracaoDoSfu();
    if (!cfg) throw new ErroDaLive("A transmissão ainda está sendo ligada pela Ktalogu.", 503, { motivo: "sem_sfu" });
    if (sala.status === "encerrada" || sala.status === "cancelada") throw new ErroDaLive("A live terminou.", 410);
    const equipe = ehDaEquipe(eu.papel);
    if (!equipe && sala.categoria === "reuniao" && sala.status !== "ao_vivo") {
      throw new ErroDaLive("A reunião começa quando quem organizou entrar. A sua câmera e o seu microfone ligam sozinhos nessa hora.", 425, { motivo: "esperando" });
    }
    const corpo = await lerCorpo(req);
    const sdp = typeof corpo.sdp === "string" && corpo.sdp.length < 200000 ? corpo.sdp : "";
    if (!sdp) throw new ErroDaLive("Oferta inválida.", 400);
    const pedidas = (Array.isArray(corpo.trilhas) ? corpo.trilhas : [])
      .map((t) => t as { mid?: unknown; tipo?: unknown })
      .filter((t) => typeof t.mid === "string" && /^[0-9a-z_-]{1,12}$/i.test(t.mid) && TIPOS_DE_TRILHA.includes(t.tipo as TipoDeTrilha))
      .slice(0, 4) as Array<{ mid: string; tipo: TipoDeTrilha }>;
    if (!pedidas.length) throw new ErroDaLive("Nada para mandar.", 400);
    for (const t of pedidas) {
      if (!podePublicar(eu.papel, t.tipo, sala, eu.pode_falar)) {
        throw new ErroDaLive(
          t.tipo === "mic" ? "Levante a mão e espere a loja liberar o seu microfone." : "Você não pode mandar isso nesta live.",
          403,
          { motivo: "sem_permissao", tipo: t.tipo },
        );
      }
    }
    const nomes = pedidas.map((t) => ({ ...t, nome: nomeDaTrilha(t.tipo) }));

    /* Começou a apresentar: quem apresentava antes deixa de apresentar. */
    if (pedidas.some((t) => t.tipo === "tela")) {
      const outros = (await pessoasOnline(admin, sala.id)).filter((p) => p.id !== eu.id && p.trilhas?.tela);
      for (const outro of outros) {
        const trilhas = await cortarTrilhas(outro, ["tela", "tela-audio"], (sessao, mids) =>
          fecharTrilhas(cfg, sessao, { tracks: mids.map((mid) => ({ mid })), force: true }),
        );
        await admin.from("live_participantes").update({ trilhas }).eq("id", outro.id);
      }
    }
    const sessao = await sessaoDoSfu(admin, eu, () => novaSessao(cfg));
    const resposta = await novasTrilhas(cfg, sessao, {
      sessionDescription: { type: "offer", sdp },
      tracks: nomes.map((t) => ({ location: "local" as const, mid: t.mid, trackName: t.nome })),
    });
    const comErro = new Set((resposta.tracks ?? []).filter((t) => t.error || t.errorCode).map((t) => t.mid));
    const trilhas = { ...(eu.trilhas ?? {}) };
    for (const t of nomes) if (!comErro.has(t.mid)) trilhas[t.tipo] = { mid: t.mid, nome: t.nome };
    await admin.from("live_participantes").update({ trilhas }).eq("id", eu.id);
    await publicarEstado(admin, sala.id);
    return {
      resposta: resposta.sessionDescription ?? null,
      renegociar: Boolean(resposta.requiresImmediateRenegotiation),
      trilhas: nomes.map((t) => ({ mid: t.mid, tipo: t.tipo, nome: t.nome, ok: !comErro.has(t.mid) })),
    };
  });
}
