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

/**
 * @file
 * This module contains utilities for interacting with DevTools
 * from the child process.
 */

import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";

// Create a variable to hold the cached ThemeChangeObserver which does not
// get created until a devtools context has been created.
let themeChangeObserver;

/**
 * An observer that watches for changes to the devtools theme and provides
 * that information to the devtools.panels.themeName API property, as well as
 * emits events for the devtools.panels.onThemeChanged event. It also caches
 * the current value of devtools.themeName.
 */
class ThemeChangeObserver extends EventEmitter {
  constructor(themeName, onDestroyed) {
    super();
    this.themeName = themeName;
    this.onDestroyed = onDestroyed;
    this.contexts = new Set();

    Services.cpmm.addMessageListener("Extension:DevToolsThemeChanged", this);
  }

  addContext(context) {
    if (this.contexts.has(context)) {
      throw new Error(
        "addContext on the ThemeChangeObserver was called more than once" +
          " for the context."
      );
    }

    context.callOnClose({
      close: () => this.onContextClosed(context),
    });

    this.contexts.add(context);
  }

  onContextClosed(context) {
    this.contexts.delete(context);

    if (this.contexts.size === 0) {
      this.destroy();
    }
  }

  onThemeChanged(themeName) {
    // Update the cached themeName and emit an event for the API.
    this.themeName = themeName;
    this.emit("themeChanged", themeName);
  }

  receiveMessage({ name, data }) {
    if (name === "Extension:DevToolsThemeChanged") {
      this.onThemeChanged(data.themeName);
    }
  }

  destroy() {
    Services.cpmm.removeMessageListener("Extension:DevToolsThemeChanged", this);
    this.onDestroyed();
    this.onDestroyed = null;
    this.contexts.clear();
    this.contexts = null;
  }
}

export var ExtensionChildDevToolsUtils = {
  /**
   * Creates an cached instance of the ThemeChangeObserver class and
   * initializes it with the current themeName. This cached instance is
   * destroyed when all of the contexts added to it are closed.
   *
   * @param {string} themeName The name of the current devtools theme.
   * @param {import("./ExtensionPageChild.sys.mjs").DevToolsContextChild} context
   *        The newly created devtools page context.
   */
  initThemeChangeObserver(themeName, context) {
    if (!themeChangeObserver) {
      themeChangeObserver = new ThemeChangeObserver(themeName, function () {
        themeChangeObserver = null;
      });
    }
    themeChangeObserver.addContext(context);
  },

  /**
   * Returns the cached instance of ThemeChangeObserver.
   *
   * @returns {ThemeChangeObserver} The cached instance of ThemeChangeObserver.
   */
  getThemeChangeObserver() {
    if (!themeChangeObserver) {
      throw new Error(
        "A ThemeChangeObserver must be created before being retrieved."
      );
    }
    return themeChangeObserver;
  },
};
