/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * Object in charge of loading, storing, and playing audio for a
 *     workspace.
 *
 * @class
 */
// Former goog.module ID: Blockly.WorkspaceAudio

import * as userAgent from './utils/useragent.js';
import type {WorkspaceSvg} from './workspace_svg.js';

/**
 * Prevent a sound from playing if another sound preceded it within this many
 * milliseconds.
 */
const SOUND_LIMIT = 100;

/**
 * Class for loading, storing, and playing audio for a workspace.
 */
export class WorkspaceAudio {
  /** Database of pre-loaded sounds. */
  private sounds = new Map<string, HTMLAudioElement>();

  /** Time that the last sound was played. */
  private lastSound_: Date | null = null;

  /**
   * @param parentWorkspace The parent of the workspace this audio object
   *     belongs to, or null.
   */
  constructor(private parentWorkspace: WorkspaceSvg) {}

  /**
   * Dispose of this audio manager.
   *
   * @internal
   */
  dispose() {
    this.sounds.clear();
  }

  /**
   * Load an audio file.  Cache it, ready for instantaneous playing.
   *
   * @param filenames List of file types in decreasing order of preference (i.e.
   *     increasing size).  E.g. ['media/go.mp3', 'media/go.wav'] Filenames
   *     include path from Blockly's root.  File extensions matter.
   * @param name Name of sound.
   */
  load(filenames: string[], name: string) {
    if (!filenames.length) {
      return;
    }
    let audioTest;
    try {
      audioTest = new globalThis['Audio']();
    } catch (e) {
      // No browser support for Audio.
      // IE can throw an error even if the Audio object exists.
      return;
    }
    let sound;
    for (let i = 0; i < filenames.length; i++) {
      const filename = filenames[i];
      const ext = filename.match(/\.(\w+)$/);
      if (ext && audioTest.canPlayType('audio/' + ext[1])) {
        // Found an audio format we can play.
        sound = new globalThis['Audio'](filename);
        break;
      }
    }
    if (sound) {
      this.sounds.set(name, sound);
    }
  }

  /**
   * Preload all the audio files so that they play quickly when asked for.
   *
   * @internal
   */
  preload() {
    for (const sound of this.sounds.values()) {
      sound.volume = 0.01;
      const playPromise = sound.play();
      // Edge does not return a promise, so we need to check.
      if (playPromise !== undefined) {
        // If we don't wait for the play request to complete before calling
        // pause() we will get an exception: (DOMException: The play() request
        // was interrupted) See more:
        // https://developers.google.com/web/updates/2017/06/play-request-was-interrupted
        playPromise.then(sound.pause).catch(
          // Play without user interaction was prevented.
          function () {},
        );
      } else {
        sound.pause();
      }

      // iOS can only process one sound at a time.  Trying to load more than one
      // corrupts the earlier ones.  Just load one and leave the others
      // uncached.
      if (userAgent.IPAD || userAgent.IPHONE) {
        break;
      }
    }
  }

  /**
   * Play a named sound at specified volume.  If volume is not specified,
   * use full volume (1).
   *
   * @param name Name of sound.
   * @param opt_volume Volume of sound (0-1).
   */
  play(name: string, opt_volume?: number) {
    const sound = this.sounds.get(name);
    if (sound) {
      // Don't play one sound on top of another.
      const now = new Date();
      if (
        this.lastSound_ !== null &&
        now.getTime() - this.lastSound_.getTime() < SOUND_LIMIT
      ) {
        return;
      }
      this.lastSound_ = now;
      let mySound;
      if (userAgent.IPAD || userAgent.ANDROID) {
        // Creating a new audio node causes lag in Android and iPad.  Android
        // refetches the file from the server, iPad uses a singleton audio
        // node which must be deleted and recreated for each new audio tag.
        mySound = sound;
      } else {
        mySound = sound.cloneNode() as HTMLAudioElement;
      }
      mySound.volume = opt_volume === undefined ? 1 : opt_volume;
      mySound.play();
    } else if (this.parentWorkspace) {
      // Maybe a workspace on a lower level knows about this sound.
      this.parentWorkspace.getAudioManager().play(name, opt_volume);
    }
  }
}
