/**
 * Audio/Video Conferencing Configuration Sheet
 * @extends {FormApplication}
 *
 * @param {AVMaster} object                   The {@link AVMaster} instance being configured.
 * @param {FormApplicationOptions} [options]  Application configuration options.
 */
class AVConfig extends FormApplication {
  constructor(object, options) {
    super(object || game.webrtc, options);
  }

  /* -------------------------------------------- */

  /** @override */
  static get defaultOptions() {
    return foundry.utils.mergeObject(super.defaultOptions, {
      title: game.i18n.localize("WEBRTC.Title"),
      id: "av-config",
      template: "templates/sidebar/apps/av-config.html",
      popOut: true,
      width: 480,
      height: "auto",
      tabs: [{navSelector: ".tabs", contentSelector: "form", initial: "general"}]
    });
  }

  /* -------------------------------------------- */

  /** @override */
  async getData(options={}) {
    const settings = this.object.settings;
    const videoSources = await this.object.client.getVideoSources();
    const audioSources = await this.object.client.getAudioSources();
    const audioSinks = await this.object.client.getAudioSinks();

    // If the currently chosen device is unavailable, display a separate option for 'unavailable device (use default)'
    const { videoSrc, audioSrc, audioSink } = settings.client;
    const videoSrcUnavailable = this._isSourceUnavailable(videoSources, videoSrc);
    const audioSrcUnavailable = this._isSourceUnavailable(audioSources, audioSrc);
    const audioSinkUnavailable = this._isSourceUnavailable(audioSinks, audioSink);
    const isSSL = window.location.protocol === "https:";

    // Audio/Video modes
    const modes = {
      [AVSettings.AV_MODES.DISABLED]: "WEBRTC.ModeDisabled",
      [AVSettings.AV_MODES.AUDIO]: "WEBRTC.ModeAudioOnly",
      [AVSettings.AV_MODES.VIDEO]: "WEBRTC.ModeVideoOnly",
      [AVSettings.AV_MODES.AUDIO_VIDEO]: "WEBRTC.ModeAudioVideo"
    };

    // Voice Broadcast modes
    const voiceModes = Object.values(AVSettings.VOICE_MODES).reduce((obj, m) => {
      obj[m] = game.i18n.localize(`WEBRTC.VoiceMode${m.titleCase()}`);
      return obj;
    }, {});

    // Nameplate settings.
    const nameplates = {
      [AVSettings.NAMEPLATE_MODES.OFF]: "WEBRTC.NameplatesOff",
      [AVSettings.NAMEPLATE_MODES.PLAYER_ONLY]: "WEBRTC.NameplatesPlayer",
      [AVSettings.NAMEPLATE_MODES.CHAR_ONLY]: "WEBRTC.NameplatesCharacter",
      [AVSettings.NAMEPLATE_MODES.BOTH]: "WEBRTC.NameplatesBoth"
    };

    const dockPositions = Object.fromEntries(Object.values(AVSettings.DOCK_POSITIONS).map(p => {
      return [p, game.i18n.localize(`WEBRTC.DockPosition${p.titleCase()}`)];
    }));

    // Return data to the template
    return {
      user: game.user,
      modes,
      voiceModes,
      serverTypes: {FVTT: "WEBRTC.FVTTSignalingServer", custom: "WEBRTC.CustomSignalingServer"},
      turnTypes: {server: "WEBRTC.TURNServerProvisioned", custom: "WEBRTC.CustomTURNServer"},
      settings,
      canSelectMode: game.user.isGM && isSSL,
      noSSL: !isSSL,
      videoSources,
      audioSources,
      audioSinks: foundry.utils.isEmpty(audioSinks) ? false : audioSinks,
      videoSrcUnavailable,
      audioSrcUnavailable,
      audioSinkUnavailable,
      audioDisabled: audioSrc === "disabled",
      videoDisabled: videoSrc === "disabled",
      nameplates,
      nameplateSetting: settings.client.nameplates ?? AVSettings.NAMEPLATE_MODES.BOTH,
      dockPositions,
      audioSourceOptions: this.#getDevices(audioSources, audioSrcUnavailable, "WEBRTC.DisableAudioSource"),
      audioSinkOptions: this.#getDevices(audioSinks, audioSinkUnavailable),
      videoSourceOptions: this.#getDevices(videoSources, videoSrcUnavailable, "WEBRTC.DisableVideoSource")
    };
  }

