/* global log -- eslint */

/**
 * @file Plugin hooks for IITC. This file defines the infrastructure for managing and executing hooks,
 * which are used to trigger custom plugin actions at specific points in the application lifecycle.
 * Plugins may listen to any number of events by specifying the name of the event and providing a function
 * to execute when an event occurs. Callbacks receive additional data created by the event as their first parameter.
 * The value is always an Object that contains more details.
 *
 * For example, this line will listen for portals to be added and print the data generated by the event to the console:
 * `window.addHook('portalAdded', function(data) { log.log(data) });`
 *
 * Boot hook: booting is handled differently because IITC may not yet be available.
 * Have a look at the plugins in plugins/. All code before `// PLUGIN START` and after `// PLUGIN END` is required
 * to successfully boot the plugin.
 *
 * Description of available hook events:
 * - `portalSelected`: Triggered when a portal on the map is selected or unselected.
 *                     Provides the GUID of both the selected and unselected portal.
 * - `mapDataRefreshStart`: Triggered at the start of refreshing map data.
 * - `mapDataEntityInject`: Triggered just as we start to render data.
 *                          Allows injecting cached entities into the map render.
 * - `mapDataRefreshEnd`: Triggered when the map data load is complete.
 * - `portalAdded`: Triggered when a portal has been received and is about to be added to its layer group.
 *                  Does not guarantee the portal will be visible or shown soon.
 *                  Portals added to hidden layers may never be shown.
 *                  Injection point is in `code/map_data.js#renderPortal` near the end.
 *                  Provides the Leaflet CircleMarker for the portal in the "portal" variable.
 * - `linkAdded`: Triggered when a link is about to be added to the map.
 * - `fieldAdded`: Triggered when a field is about to be added to the map.
 * - `portalRemoved`: Triggered when a portal has been removed.
 * - `linkRemoved`: Triggered when a link has been removed.
 * - `fieldRemoved`: Triggered when a field has been removed.
 * - `portalDetailsUpdated`: Fired after the details in the sidebar have been (re-)rendered.
 *                           Provides data about the selected portal.
 * - `commDataAvailable`: Runs after data for any of the chats has been received and processed, but not yet
 *                        been displayed. The data hash contains both the unprocessed raw ajax response as
 *                        well as the chat data that is going to be used for display.
 * - `publicChatDataAvailable`: Similar to `chatDataAvailable`, but for all chat only.
 * - `factionChatDataAvailable`: Similar to `publicChatDataAvailable`, but for faction chat.
 * - `alertsChatDataAvailable`: Similar to `publicChatDataAvailable`, but for alerts chat.
 * - `requestFinished`: **Deprecated**. Recommended to use `mapDataRefreshEnd` instead.
 *                      Called after each map data request is finished. Argument is {success: boolean}.
 * - `iitcLoaded`: Called after IITC and all plugins have loaded.
 * - `portalDetailLoaded`: Called when a request to load full portal detail completes.
 *                         Parameters are guid, success, details.
 * - `paneChanged`: Called when the current pane has changed. On desktop, this changes the current chat pane;
 *                  on mobile, it also switches between map, info, and other panes defined by plugins.
 * - `artifactsUpdated`: Called when the set of artifacts (including targets) has changed.
 *                       Parameters are old, new.
 * - `nicknameClicked`: Event triggered when a player's nickname is clicked.
 * - `geoSearch`: Event triggered during a geographic search.
 * - `search`: Event triggered during a search operation.
 *
 * @module hooks
 */

window._hooks = {};
window.VALID_HOOKS = []; // stub for compatibility

var isRunning = 0;

/**
 * Executes all callbacks associated with a given hook event.
 *
 * @function runHooks
 * @param {string} event - The name of the hook event.
 * @param {Object} [data] - Additional data to pass to each callback.
 * @returns {boolean} Returns `false` if the execution of the callbacks was interrupted, otherwise `true`.
 */
window.runHooks = function (event, data) {
  if (!window._hooks[event]) {
    return true;
  }
  var interrupted = false;
  isRunning++;
  $.each(window._hooks[event], function (ind, callback) {
    try {
      if (callback(data) === false) {
        interrupted = true;
        return false; // break from $.each
      }
    } catch (e) {
      log.error('error running hook ' + event, '\n' + e, '\ncallback: ', callback, '\ndata: ', data);
    }
  });
  isRunning--;
  return !interrupted;
};

window.pluginCreateHook = function () {}; // stub for compatibility

/**
 * Registers a callback function for a specified hook event.
 *
 * @function addHook
 * @param {string} event - The name of the hook event.
 * @param {Function} callback - The callback function to be executed when the event is triggered.
 */
window.addHook = function (event, callback) {
  if (typeof callback !== 'function') {
    throw new Error('Callback must be a function.');
  }

  if (!window._hooks[event]) {
    window._hooks[event] = [callback];
  } else {
    window._hooks[event].push(callback);
  }
};

/**
 * Removes a previously registered callback function for a specified hook event.
 * Callback must the SAME function to be unregistered.
 *
 * @function removeHook
 * @param {string} event - The name of the hook event.
 * @param {Function} callback - The callback function to be removed.
 */
window.removeHook = function (event, callback) {
  if (typeof callback !== 'function') {
    throw new Error('Callback must be a function.');
  }

  var listeners = window._hooks[event];
  if (listeners) {
    var index = listeners.indexOf(callback);
    if (index === -1) {
      log.warn("Callback wasn't registered for this event.");
    } else {
      if (isRunning) {
        listeners[index] = $.noop;
        window._hooks[event] = listeners = listeners.slice();
      }
      listeners.splice(index, 1);
    }
  }
};