/* 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 { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

export class Input extends Domain {
  // commands

  /**
   * Simulate key events.
   *
   * @param {object} options
   *        - autoRepeat (not supported)
   *        - code (not supported)
   *        - key
   *        - isKeypad (not supported)
   *        - location (not supported)
   *        - modifiers
   *        - text (not supported)
   *        - type
   *        - unmodifiedText (not supported)
   *        - windowsVirtualKeyCode
   *        - nativeVirtualKeyCode (not supported)
   *        - keyIdentifier (not supported)
   *        - isSystemKey (not supported)
   */
  async dispatchKeyEvent(options = {}) {
    // missing code, text, unmodifiedText, autorepeat, location, iskeypad
    const { key, modifiers, type, windowsVirtualKeyCode } = options;
    const { alt, ctrl, meta, shift } = Input.Modifier;

    let domType;
    if (type == "keyDown" || type == "rawKeyDown") {
      // 'rawKeyDown' is passed as type by puppeteer for all non-text keydown events:
      // See https://github.com/GoogleChrome/puppeteer/blob/2d99d85976dcb28cc6e3bad4b6a00cd61a67a2cf/lib/Input.js#L52
      // For now we simply map rawKeyDown to keydown.
      domType = "keydown";
    } else if (type == "keyUp" || type == "char") {
      // 'char' is fired as a single key event. Behind the scenes it will trigger keydown,
      // keypress and keyup. `domType` will only be used as the event to wait for.
      domType = "keyup";
    } else {
      throw new Error(`Unknown key event type ${type}`);
    }

    const { browser } = this.session.target;
    const browserWindow = browser.ownerGlobal;

    const EventUtils = this._getEventUtils(browserWindow);
    const eventId = await this.executeInChild(
      "_addContentEventListener",
      domType
    );

    if (type == "char") {
      // type == "char" is used when doing `await page.keyboard.type( 'I’m a list' );`
      // the ’ character will be calling dispatchKeyEvent only once with type=char.
      EventUtils.synthesizeKey(key, {}, browserWindow);
    } else {
      // Non printable keys should be prefixed with `KEY_`
      const eventUtilsKey = key.length == 1 ? key : "KEY_" + key;
      const eventInfo = {
        keyCode: windowsVirtualKeyCode,
        type: domType,
        altKey: !!(modifiers & alt),
        ctrlKey: !!(modifiers & ctrl),
        metaKey: !!(modifiers & meta),
        shiftKey: !!(modifiers & shift),
      };
      EventUtils.synthesizeKey(eventUtilsKey, eventInfo, browserWindow);
    }

    await this.executeInChild("_waitForContentEvent", eventId);
  }

  /**
   * Simulate mouse events.
   *
   * @param {object} options
   * @param {string} options.type
   * @param {number} options.x
   * @param {number} options.y
   * @param {number} options.modifiers
   * @param {number} options.timestamp [Not Supported]
   * @param {string} options.button
   * @param {number} options.buttons [Not Supported]
   * @param {string} options.clickCount
   * @param {number} options.deltaX [Not Supported]
   * @param {number} options.deltaY [Not Supported]
   * @param {string} options.pointerType [Not Supported]
   */
  async dispatchMouseEvent(options = {}) {
    const { button, clickCount, modifiers, type, x, y } = options;
    const { alt, ctrl, meta, shift } = Input.Modifier;

    let domType;
    if (type === "mousePressed") {
      domType = "mousedown";
    } else if (type === "mouseReleased") {
      domType = "mouseup";
    } else if (type === "mouseMoved") {
      domType = "mousemove";
    } else {
      throw new Error(`Mouse type is not supported: ${type}`);
    }

    if (domType === "mousedown" && button === "right") {
      domType = "contextmenu";
    }

    const buttonID = Input.Button[button] || Input.Button.left;
    const { browser } = this.session.target;
    const currentWindow = browser.ownerGlobal;

    const EventUtils = this._getEventUtils(currentWindow);
    const eventId = await this.executeInChild(
      "_addContentEventListener",
      domType
    );

    EventUtils.synthesizeMouse(browser, x, y, {
      type: domType,
      button: buttonID,
      clickCount: clickCount || 1,
      altKey: !!(modifiers & alt),
      ctrlKey: !!(modifiers & ctrl),
      metaKey: !!(modifiers & meta),
      shiftKey: !!(modifiers & shift),
    });

    await this.executeInChild("_waitForContentEvent", eventId);
  }

  /**
   * Memoized EventUtils getter.
   */
  _getEventUtils(win) {
    if (!this._eventUtils) {
      this._eventUtils = {
        window: win,
        parent: win,
        _EU_Ci: Ci,
        _EU_Cc: Cc,
      };
      Services.scriptloader.loadSubScript(
        "chrome://remote/content/external/EventUtils.js",
        this._eventUtils
      );
    }
    return this._eventUtils;
  }
}

Input.Button = {
  left: 0,
  middle: 1,
  right: 2,
  back: 3,
  forward: 4,
};

Input.Modifier = {
  alt: 1,
  ctrl: 2,
  meta: 4,
  shift: 8,
};
