class Radiostasis {
  // audio player
  private readonly player: Player;
  private readonly playlist: Playlist;

  // ui element
  private readonly main: HTMLElement;

  private debouncer: number | null = null;

  constructor() {
    this.playlist = new Playlist(() => this.player);
    this.player = new Player(this.playlist);
    this.playlist.initialize();

    this.main = getOrThrow(document.getElementsByTagName('main').item(0));

    // set up htmx event handlers
    document.addEventListener('htmx:historyRestore', () =>
      this.wireLoadedFragment()
    );
    document
      .getElementsByTagName('main')
      .item(0)
      ?.addEventListener('htmx:afterSwap', () => this.wireLoadedFragment());

    // set up playlist changed handler
    this.playlist.addPlaylistChangedHandler(() => this.episodeStateChanged());

    // set up prompt to prevent accidentally leaving the page while playing
    window.addEventListener('beforeunload', (e) => {
      if (!this.player.isPlaying()) return undefined;
      e.preventDefault();
      e.returnValue = '';
    });
  }

  public initialize(): void {
    const path =
      location.pathname == '/'
        ? '/partial/home.html'
        : `/partial/${location.pathname}.html`;
    htmx.ajax('GET', path, 'main');
  }

  private wireLoadedFragment(): void {
    // save a list of all episodes for the series play/queue buttons
    const seriesEpisodes: Array<Episode> = [];

    // episode play and queue buttons
    const episodes = this.main.getElementsByClassName('episode');
    for (let i = 0; i < episodes.length; i++) {
      const el = <HTMLLIElement>episodes.item(i);
      if (!el || !el.dataset.episode) continue;

      const episode = <Episode>JSON.parse(el.dataset.episode);

      // save and store this model for later use
      seriesEpisodes.push(episode);

      // play button
      el.getElementsByTagName('a')
        .item(0)
        ?.addEventListener('click', (e) => {
          if (this.player.currentEpisode()?.id !== episode.id) {
            this.playlist.reQueueNowPlaying();
            this.player.playEpisode(episode);
          }
          e.preventDefault();
        });

      // queue button
      el.getElementsByTagName('a')
        .item(1)
        ?.addEventListener('click', (e) => {
          if (!this.player.currentEpisode()) {
            // if nothing is playing, first queued item gets loaded
            // into the player but does not autoplay
            this.player.playEpisode(episode, true);
          } else if (this.player.currentEpisode()?.id !== episode.id) {
            if (this.playlist.isQueued(episode)) {
              this.playlist.removeEpisode(episode);
            } else {
              this.playlist.pushEpisode(episode);
            }
          }
          e.preventDefault();
        });

      // set playing/queued state
      this.updateEpisodeElementFromState(el, episode);
    }

    // series play and queue buttons
    const series = document
      .getElementsByClassName('seriesDetails')
      .item(0)
      ?.getElementsByTagName('section')
      .item(0);

    if (series) {
      // play series button
      series
        .getElementsByTagName('a')
        .item(0)
        ?.addEventListener('click', () => {
          this.playlist.unshiftEpisodes(seriesEpisodes);
          if (this.player.currentEpisode()?.id !== seriesEpisodes[0].id) {
            this.player.playEpisode(seriesEpisodes[0]);
          } else {
            this.playlist.removeEpisode(seriesEpisodes[0]);
          }
        });

      // queue series button
      series
        .getElementsByTagName('a')
        .item(1)
        ?.addEventListener('click', () => {
          const toQueue = seriesEpisodes.filter(
            (e) => this.player.currentEpisode()?.id !== e.id
          );
          this.playlist.pushEpisodes(toQueue);
          // if nothing is playing, peel off the first episode and load it
          // into the player without autoplay
          if (!this.player.currentEpisode()) {
            this.player.playEpisode(seriesEpisodes[0], true);
          }
        });
    }

    // series filter input
    const filter = <HTMLInputElement>(
      this.main.getElementsByClassName('filter').item(0)
    );

    if (filter) {
      filter.value = filter.dataset.search ?? '';
      const allSeries = this.main.getElementsByTagName('section');
      filter.addEventListener('input', () => {
        filter.dataset.search = filter.value;
        if (this.debouncer) clearTimeout(this.debouncer);
        this.debouncer = setTimeout(() => {
          const terms = filter.value.toLowerCase().split(' ');
          for (let i = 0; i < allSeries.length; i++) {
            const series = allSeries.item(i);
            if (!series || !series.dataset.filter) continue;
            let match = true;
            for (const term of terms) {
              if (term.length > 0 && series.dataset.filter.indexOf(term) < 0) {
                match = false;
                break;
              }
            }
            if (match) series.classList.remove('no-match');
            else series.classList.add('no-match');
          }
        }, 499);
      });
    }
  }

  private updateEpisodeElementFromState(
    litem: HTMLLIElement,
    episode: Episode
  ): void {
    if (this.player.currentEpisode()?.id === episode.id) {
      litem.classList.add('playing');
      getOrThrow(litem.getElementsByTagName('a').item(0)).innerHTML = 'Playing';
    } else {
      litem.classList.remove('playing');
      getOrThrow(litem.getElementsByTagName('a').item(0)).innerHTML =
        'Play Episode';
    }
    const queueBtn = getOrThrow(litem.getElementsByTagName('a').item(1));
    if (this.playlist.isQueued(episode)) {
      queueBtn.classList.add('queued');
      queueBtn.innerHTML = 'Remove from Queue';
    } else {
      queueBtn.classList.remove('queued');
      queueBtn.innerHTML = 'Queue Episode';
    }
  }

  private episodeStateChanged(): void {
    const episodes = this.main.getElementsByClassName('episode');
    for (let i = 0; i < episodes.length; i++) {
      const el = <HTMLLIElement>episodes.item(i);
      if (!el || !el.dataset.episode) continue;
      const episode = <Episode>JSON.parse(el.dataset.episode);
      this.updateEpisodeElementFromState(el, episode);
    }
  }
}