  /* -------------------------------------------- */

  /**
   * Get an array of available devices which can be chosen.
   * @param {Record<string, string>} devices
   * @param {string} unavailableDevice
   * @param {string} disabledLabel
   * @returns {FormSelectOption[]}
   */
  #getDevices(devices, unavailableDevice, disabledLabel) {
    const options = [];
    let hasDefault = false;
    for ( const [k, v] of Object.entries(devices) ) {
      if ( k === "default" ) hasDefault = true;
      options.push({value: k, label: v});
    }
    if ( !hasDefault ) {
      options.unshift({value: "default", label: game.i18n.localize("WEBRTC.DefaultSource")});
    }
    if ( disabledLabel ) {
      options.unshift({value: "disabled", label: game.i18n.localize(disabledLabel)});
    }
    if ( unavailableDevice ) {
      options.push({value: unavailableDevice, label: game.i18n.localize("WEBRTC.UnavailableDevice")});
    }
    return options;
  }

  /* -------------------------------------------- */
  /*  Event Listeners and Handlers                */
  /* -------------------------------------------- */

  /** @override */
  activateListeners(html) {
    super.activateListeners(html);

    // Options below are GM only
    if ( !game.user.isGM ) return;
    html.find('select[name="world.turn.type"]').change(this._onTurnTypeChanged.bind(this));

    // Activate or de-activate the custom server and turn configuration sections based on current settings
    const settings = this.object.settings;
    this._setConfigSectionEnabled(".webrtc-custom-turn-config", settings.world.turn.type === "custom");
  }

  /* -------------------------------------------- */

  /**
   * Set a section's input to enabled or disabled
   * @param {string} selector    Selector for the section to enable or disable
   * @param {boolean} enabled    Whether to enable or disable this section
   * @private
   */
  _setConfigSectionEnabled(selector, enabled = true) {
    let section = this.element.find(selector);
    if (section) {
      section.css("opacity", enabled ? 1.0 : 0.5);
      section.find("input").prop("disabled", !enabled);
    }
  }

  /* -------------------------------------------- */

  /**
   * Determine whether a given video or audio source, or audio sink has become
   * unavailable since the last time it was set.
   * @param {object} sources The available devices
   * @param {string} source  The selected device
   * @private
   */
  _isSourceUnavailable(sources, source) {
    const specialValues = ["default", "disabled"];
    return source && (!specialValues.includes(source)) && !Object.keys(sources).includes(source);
  }

  /* -------------------------------------------- */

  /**
   * Callback when the turn server type changes
   * Will enable or disable the turn section based on whether the user selected a custom turn or not
   * @param {Event} event   The event that triggered the turn server type change
   * @private
   */
  _onTurnTypeChanged(event) {
    event.preventDefault();
    const choice = event.currentTarget.value;
    this._setConfigSectionEnabled(".webrtc-custom-turn-config", choice === "custom")
  }

  /* -------------------------------------------- */

  /** @override */
  async _updateObject(event, formData) {
    const settings = game.webrtc.settings;
    settings.client.videoSrc = settings.client.videoSrc || null;
    settings.client.audioSrc = settings.client.audioSrc || null;

    const update = foundry.utils.expandObject(formData);

    // Update world settings
    if ( game.user.isGM ) {
      if ( settings.world.mode !== update.world.mode ) SettingsConfig.reloadConfirm({world: true});
      const world = foundry.utils.mergeObject(settings.world, update.world);
      await game.settings.set("core", "rtcWorldSettings", world);
    }

    // Update client settings
    const client = foundry.utils.mergeObject(settings.client, update.client);
    await game.settings.set("core", "rtcClientSettings", client);
  }
}
