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

export class AudioManager {
  globalGain = writable(1.0);
  useMusic: boolean;
  ac: AudioContext;
  dest: MediaStreamAudioDestinationNode;
  gain: GainNode;
  source: AudioNode;
  public readonly output: MediaStream;

  constructor(media: HTMLAudioElement | MediaStream) {
    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;
    }
    this.gain.gain.value /= 32;
    this.source.connect(this.gain).connect(this.dest)
    this.output = this.dest.stream;
  }

  createPlayerAudio(outputDiv: HTMLDivElement, localPlayer: Readable<Player | undefined>, peerPlayer: Readable<Player | undefined>) {
    const panner = this.ac.createPanner();
    panner.distanceModel = "linear";
    panner.refDistance = 1500;
    panner.maxDistance = 2500;
    const gain = this.ac.createGain();
    const dest = this.ac.createMediaStreamDestination();
    gain.connect(panner).connect(dest);

    let audio = document.createElement("audio");
    audio.srcObject = dest.stream;
    audio.autoplay = true;
    audio.controls = false;
    outputDiv.appendChild(audio);

    return new PlayerAudio(this.ac, panner, gain, localPlayer, peerPlayer);
  }
}

export class PlayerAudio {
  private source: MediaStreamAudioSourceNode | undefined;
  private playerWatcher: Readable<[number[], boolean]>;
  constructor(
    private context: AudioContext,
    private panner: PannerNode,
    private gain: GainNode,
    localPlayer: Readable<Player | undefined>,
    peerPlayer: Readable<Player | undefined>
  ) {
    this.playerWatcher = derived([localPlayer, peerPlayer], ([localPlayer, peerPlayer], set) => {
      if (!localPlayer || !peerPlayer) {
        console.log("got player binding")
        set([[0, 0, 0], false]);
        return () => { };
      }

      return derived([localPlayer.stageInfo, peerPlayer.stageInfo], ([localInfo, peerInfo]): [number[], boolean] => {
        return [
          localInfo.position.map((localPosition, i) => peerInfo.position[i] - localPosition),
          localInfo.stage === peerInfo.stage
        ];
      }).subscribe(set);
    });

    this.playerWatcher.subscribe(([[x, y, z], sameStage]) => {
      this.panner.positionX.value = x;
      this.panner.positionY.value = y;
      this.panner.positionZ.value = z;
      this.gain.gain.value = +sameStage;
    });
  }

  gotTrack(stream: MediaStream) {
    this.source = this.context.createMediaStreamSource(stream);
    this.source.connect(this.gain);
  }
}
