import { useEffect, useRef, useState } from 'react';
import { useVoiceStore } from '../stores/voice.ts';
import { Track, RoomEvent } from 'livekit-client';
import type { Participant, TrackPublication, Room } from 'livekit-client';

// ponytail: global set of audio elements blocked by autoplay policy
const pendingAudio = new Set<HTMLAudioElement>();

function flushPendingAudio() {
  for (const el of pendingAudio) {
    el.play().catch(() => {});
    pendingAudio.delete(el);
  }
}

let listenerAttached = false;
function ensureInteractionListener() {
  if (listenerAttached) return;
  listenerAttached = true;
  document.addEventListener('click', flushPendingAudio, { once: false });
  document.addEventListener('keydown', flushPendingAudio, { once: false });
}

function RemoteAudioTrack({ participant, room }: { participant: Participant; room: Room }) {
  const audioRef = useRef<HTMLAudioElement>(null);

  useEffect(() => {
    const el = audioRef.current;
    if (!el) return;

    let pub: TrackPublication | undefined;

    const tryPlay = () => {
      el.play().catch(() => {
        pendingAudio.add(el);
        ensureInteractionListener();
      });
    };

    const attachIfReady = () => {
      pub = participant.getTrackPublication(Track.Source.Microphone);
      if (pub?.track && el) {
        pub.track.attach(el);
        tryPlay();
      }
    };

    attachIfReady();

    const handleSubscribed = (track: Track, _pub: TrackPublication, p: Participant) => {
      if (p.identity === participant.identity && _pub.source === Track.Source.Microphone) {
        track.attach(el);
        tryPlay();
      }
    };
    room.on(RoomEvent.TrackSubscribed, handleSubscribed);

    return () => {
      if (pub?.track) pub.track.detach(el);
      pendingAudio.delete(el);
      room.off(RoomEvent.TrackSubscribed, handleSubscribed);
    };
  }, [participant, room]);

  return <audio ref={audioRef} autoPlay />;
}

export function AudioRenderers() {
  const room = useVoiceStore((s) => s._room);
  // ponytail: use a counter that increments on every participant change
  // zustand's Object.is check on the participants array wasn't triggering re-renders
  const [, setTick] = useState(0);

  useEffect(() => {
    if (!room) return;
    const bump = () => setTick((t) => t + 1);
    room.on(RoomEvent.ParticipantConnected, bump);
    room.on(RoomEvent.ParticipantDisconnected, bump);
    room.on(RoomEvent.TrackSubscribed, bump);
    room.on(RoomEvent.TrackUnsubscribed, bump);
    return () => {
      room.off(RoomEvent.ParticipantConnected, bump);
      room.off(RoomEvent.ParticipantDisconnected, bump);
      room.off(RoomEvent.TrackSubscribed, bump);
      room.off(RoomEvent.TrackUnsubscribed, bump);
    };
  }, [room]);

  if (!room) return null;

  const remoteParticipants = Array.from(room.remoteParticipants.values());

  return (
    <div className="hidden">
      {remoteParticipants.map((p) => (
        <RemoteAudioTrack key={p.identity} participant={p} room={room} />
      ))}
    </div>
  );
}
