---
export interface Track {
  src: string;
  metadata: Record<string, any>;
  duration: number;
}

import { Icon } from "astro-icon/components";

interface Props {
  height?: string;
}

const { height = "h-28" } = Astro.props;

import { getAudioDurationInSeconds } from "get-audio-duration";
import { join } from "path";

import { getCollection } from "astro:content";
const autoQueuedTracks = (
  await getCollection("tracks", ({ data }) => data.autoQueue)
).sort(
  (a, b) => (a.data.autoQueue?.order || -1) - (b.data.autoQueue?.order || 1)
);

const initialQueue = await Promise.all(
  autoQueuedTracks.map(async ({ data }) => {
    const fullFilePath = join(process.cwd(), "public", data.src);
    const duration = await getAudioDurationInSeconds(fullFilePath);
    return {
      src: data.src,
      metadata: data.metadata,
      duration: duration
    };
  })
);

const playerArtworkBlur = "10px";
const playerArtworkOpacity = 0.4;
---

<div
  id="player"
  class:list={[
    "fixed right-0 bottom-0 left-0 z-100 overflow-hidden bg-black shadow-lg/75 md:right-3 md:bottom-3 md:left-3 md:rounded",
    height
  ]}
  transition:persist=""
  transition:name="player"
  transition:animate="none"
>
  <div class="absolute inset-0 -z-90 overflow-hidden">
    <div
      id="player:artwork:background"
      class="animate-bg-perlin absolute top-0 right-0 bottom-0 left-0 -z-90"
    >
    </div>
  </div>
  <div
    id="player:time:progress"
    class="relative top-0 right-0 left-0 m-0 h-2 w-full rounded-t p-0 hover:cursor-pointer"
  >
    <div
      id="player:time:progress:background"
      class="absolute top-0 left-0 m-0 h-full w-full bg-gray-900 p-0 opacity-50"
    >
    </div>
    <div
      id="player:time:progress:buffered"
      class="absolute top-0 left-0 m-0 h-full bg-gray-700 p-0 opacity-50"
    >
    </div>
    <div
      id="player:time:progress:played"
      class="from-primary to-primary-50 absolute top-0 left-0 m-0 h-full bg-gradient-to-r p-0 opacity-100"
    >
    </div>
  </div>
  <div class="px-2 pt-2 pb-2 text-white md:px-3 md:pb-3">
    <div class="grid grid-cols-2 text-xs">
      <span id="player:times:current" class="col-span-1 text-left">0:00</span>
      <span id="player:times:duration" class="col-span-1 text-right">0:00</span>
    </div>
    <div class="flex h-full flex-row items-center justify-center align-middle">
      <div class="m-0 flex grow-0 flex-row space-x-2 p-0 text-3xl">
        <span>
          <Icon name="fa7-solid:backward" id="player:controls:previous" />
        </span>
        <span>
          <Icon name="fa7-solid:play" id="player:controls:play" />
          <Icon
            name="fa7-solid:pause"
            id="player:controls:pause"
            class="hidden"
          />
        </span>
        <span>
          <Icon name="fa7-solid:forward" id="player:controls:next" />
        </span>
      </div>
      <div class="flex flex-grow flex-col text-sm">
        <span id="player:metadata:title" class="w-full text-right font-semibold"
        ></span>
        <span id="player:metadata:subtitle" class="w-full text-right"></span>
        <span id="player:metadata:extra" class="w-full text-right"></span>
      </div>
    </div>
  </div>
</div>
<div class:list={[height, "bg-gray-900"]}></div>

<script is:inline src="/js/howler.min.js"></script>
<script
  is:inline
  define:vars={{ initialQueue, playerArtworkBlur, playerArtworkOpacity }}
