/* 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";

const {
  workerTargetSpec,
} = require("resource://devtools/shared/specs/targets/worker.js");

const {
  WebConsoleActor,
} = require("resource://devtools/server/actors/webconsole.js");
const { ThreadActor } = require("resource://devtools/server/actors/thread.js");
const { TracerActor } = require("resource://devtools/server/actors/tracer.js");
const {
  ObjectsManagerActor,
} = require("resource://devtools/server/actors/objects-manager.js");

const Targets = require("resource://devtools/server/actors/targets/index.js");

const makeDebuggerUtil = require("resource://devtools/server/actors/utils/make-debugger.js");
const {
  SourcesManager,
} = require("resource://devtools/server/actors/utils/sources-manager.js");

const {
  BaseTargetActor,
} = require("resource://devtools/server/actors/targets/base-target-actor.js");

class WorkerTargetActor extends BaseTargetActor {
  /**
   * Target actor for a worker in the content process.
   *
   * @param {DevToolsServerConnection} conn: The connection to the client.
   * @param {WorkerGlobalScope} workerGlobal: The worker global.
   * @param {object} workerDebuggerData: The worker debugger information
   * @param {string} workerDebuggerData.id: The worker debugger id
   * @param {string} workerDebuggerData.url: The worker debugger url
   * @param {string} workerDebuggerData.type: The worker debugger type
   * @param {number?} workerDebuggerData.relatedDocumentInnerWindowId: (optional)
   *                  If the worker is spawned from a document, the innerWindowId of it.
   * @param {boolean} workerDebuggerData.workerConsoleApiMessagesDispatchedToMainThread:
   *                  Value of the dom.worker.console.dispatch_events_to_main_thread pref
   * @param {object} sessionContext: The Session Context to help know what is debugged.
   *                                 See devtools/server/actors/watcher/session-context.js
   */
  constructor(conn, workerGlobal, workerDebuggerData, sessionContext) {
    super(conn, Targets.TYPES.WORKER, workerTargetSpec);

    // workerGlobal is needed by the console actor for evaluations.
    this.#workerGlobal = workerGlobal;
    this.sessionContext = sessionContext;

    // We don't have access to Ci from worker thread
    // 2 == nsIWorkerDebugger.TYPE_SERVICE
    // 1 == nsIWorkerDebugger.TYPE_SHARED
    if (workerDebuggerData.type == 2) {
      this.targetType = Targets.TYPES.SERVICE_WORKER;
    } else if (workerDebuggerData.type == 1) {
      this.targetType = Targets.TYPES.SHARED_WORKER;
    }
    this._relatedDocumentInnerWindowId =
      workerDebuggerData.relatedDocumentInnerWindowId;

    this._workerDebuggerData = workerDebuggerData;
    this._sourcesManager = null;
    this.workerConsoleApiMessagesDispatchedToMainThread =
      workerDebuggerData.workerConsoleApiMessagesDispatchedToMainThread;

    this.makeDebugger = makeDebuggerUtil.bind(null, {
      findDebuggees: () => {
        return [workerGlobal];
      },
      shouldAddNewGlobalAsDebuggee: () => true,
    });

    // needed by the console actor
    this.threadActor = new ThreadActor(this);

    // needed by the thread actor to communicate with the console when evaluating logpoints.
    this._consoleActor = new WebConsoleActor(this.conn, this);

    this.tracerActor = new TracerActor(this.conn, this);
    this.objectsManagerActor = new ObjectsManagerActor(this.conn, this);

    this.manage(this.threadActor);
    this.manage(this._consoleActor);
    this.manage(this.tracerActor);
    this.manage(this.objectsManagerActor);
  }

  #workerGlobal = null;

  get targetGlobal() {
    return this.#workerGlobal;
  }

  // Expose the worker URL to the thread actor.
  // so that it can easily know what is the base URL of all worker scripts.
  get workerUrl() {
    return this._workerDebuggerData.url;
  }

  form() {
    return {
      actor: this.actorID,
      targetType: this.targetType,

      consoleActor: this._consoleActor?.actorID,
      threadActor: this.threadActor?.actorID,
      tracerActor: this.tracerActor?.actorID,
      objectsManagerActor: this.objectsManagerActor?.actorID,

      id: this._workerDebuggerData.id,
      name: this._workerDebuggerData.name,
      type: this._workerDebuggerData.type,
      url: this._workerDebuggerData.url,
      relatedDocumentInnerWindowId: this._relatedDocumentInnerWindowId,
      traits: {
        // See trait description in browsing-context.js
        supportsTopLevelTargetFlag: false,
      },
    };
  }

  get dbg() {
    if (!this._dbg) {
      this._dbg = this.makeDebugger();
    }
    return this._dbg;
  }

  get sourcesManager() {
    if (this._sourcesManager === null) {
      this._sourcesManager = new SourcesManager(this.threadActor);
    }

    return this._sourcesManager;
  }

  destroy() {
    super.destroy();

    if (this._sourcesManager) {
      this._sourcesManager.destroy();
      this._sourcesManager = null;
    }

    this.#workerGlobal = null;
    this._dbg = null;
    this._consoleActor = null;
    this.threadActor = null;
  }
}
exports.WorkerTargetActor = WorkerTargetActor;
