/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This Source Code Form is "Incompatible With Secondary Licenses", as
 * defined by the Mozilla Public License, v. 2.0. */

/**
 * Reference or define the Bugzilla app namespace.
 * @namespace
 */
var Bugzilla = Bugzilla || {};

/**
 * Implement the one-click Component Watching functionality that can be added to any page.
 */
Bugzilla.ComponentWatching = class ComponentWatching {
  /**
   * Initialize a new ComponentWatching instance. Since constructors can't be async, use a separate function to move on.
   */
  constructor() {
    this._method = 'component-watching';
    this.buttons = document.querySelectorAll('button.component-watching');

    // Check if the user is logged in and the API key is available. If not, remove the Watch buttons.
    if (BUGZILLA.api_token) {
      this.init();
    } else {
      this.buttons.forEach($button => $button.remove());
    }
  }

  /**
   * Retrieve the user's current watch list.
   * @returns {Promise.<(Array.<Object>|Error)>} List of the current watches.
   */
  async list() {
    return Bugzilla.API.get(this._method);
  }

  /**
   * Start watching the current product or component.
   * @param {String} product Product name.
   * @param {String} [component=''] Component name. If omitted, all the components in the product will be watched.
   * @returns {Promise.<(Object|Error)>} Detail of the added watch.
   */
  async watch(product, component = '') {
    return Bugzilla.API.post(this._method, { product, component });
  }

  /**
   * Stop watching the current product or component.
   * @param {Number} id ID of the watch to be removed.
   * @returns {Promise.<(Array.<Object>|Error)>} List of the current watches excluding the removed one.
   */
  async unwatch(id) {
    return Bugzilla.API.delete(`${this._method}/${id}`);
  }

  /**
   * Log an event with Google Analytics if possible. For privacy reasons, we don't send any specific product or
   * component name.
   * @param {String} source Event source that will be part of the event category.
   * @param {String} action `watch` or `unwatch`.
   * @param {String} type `product` or `component`.
   * @param {Number} code `0` for a successful change, `1` otherwise.
   * @see https://developers.google.com/analytics/devguides/collection/analyticsjs/events
   */
  track_event(source, action, type, code) {
    if ('ga' in window) {
      ga('send', 'event', `Component Watching: ${source}`, action, type, code);
    }
  }

  /**
   * Show a short floating message if the button is on BugModal. This code is from bug_modal.js, requiring jQuery.
   * @param {String} message Message text.
   */
  show_message(message) {
    if (!document.querySelector('#floating-message')) {
      return;
    }

    $('#floating-message-text').text(message);
    $('#floating-message').fadeIn(250).delay(2500).fadeOut();
  }

  /**
   * Get all the component watching buttons on the current page.
   * @param {String} [product] Optional product name.
   * @param {String} [component] Optional component name.
   * @returns {HTMLButtonElement[]} List of button elements.
   */
  get_buttons(product = undefined, component = undefined) {
    let buttons = [...this.buttons];

    if (product) {
      buttons = buttons.filter($button => $button.dataset.product === product);
    }

    if (component) {
      buttons = buttons.filter($button => $button.dataset.component === component);
    }

    return buttons;
  }

  /**
   * Update a Watch/Unwatch button for a product or component.
   * @param {HTMLButtonElement} $button Button element to be updated.
   * @param {Boolean} disabled Whether the button has to be disabled.
   * @param {Number} [watchId] Optional watch ID if the product or component is being watched.
   */
  update_button($button, disabled, watchId = undefined) {
    const { product, component } = $button.dataset;

    if (watchId) {
      $button.dataset.watchId = watchId;
      $button.textContent = $button.getAttribute('data-label-unwatch') || 'Unwatch';
      $button.title = component ?
        `Stop watching the ${component} component` :
        `Stop watching all components in the ${product} product`;
    } else {
      delete $button.dataset.watchId;

      $button.textContent = $button.getAttribute('data-label-watch') || 'Watch';
      $button.title = component ?
        `Start watching the ${component} component` :
        `Start watching all components in the ${product} product`;
    }

    $button.disabled = disabled;
  }

  /**
   * Called whenever a Watch/Unwatch button is clicked. Send a request to update the user's watch list, and update the
   * relevant buttons on the page.
   * @param {HTMLButtonElement} $button Clicked button element.
   */
  async button_onclick($button) {
    const { product, component, watchId, source } = $button.dataset;
    let message = '';
    let code = 0;

    // Disable the button until the request is complete
    $button.disabled = true;

    try {
      if (watchId) {
        await this.unwatch(watchId);

        if (component) {
          message = `You are no longer watching the ${component} component`;

          this.get_buttons(product, component).forEach($button => this.update_button($button, false));
        } else {
          message = `You are no longer watching all components in the ${product} product`;

          this.get_buttons(product).forEach($button => this.update_button($button, false));
        }
      } else {
        const watch = await this.watch(product, component);

        if (component) {
          message = `You are now watching the ${component} component`;

          this.get_buttons(product, component).forEach($button => this.update_button($button, false, watch.id));
        } else {
          message = `You are now watching all components in the ${product} product`;

          this.get_buttons(product).forEach($button => {
            if ($button.dataset.component) {
              this.update_button($button, true);
            } else {
              this.update_button($button, false, watch.id);
            }
          });
        }
      }
    } catch (ex) {
      message = 'Your watch list could not be updated. Please try again later.';
      code = 1;
    }

    this.show_message(message);
    this.track_event(source, watchId ? 'unwatch' : 'watch', component ? 'component' : 'product', code);
  }

  /**
   * Retrieve the current watch list, and initialize all the buttons.
   */
  async init() {
    try {
      const all_watches = await this.list();

      this.get_buttons().forEach($button => {
        const { product, component } = $button.dataset;
        const watches = all_watches.filter(watch => watch.product_name === product);
        const product_watch = watches.find(watch => !watch.component);

        if (!component) {
          // This button is for product watching
          this.update_button($button, false, product_watch ? product_watch.id : undefined);
        } else if (product_watch) {
          // Disabled the button because all the components in the product is being watched
          this.update_button($button, true);
        } else {
          const watch = watches.find(watch => watch.component_name === component);

          this.update_button($button, false, watch ? watch.id : undefined);
        }

        $button.addEventListener('click', () => this.button_onclick($button));
      });
    } catch (ex) {}
  }
};

window.addEventListener('DOMContentLoaded', () => new Bugzilla.ComponentWatching(), { once: true });
