// Session-only per-share audio volume. Mirror of `participantVolumes` but
// NOT persisted — when the user leaves the call or restarts the app, these
// reset to default. Intentional: the relevant trackSid is ephemeral anyway,
// and users don't expect screen-share volume to survive between sessions.
//
// Keys are participantIds (LiveKit identity). There's one screen-share per
// participant at a time in LiveKit, so keying by id keeps the API aligned
// with how the context menu surfaces the control ("Dennis's share").

const DEFAULT_VOLUME = 1;
// Matches Discord's slider range — up to 200% via the WebAudio GainNode in
// remoteAudioPipelines. HTMLMediaElement.volume only goes to 1.0, so the
// above-100% values are only meaningful on the WebAudio output path.
const MAX_VOLUME = 2;

type VolumeMap = Record<string, number>;
type Listener = (map: VolumeMap) => void;

let current: VolumeMap = {};
const listeners = new Set<Listener>();

function clamp(v: number): number {
  if (!Number.isFinite(v)) return DEFAULT_VOLUME;
  if (v < 0) return 0;
  if (v > MAX_VOLUME) return MAX_VOLUME;
  return v;
}

function notify(): void {
  for (const fn of listeners) fn(current);
}

export function getScreenShareVolume(userId: string): number {
  return current[userId] ?? DEFAULT_VOLUME;
}

export function setScreenShareVolume(userId: string, volume: number): void {
  const next = clamp(volume);
  if (next === (current[userId] ?? DEFAULT_VOLUME)) return;
  current = { ...current, [userId]: next };
  applyToAttachedElements(userId, next);
  notify();
}

export function subscribeScreenShareVolumes(fn: Listener): () => void {
  listeners.add(fn);
  return () => {
    listeners.delete(fn);
  };
}

// Reset on call end — called from CallContext when CallState.idle triggers.
export function clearScreenShareVolumes(): void {
  if (Object.keys(current).length === 0) return;
  current = {};
  notify();
}

// Live-apply to any <audio> element already attached for this share. The
// elements are tagged by attachTrack in CallContext with
// `data-participant="<identity>"` + `data-track-source="screenshare"` — the
// combined selector makes sure we don't retarget the mic audio for the same
// user (different track-source). HTMLMediaElement.volume caps at 1.0, so
// clip here — the WebAudio GainNode on the live pipeline handles values
// above 1.
function applyToAttachedElements(userId: string, volume: number): void {
  const elVolume = Math.min(1, Math.max(0, volume));
  const nodes = document.querySelectorAll<HTMLAudioElement>(
    'audio[data-participant="' +
      cssEscape(userId) +
      '"][data-track-source="screenshare"]',
  );
  nodes.forEach((el) => {
    el.volume = elVolume;
  });
}

function cssEscape(v: string): string {
  if (typeof (globalThis as { CSS?: { escape?: (s: string) => string } }).CSS
    ?.escape === 'function') {
    return (globalThis as { CSS: { escape: (s: string) => string } }).CSS.escape(v);
  }
  return v.replace(/"/g, '\\"');
}