>
  /**
   * Player class containing the state of our playlist and where we are in it.
   * Includes all methods for playing, skipping, updating the display, etc.
   * @param {Array} playlist Array of objects with playlist song details ({title, file, howl}).
   */
  class Player {
    constructor(playlist) {
      this.playlist = window.player?.playlist ?? playlist;
      this.index = window.player?.index ?? 0;

      this.UI = {
        controls: {
          play: document.getElementById("player:controls:play"),
          pause: document.getElementById("player:controls:pause"),
          previous: document.getElementById("player:controls:previous"),
          next: document.getElementById("player:controls:next")
        },
        times: {
          current: document.getElementById("player:times:current"),
          duration: document.getElementById("player:times:duration")
        },
        metadata: {
          title: document.getElementById("player:metadata:title"),
          subtitle: document.getElementById("player:metadata:subtitle"),
          extra: document.getElementById("player:metadata:extra")
        },
        progress: {
          bar: document.getElementById("player:time:progress"),
          buffered: document.getElementById("player:time:progress:buffered"),
          played: document.getElementById("player:time:progress:played")
        },
        artwork: {
          background: document.getElementById("player:artwork:background")
        }
      };

      this.UI.metadata.title.textContent = playlist[this.index].metadata.title;
      this.UI.metadata.subtitle.textContent =
        playlist[this.index].metadata.subtitle;
      this.UI.metadata.extra.textContent = playlist[this.index].metadata.extra;
      this.UI.times.duration.textContent = this.formatTime(
        Math.round(playlist[this.index].duration)
      );

      if (playlist[this.index].metadata.artwork) {
        this.UI.artwork.background.style.backgroundImage = `url('${playlist[this.index].metadata.artwork.src}')`;
        this.UI.artwork.background.style.backgroundSize = "cover";
        this.UI.artwork.background.style.backgroundPosition = "center";
        this.UI.artwork.background.style.filter = `blur(${playerArtworkBlur})`;
        this.UI.artwork.background.style.opacity = playerArtworkOpacity;
      } else {
        this.UI.artwork.background.style.backgroundImage = "";
        this.UI.artwork.background.style.filter = "";
        this.UI.artwork.background.style.opacity = "0";
      }

      this.UI.controls.play.addEventListener("click", function () {
        window.player.play();
      });
      this.UI.controls.pause.addEventListener("click", function () {
        window.player.pause();
      });
      this.UI.controls.previous.addEventListener("click", function () {
        window.player.skip("prev");
      });
      this.UI.controls.next.addEventListener("click", function () {
        window.player.skip("next");
      });

      this.UI.progress.bar.addEventListener("click", function (event) {
        window.player.seek(event.clientX / window.innerWidth);
      });

      window.playerIsInitialised = true;
    }

    /**
     * Play a song in the playlist.
     * @param  {Number} index Index of the song in the playlist (leave empty to play the first or current).
     */
    play(index) {
      var sound;

      index = typeof index === "number" ? index : this.index;
      var data = this.playlist[index];

      // If we already loaded this track, use the current one.
      if (data.howl) {
        sound = data.howl;
      } else {
        sound = data.howl = new Howl({
          src: [data.src],
          autoplay: false,
          html5: true, // Force to HTML5 so that the audio can stream in (best for large files).
          onplay: () => {
            // Display the duration.
            this.UI.times.duration.textContent = this.formatTime(
              Math.round(sound.duration())
            );

            // Start updating the progress of the track.
            requestAnimationFrame(this.step.bind(this));

            this.UI.controls.pause.classList.remove("hidden");
            this.UI.controls.play.classList.add("hidden");
          },
          // onload: () => {
          //   loading.style.display = "none";
          // },
          onend: () => {
            this.skip("next");
          },
          onpause: () => {
            this.UI.controls.pause.classList.add("hidden");
            this.UI.controls.play.classList.remove("hidden");
          },
          // onstop: () => {
          // },
          onseek: () => {
            // Start updating the progress of the track.
            requestAnimationFrame(this.step.bind(this));
          }
        });
      }

      // Begin playing the sound.
      sound.play();

      // Update the track display.
      this.UI.metadata.title.textContent = data.metadata.title;
      this.UI.metadata.subtitle.textContent = data.metadata.subtitle;
      this.UI.metadata.extra.textContent = data.metadata.extra;
      this.UI.times.duration.textContent = this.formatTime(
        Math.round(data.duration)
      );
      this.UI.progress.buffered.style.width = "0%";

      if (data.metadata.artwork) {
        this.UI.artwork.background.style.backgroundImage = `url('${data.metadata.artwork.src}')`;
        this.UI.artwork.background.style.backgroundSize = "cover";
        this.UI.artwork.background.style.backgroundPosition = "center";
        this.UI.artwork.background.style.filter = `blur(${playerArtworkBlur})`;
        this.UI.artwork.background.style.opacity = playerArtworkOpacity;
      } else {
        this.UI.artwork.background.style.backgroundImage = "";
        this.UI.artwork.background.style.filter = "";
        this.UI.artwork.background.style.opacity = "0";
      }

      // Keep track of the index we are currently playing.
      this.index = index;
    }

    /**
     * Pause the currently playing track.
     */
    pause() {
      // Get the Howl we want to manipulate.
      var sound = this.playlist[this.index].howl;

      sound.pause();

      this.UI.controls.pause.classList.add("hidden");
      this.UI.controls.play.classList.remove("hidden");
    }

    /**
     * Skip to the next or previous track.
     * @param  {String} direction 'next' or 'prev'.
     */
    skip(direction) {
      var current = this.playlist[this.index];
      var sound = current.howl;
      var currentlyPlaying = sound && sound.playing();

      if (direction === "prev" && currentlyPlaying && sound.seek() > 3) {
        sound.seek(0);
        return;
      }

      let index;
      if (direction === "prev") {
        index = (this.index - 1 + this.playlist.length) % this.playlist.length;
      } else {
        index = (this.index + 1) % this.playlist.length;
      }

      this.skipTo(index);
    }

    /**
     * Skip to a specific track based on its playlist index.
     * @param  {Number} index Index in the playlist.
     */
    skipTo(index) {
      // Stop the current track.
      if (this.playlist[this.index].howl) {
        this.playlist[this.index].howl.stop();
      }

      // Reset progress.
      this.UI.progress.played.style.width = "0%";
      this.UI.times.current.textContent = this.formatTime(0);

      // Play the new track.
      this.play(index);
    }

    /**
     * Seek to a new position in the currently playing track.
     * @param  {Number} per Percentage through the song to skip.
     */
    seek(per) {
      // Get the Howl we want to manipulate.
      var sound = this.playlist[this.index].howl;

      if (sound.playing()) {
        // Convert the percent into a seek position.
        sound.seek(sound.duration() * per);
      }
    }

    /**
     * The step called within requestAnimationFrame to update the playback position.
     */
    step() {
      // Get the Howl we want to manipulate.
      var sound = this.playlist[this.index].howl;

      var seek = sound.seek() || 0;
      this.UI.times.current.textContent = this.formatTime(Math.round(seek));
      this.UI.progress.played.style.width =
        ((seek / sound.duration()) * 100 || 0) + "%";

      this.UI.progress.buffered.style.width = `${this.getBufferedAmount(sound).bufferedPercent}%`;

      // If the sound is still playing, continue stepping.
      if (sound.playing()) {
        requestAnimationFrame(this.step.bind(this));
      }
    }

    /**
     * Format the time from seconds to M:SS.
     * @param  {Number} secs Seconds to format.
     * @return {String}      Formatted time.
     */
    formatTime(secs) {
      var minutes = Math.floor(secs / 60) || 0;
      var seconds = secs - minutes * 60 || 0;

      return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
    }

    queue(track, immediatePlay = false) {
      this.playlist.splice(this.index + 1, 0, track);

      if (immediatePlay) {
        this.skipTo(this.index + 1);
      }
    }

    getBufferedAmount(sound) {
      if (sound._sounds.length > 0) {
        const audioNode = sound._sounds[0]._node; // The underlying HTML5 Audio element
        if (audioNode.buffered.length > 0) {
          const bufferedEnd = audioNode.buffered.end(0); // Time (seconds) where buffering ends
          const duration = audioNode.duration;
          const bufferedSeconds = bufferedEnd;
          const bufferedPercent = duration ? (bufferedEnd / duration) * 100 : 0;
          return { bufferedSeconds, bufferedPercent };
        }
      }
      return { bufferedSeconds: 0, bufferedPercent: 0 };
    }

    addToQueue(track, immediatePlay = false) {
      var current = this.playlist[this.index];

      if (
        track.metadata.title === current.metadata.title &&
        track.metadata.subtitle === current.metadata.subtitle &&
        track.metadata.extra === current.metadata.extra
      ) {
        if (current.howl && current.howl.playing()) {
          this.pause();
        } else {
          this.play();
        }
      } else {
        this.queue(track, immediatePlay);
      }
    }
  }

  if (!window.playerIsInitialised) {
    window.player = new Player(initialQueue);
  }
</script>
