import { derived, type Readable, type Unsubscriber, writable } from "svelte/store";
import { Player } from "./client";
import { localStore, subscribe } from "./stores";

export class AudioManager {
  micGain: Readable<number>;
  useMusic: boolean;
  ac: AudioContext;
  dest: MediaStreamAudioDestinationNode;
  gain: GainNode;
  source: AudioNode;
  public readonly output: MediaStream;

  constructor(media: HTMLAudioElement | MediaStream, micVolume: Readable<number>, muted: Readable<boolean>) {
    this.ac = new AudioContext();
    this.dest = this.ac.createMediaStreamDestination();
    this.gain = this.ac.createGain();
    globalThis.audioPlay = this;

    if (media instanceof HTMLAudioElement) {
      this.source = this.ac.createMediaElementSource(media);
      media.autoplay = true;
      media.controls = true;
      media.play();
      this.useMusic = true;
    } else {
      this.source = this.ac.createMediaStreamSource(media);
      this.useMusic = false;
    }
    subscribe([micVolume, muted], ([micVolume, muted]) => {
      this.gain.gain.value = micVolume * +!muted;
    });
    this.source.connect(this.gain).connect(this.dest)
    this.output = this.dest.stream;
  }

  createPlayerAudio(
    outputDiv: HTMLDivElement,
    localPlayer: Readable<Player | undefined>,
    peerPlayer: Readable<Player | undefined>,
    globalSpeak: Readable<boolean>,
  ) {
    const gain = this.ac.createGain();
    const dest = this.ac.createMediaStreamDestination();

    const isDev = ((globalThis as any).isDev = "hot" in import.meta);
    let audio = document.createElement("audio");
    audio.srcObject = dest.stream;
    audio.autoplay = true;
    audio.controls = isDev;
    audio.play();
    outputDiv.appendChild(audio);

    return new PlayerAudio(this.ac, audio, dest, gain, localPlayer, peerPlayer, globalSpeak);
  }
}

export class PlayerAudio {
  private source: MediaStreamAudioSourceNode | undefined;
  private ender: Unsubscriber;
  constructor(
    private context: AudioContext,
    private audioElement: HTMLAudioElement,
    private dest: MediaStreamAudioDestinationNode,
    private gain: GainNode,
    localPlayer: Readable<Player | undefined>,
    peerPlayer: Readable<Player | undefined>,
    globalSpeak: Readable<boolean>,
  ) {
    console.log("created player audio");

    this.ender = subscribe([localPlayer, peerPlayer], ([localPlayer, peerPlayer]) => {
      if (!localPlayer || !peerPlayer) {
        this.gain.gain.value = 0;
        // console.warn("missing a player...");
        return () => { };
      }

      console.log("got player binding for (local, peer)", localPlayer.name, peerPlayer.name);

      return subscribe([localPlayer.proximityInfo, peerPlayer.proximityInfo, globalSpeak], ([localInfo, peerInfo, globalSpeak]) => {
        const [x, y, z] = localInfo.position.map((localPosition, i) => peerInfo.position[i] - localPosition);
        const sameStage = localInfo.stage === peerInfo.stage;
        let magnitude = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2) + Math.pow(z, 2));
        const max = 3500, min = 1000;
        let scaledGain = 1 - (magnitude - min) / max;
        let t = Math.max(Math.min(scaledGain * +sameStage, 1), 0);
        function easeInExpo(x: number): number {
          return x === 0 ? 0 : Math.pow(2, 10 * x - 10);
        }
        this.gain.gain.value = globalSpeak ? 1 : easeInExpo(t);
      });
    });
  }

  gotTrack(stream: MediaStream) {
    let a: HTMLAudioElement | null = new Audio();
    a.muted = true;
    a.srcObject = stream;
    a.addEventListener('canplaythrough', () => {
      a = null;
    });
    a.play();
    this.source = this.context.createMediaStreamSource(stream);
    let gainOut = this.source.connect(this.gain);
    gainOut.connect(this.dest);
    // gainOut.connect(this.context.destination);
  }

  delete() {
    this.audioElement.parentElement?.removeChild(this.audioElement);
    this.ender();
  }
}
