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

"use strict";

// List of all command modules
// (please try to keep the list alphabetically sorted)
/* eslint sort-keys: "error" */
/* eslint-enable sort-keys */
const Commands = {
  inspectedWindowCommand:
    "devtools/shared/commands/inspected-window/inspected-window-command",
  inspectorCommand: "devtools/shared/commands/inspector/inspector-command",
  networkCommand: "devtools/shared/commands/network/network-command",
  resourceCommand: "devtools/shared/commands/resource/resource-command",
  rootResourceCommand:
    "devtools/shared/commands/root-resource/root-resource-command",
  scriptCommand: "devtools/shared/commands/script/script-command",
  targetCommand: "devtools/shared/commands/target/target-command",
  targetConfigurationCommand:
    "devtools/shared/commands/target-configuration/target-configuration-command",
  threadConfigurationCommand:
    "devtools/shared/commands/thread-configuration/thread-configuration-command",
};
/* eslint-disable sort-keys */

/**
 * For a given descriptor and its related Targets, already initialized,
 * return the dictionary with all command instances.
 * This dictionary is lazy and commands will be loaded and instanciated on-demand.
 */
async function createCommandsDictionary(descriptorFront) {
  // Bug 1675763: Watcher actor is not available in all situations yet.
  let watcherFront;
  const supportsWatcher = descriptorFront.traits?.watcher;
  if (supportsWatcher) {
    watcherFront = await descriptorFront.getWatcher();
  }
  const { client } = descriptorFront;

  const allInstantiatedCommands = new Set();

  const dictionary = {
    // Expose both client and descriptor for legacy codebases, or tests.
    // But ideally only commands should interact with these two objects
    client,
    descriptorFront,
    watcherFront,

    // Expose for tests
    waitForRequestsToSettle() {
      return descriptorFront.client.waitForRequestsToSettle();
    },

    // Boolean flag to know if the DevtoolsClient should be closed
    // when this commands happens to be destroyed.
    // This is set by:
    // * commands-from-url in case we are opening a toolbox
    //   with a dedicated DevToolsClient (mostly from about:debugging, when the client isn't "cached").
    // * CommandsFactory, when we are connecting to a local tab and expect
    //   the client, toolbox and descriptor to all follow the same lifecycle.
    shouldCloseClient: true,

    /**
     * Destroy the commands which will destroy:
     * - all inner commands,
     * - the related descriptor,
     * - the related DevToolsClient (not always)
     */
    async destroy() {
      descriptorFront.off("descriptor-destroyed", this.destroy);

      // Destroy all inner command modules
      for (const command of allInstantiatedCommands) {
        if (typeof command.destroy == "function") {
          command.destroy();
        }
      }
      allInstantiatedCommands.clear();

      // Destroy the descriptor front, and all its children fronts.
      // Watcher, targets,...
      //
      // Note that DescriptorFront.destroy will be null because of Pool.destroy
      // when this function is called while the descriptor front itself is being
      // destroyed.
      if (!descriptorFront.isDestroyed()) {
        await descriptorFront.destroy();
      }

      // Close the DevToolsClient. Shutting down the connection
      // to the debuggable context and its DevToolsServer.
      //
      // See shouldCloseClient jsdoc about this condition.
      if (this.shouldCloseClient) {
        await client.close();
      }
    },
  };
  dictionary.destroy = dictionary.destroy.bind(dictionary);

  // Automatically destroy the commands object if the descriptor
  // happens to be destroyed. Which means that the debuggable context
  // is no longer debuggable.
  descriptorFront.on("descriptor-destroyed", dictionary.destroy);

  for (const name in Commands) {
    loader.lazyGetter(dictionary, name, () => {
      const Constructor = require(Commands[name]);
      const command = new Constructor({
        // Commands can use other commands
        commands: dictionary,

        // The context to inspect identified by this descriptor
        descriptorFront,

        // The front for the Watcher Actor, related to the given descriptor
        // This is a key actor to watch for targets and resources and pull global actors running in the parent process
        watcherFront,

        // From here, we could pass DevToolsClient, or any useful protocol classes...
        // so that we abstract where and how to fetch all necessary interfaces
        // and avoid having to know that you might pull the client via descriptorFront.client
      });
      allInstantiatedCommands.add(command);
      return command;
    });
  }

  return dictionary;
}
exports.createCommandsDictionary = createCommandsDictionary;
