/* 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/. */

import {
  ActionsProvider,
  ActionsResult,
} from "resource:///modules/ActionsProvider.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  QuickActionsLoaderDefault:
    "resource:///modules/QuickActionsLoaderDefault.sys.mjs",
  UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
});

// These prefs are relative to the `browser.urlbar` branch.
const ENABLED_PREF = "suggest.quickactions";
const MATCH_IN_PHRASE_PREF = "quickactions.matchInPhrase";
const MIN_SEARCH_PREF = "quickactions.minimumSearchString";

/**
 * A provider that matches the urlbar input to built in actions.
 */
class ProviderQuickActions extends ActionsProvider {
  get name() {
    return "ActionsProviderQuickActions";
  }

  isActive(queryContext) {
    return (
      lazy.UrlbarPrefs.get(ENABLED_PREF) &&
      !queryContext.searchMode &&
      queryContext.trimmedSearchString.length < 50 &&
      queryContext.trimmedSearchString.length >=
        lazy.UrlbarPrefs.get(MIN_SEARCH_PREF)
    );
  }

  async queryActions(queryContext) {
    let input = queryContext.trimmedLowerCaseSearchString;
    let results = await this.getActions(input);

    if (lazy.UrlbarPrefs.get(MATCH_IN_PHRASE_PREF)) {
      for (let [keyword, key] of this.#keywords) {
        if (input.includes(keyword)) {
          results.push(key);
        }
      }
    }

    // Remove invisible actions.
    results = results.filter(key => {
      const action = this.#actions.get(key);
      return action.isVisible?.() ?? true;
    });

    if (!results.length) {
      return null;
    }

    return results.map(key => {
      let action = this.#actions.get(key);
      return new ActionsResult({
        key,
        l10nId: action.label,
        icon: action.icon,
        dataset: {
          action: key,
          inputLength: queryContext.trimmedSearchString.length,
        },
        onPick: action.onPick,
      });
    });
  }

  async getActions(prefix) {
    await lazy.QuickActionsLoaderDefault.ensureLoaded();
    return [...(this.#prefixes.get(prefix) ?? [])];
  }

  getAction(key) {
    return this.#actions.get(key);
  }

  pickAction(_queryContext, _controller, element) {
    let action = element.dataset.action;
    let inputLength = Math.min(element.dataset.inputLength, 10);
    Glean.urlbarQuickaction.picked[`${action}-${inputLength}`].add(1);
    let options = this.#actions.get(action).onPick();
    if (options?.focusContent) {
      element.ownerGlobal.gBrowser.selectedBrowser.focus();
    }
  }

  /**
   * Adds a new QuickAction.
   *
   * @param {string} key A key to identify this action.
   * @param {string} definition An object that describes the action.
   */
  addAction(key, definition) {
    this.#actions.set(key, definition);
    definition.commands.forEach(cmd => this.#keywords.set(cmd, key));
    this.#loopOverPrefixes(definition.commands, prefix => {
      let result = this.#prefixes.get(prefix);
      if (result) {
        if (!result.includes(key)) {
          result.push(key);
        }
      } else {
        result = [key];
      }
      this.#prefixes.set(prefix, result);
    });
  }

  /**
   * Removes an action.
   *
   * @param {string} key A key to identify this action.
   */
  removeAction(key) {
    let definition = this.#actions.get(key);
    this.#actions.delete(key);
    definition.commands.forEach(cmd => this.#keywords.delete(cmd));
    this.#loopOverPrefixes(definition.commands, prefix => {
      let result = this.#prefixes.get(prefix);
      if (result) {
        result = result.filter(val => val != key);
      }
      this.#prefixes.set(prefix, result);
    });
  }

  // A map from keywords to an action.
  #keywords = new Map();

  // A map of all prefixes to an array of actions.
  #prefixes = new Map();

  // The actions that have been added.
  #actions = new Map();

  #loopOverPrefixes(commands, fun) {
    for (const command of commands) {
      // Loop over all the prefixes of the word, ie
      // "", "w", "wo", "wor", stopping just before the full
      // word itself which will be matched by the whole
      // phrase matching.
      for (let i = 1; i <= command.length; i++) {
        let prefix = command.substring(0, command.length - i);
        fun(prefix);
      }
    }
  }
}

export var ActionsProviderQuickActions = new ProviderQuickActions();
