import { supabase } from '@/integrations/supabase/client';
import {
  generateRoomCode,
  joinSignalingChannel,
  sendSignal,
  waitForSignalingReady,
  ICE_SERVERS,
  type SignalMessage,
} from './signaling';
import type { RealtimeChannel } from '@supabase/supabase-js';

export interface BroadcasterHandle {
  code: string;
  stop: () => Promise<void>;
  onViewerCountChange: (cb: (n: number) => void) => () => void;
  /** Swap the outgoing video track on every active peer (e.g. when the XR mirror canvas becomes available). */
  replaceVideoTrack: (newStream: MediaStream) => Promise<void>;
  /** Low-FPS backup source used when Quest/browser captureStream produces black frames. */
  setFrameSource: (canvas: HTMLCanvasElement | null) => void;
}

const MAX_VIEWERS = 5;

/**
 * Starts broadcasting a MediaStream under a fresh room code.
 * Returns a handle exposing the code and a stop() to teardown.
 */
export async function startBroadcast(
  stream: MediaStream,
  options: { frameSource?: HTMLCanvasElement | null } = {},
): Promise<BroadcasterHandle> {
  const code = generateRoomCode();

  // Insert room
  const { error: insErr } = await supabase.from('share_rooms').insert({ code, is_active: true });
  if (insErr) throw new Error(`Falha ao criar sala: ${insErr.message}`);

  const peers = new Map<string, RTCPeerConnection>();
  const listeners = new Set<(n: number) => void>();
  const notify = () => listeners.forEach((cb) => cb(peers.size));

  let channel: RealtimeChannel | null = null;
  let activeStream: MediaStream = stream;
  let frameSource: HTMLCanvasElement | null = options.frameSource ?? null;
  let frameBusy = false;

  const handleMessage = async (msg: SignalMessage) => {
    if (msg.type === 'viewer-join') {
      if (peers.size >= MAX_VIEWERS) return;
      const pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });
      peers.set(msg.viewerId, pc);

      activeStream.getTracks().forEach((t) => pc.addTrack(t, activeStream));
      if (activeStream.getTracks().length === 0) pc.createDataChannel('fallback-frames');

      pc.onicecandidate = (ev) => {
        if (ev.candidate && channel) {
          void sendSignal(channel, {
            type: 'ice',
            viewerId: msg.viewerId,
            from: 'broadcaster',
            candidate: ev.candidate.toJSON(),
          });
        }
      };

      pc.onconnectionstatechange = () => {
        if (['failed', 'closed', 'disconnected'].includes(pc.connectionState)) {
          peers.delete(msg.viewerId);
          pc.close();
          notify();
          updateViewerCount(code, peers.size);
        }
      };

      const offer = await pc.createOffer();
      await pc.setLocalDescription(offer);
      if (channel) {
        await sendSignal(channel, { type: 'offer', viewerId: msg.viewerId, sdp: offer });
      }
      notify();
      updateViewerCount(code, peers.size);
    } else if (msg.type === 'answer') {
      const pc = peers.get(msg.viewerId);
      if (pc && pc.signalingState !== 'stable') {
        await pc.setRemoteDescription(msg.sdp);
      }
    } else if (msg.type === 'ice' && msg.from === 'viewer') {
      const pc = peers.get(msg.viewerId);
      if (pc) {
        try {
          await pc.addIceCandidate(msg.candidate);
        } catch (e) {
          console.warn('[broadcaster] addIceCandidate failed', e);
        }
      }
    } else if (msg.type === 'viewer-leave') {
      const pc = peers.get(msg.viewerId);
      if (pc) {
        pc.close();
        peers.delete(msg.viewerId);
        notify();
        updateViewerCount(code, peers.size);
      }
    }
  };

  channel = joinSignalingChannel(code, handleMessage);
  await waitForSignalingReady(channel);

  const frameCanvas = document.createElement('canvas');
  frameCanvas.width = 640;
  frameCanvas.height = 360;
  const frameCtx = frameCanvas.getContext('2d', { alpha: false });
  const frameTimer = window.setInterval(() => {
    if (!channel || !frameSource || !frameCtx || peers.size === 0 || frameBusy) return;
    frameBusy = true;
    try {
      frameCtx.fillStyle = '#1a1a1a';
      frameCtx.fillRect(0, 0, frameCanvas.width, frameCanvas.height);
      frameCtx.drawImage(frameSource, 0, 0, frameCanvas.width, frameCanvas.height);
      const dataUrl = frameCanvas.toDataURL('image/jpeg', 0.55);
      void sendSignal(channel, { type: 'frame', dataUrl, ts: Date.now() }).finally(() => { frameBusy = false; });
    } catch (e) {
      frameBusy = false;
      console.warn('[broadcaster] frame fallback failed', e);
    }
  }, 500);

  // Stop when the underlying stream ends
  const attachEndedListeners = (s: MediaStream) => {
    s.getTracks().forEach((t) => {
      t.addEventListener('ended', () => {
        if (s === activeStream) stop();
      });
    });
  };
  attachEndedListeners(stream);

  let stopped = false;
  const stop = async () => {
    if (stopped) return;
    stopped = true;
    peers.forEach((pc) => pc.close());
    peers.clear();
    window.clearInterval(frameTimer);
    notify();
    if (channel) await supabase.removeChannel(channel);
    await supabase.from('share_rooms').update({ is_active: false, viewer_count: 0 }).eq('code', code);
  };

  const replaceVideoTrack = async (newStream: MediaStream) => {
    const newVideo = newStream.getVideoTracks()[0];
    if (!newVideo) return;
    activeStream = newStream;
    attachEndedListeners(newStream);
    for (const pc of peers.values()) {
      const sender = pc.getSenders().find((s) => s.track && s.track.kind === 'video');
      if (sender) {
        try {
          await sender.replaceTrack(newVideo);
        } catch (e) {
          console.warn('[broadcaster] replaceTrack failed', e);
        }
      }
    }
  };

  return {
    code,
    stop,
    replaceVideoTrack,
    setFrameSource: (canvas) => { frameSource = canvas; },
    onViewerCountChange: (cb) => {
      listeners.add(cb);
      cb(peers.size);
      return () => listeners.delete(cb);
    },
  };
}

async function updateViewerCount(code: string, n: number) {
  await supabase.from('share_rooms').update({ viewer_count: n }).eq('code', code);
}
