/**
 * Broadcast <-> voice coupling, shared by the normal-bot and userbot entry
 * points so both modes behave identically.
 *
 * The screen broadcast is coupled to the voice session: it auto-starts when the
 * assistant joins a voice channel (screen-share mode default), reports its live
 * state to the brain each turn (search routes Chrome while live / Gemini when
 * off), and the brain can toggle it by voice ("방송 켜줘 / 꺼줘"). This logic
 * used to live only in the normal-bot join handler, so the userbot path (the
 * only mode that can actually Go Live) never started a broadcast. Keeping it
 * here means a single implementation serves both.
 */
import { config } from "./config.ts";
import { createStreamer, type ScreenStreamer, type StreamContext } from "./stream/index.ts";
import type { VoiceSession } from "./voice.ts";

/** One streamer per guild, shared across both entry points. */
const streamers = new Map<string, ScreenStreamer>();

export async function getStreamer(guildId: string): Promise<ScreenStreamer> {
  let s = streamers.get(guildId);
  if (!s) {
    s = await createStreamer(config);
    streamers.set(guildId, s);
  }
  return s;
}

/** The existing streamer for a guild, if one has been created. */
export function peekStreamer(guildId: string): ScreenStreamer | undefined {
  return streamers.get(guildId);
}

type BroadcastSession = Pick<
  VoiceSession,
  "getBroadcasting" | "onBroadcastAction" | "getSharedSession"
>;

/**
 * Wire a voice session to a streamer (no auto-start). Pure (no config / registry
 * access) so it can be unit-tested with a fake streamer:
 *  - report live state to the brain each turn,
 *  - let the brain toggle the stream by voice.
 */
export function wireBroadcast(
  session: BroadcastSession,
  streamer: ScreenStreamer,
  ctx: StreamContext,
): void {
  session.getBroadcasting = () => streamer.isActive();
  session.onBroadcastAction = async (action) => {
    if (action === "start") {
      if (!streamer.isActive()) await streamer.start(ctx);
    } else if (streamer.isActive()) {
      await streamer.stop();
    }
  };
}

/**
 * Auto-start the broadcast, retrying a couple of times before giving up. The
 * selfbot Go-Live path takes ~10-15s of humanised delays and can fail on a
 * transient (login race, voice not ready yet), so a bounded retry recovers the
 * common case. On final failure it logs loudly rather than silently leaving the
 * user in voice with no broadcast. Never throws: a broadcast problem must not
 * tear down the voice conversation. Returns whether the stream ended up live.
 */
export async function autoStartBroadcast(
  streamer: ScreenStreamer,
  ctx: StreamContext,
  attempts = 2,
  retryDelayMs = 1500,
): Promise<boolean> {
  for (let attempt = 1; attempt <= attempts; attempt++) {
    try {
      if (streamer.isActive()) return true;
      await streamer.start(ctx);
      if (streamer.isActive()) {
        console.log("🔴 [broadcast] auto-started on voice join (Go Live)");
        return true;
      }
      console.error(
        `[broadcast] auto-start attempt ${attempt}/${attempts} did not go live`,
      );
    } catch (e) {
      console.error(`[broadcast] auto-start attempt ${attempt}/${attempts} failed:`, e);
    }
    if (attempt < attempts && retryDelayMs > 0) {
      await new Promise((r) => setTimeout(r, retryDelayMs));
    }
  }
  console.error(
    "[broadcast] ⚠️ auto-start FAILED after retries — voice is up but NOT broadcasting " +
      "(STREAM_BROWSER=true). Trigger it by voice ('방송 켜줘') or check the selfbot stream deps.",
  );
  return false;
}

/**
 * Couple the broadcast to a freshly joined voice session when screen-share mode
 * is on (STREAM_BROWSER=true). No-op in voice-only mode (STREAM_BROWSER=false).
 *
 * The toggle hooks are wired synchronously; the auto-start runs in the
 * BACKGROUND so the ~10-15s Go-Live handshake never blocks the voice session
 * from coming up. The bot can already hear and reply while the stream warms.
 */
export async function maybeCoupleBroadcast(
  session: BroadcastSession,
  ctx: StreamContext,
): Promise<void> {
  if (!config.screenBrowser) return;
  const streamer = await getStreamer(ctx.guildId);
  // Let the selfbot backend broadcast on the conversation's own session
  // (single-account Go-Live) instead of a second login.
  const ctxWithSession: StreamContext = {
    ...ctx,
    getSharedSession: () => session.getSharedSession?.() ?? null,
  };
  wireBroadcast(session, streamer, ctxWithSession);
  void autoStartBroadcast(streamer, ctxWithSession);
}

/** Stop the broadcast for a guild if one is live (e.g. on leave). */
export async function stopBroadcast(guildId: string): Promise<void> {
  const s = streamers.get(guildId);
  if (s?.isActive()) {
    try {
      await s.stop();
    } catch (e) {
      console.error("[broadcast] stop failed:", e);
    }
  }
}
