//////////////////////////////////////////////////////////////////////////////////////////
//   _  _ ____ _  _ ___  ____                                                           //
//   |_/  |__| |\ | |  \ |  |    This file belongs to Kando, the cross-platform         //
//   | \_ |  | | \| |__/ |__|    pie menu. Read more on github.com/kando-menu/kando     //
//                                                                                      //
//////////////////////////////////////////////////////////////////////////////////////////

// SPDX-FileCopyrightText: Simon Schneegans <code@simonschneegans.de>
// SPDX-License-Identifier: MIT

import { native } from './native';
import { LinuxBackend } from '../backend';
import { GeneralSettings, KeySequence, WindowDescription } from '../../../../common';
import { mapKeys } from '../../../../common/key-codes';
import { Settings } from '../../../../main/settings';

/**
 * This is a partial implementation of the Backend interface for wlroots-based Wayland
 * compositors. It can be used as a base class for other wlroots-based backends. It
 * provides the following functionality:
 *
 * - Moving the mouse pointer using the wlr-virtual-pointer-unstable-v1 protocol.
 * - Sending key input using the virtual-keyboard-unstable-v1 protocol.
 */

type PointerTimeoutBehavior =
  | 'top-left'
  | 'top-right'
  | 'bottom-left'
  | 'bottom-right'
  | 'center'
  | 'previously-reported-position';

export abstract class WLRBackend extends LinuxBackend {
  private previouslyReportedX = 0;
  private previouslyReportedY = 0;
  public generalSettings: Settings<GeneralSettings>;
  public defaultBehavior: PointerTimeoutBehavior = 'center';

  /** Uses the foreign-toplevel protocol to list currently open windows. */
  public async getOpenWindows(): Promise<WindowDescription[]> {
    return native.getOpenWindows();
  }

  /** Uses the foreign-toplevel protocol to focus the given window. */
  public async focusWindow(window: WindowDescription): Promise<void> {
    native.focusWindow(window.windowName, window.appName);
  }

  /** Uses the foreign-toplevel protocol to get the currently focused window. */
  protected async getFocusedWindow(): Promise<WindowDescription | null> {
    return native.getFocusedWindow();
  }

  /**
   * Moves the pointer by the given amount using the native module which uses the
   * wlr-virtual-pointer-unstable-v1 Wayland protocol.
   *
   * @param dx The amount of horizontal movement.
   * @param dy The amount of vertical movement.
   */
  public async movePointer(dx: number, dy: number) {
    try {
      native.movePointer(dx, dy);
    } catch (e) {
      console.error('Failed to move mouse pointer: ' + e.message);
    }
  }

  /**
   * This simulates a key sequence by sending the keys to the currently focused window
   * using the virtual-keyboard-unstable-v1 Wayland protocol. If one of the given keys in
   * the sequence is not known, an exception will be thrown.
   *
   * @param shortcut The keys to simulate.
   */
  protected override async simulateKeysImpl(keys: KeySequence) {
    // We first need to convert the given DOM key names to X11 key codes. If a key code is
    // not found, this throws an error.
    const keyCodes = mapKeys(keys, 'linux');

    // Now simulate the key presses. We wait a couple of milliseconds if the key has a
    // delay specified.
    for (let i = 0; i < keyCodes.length; i++) {
      if (keys[i].delay > 0) {
        await new Promise((resolve) => {
          setTimeout(resolve, keys[i].delay);
        });
      }

      native.simulateKey(keyCodes[i], keys[i].down);
    }
  }

  /**
   * This gets the pointer's position and work area size. Derived backends may use this in
   * their getWMInfo() implementations.
   *
   * This is only supported on Wayland compositors implementing the wlr-layer-shell
   * protocol. Also, it requires that the compositor automatically sends a pointer-enter
   * event when the surface is created. It seems that for instance Niri does this, but
   * Hyprland does not. Hence, on Hyprland, the method would block until the user moves
   * the pointer.
   */
  protected getPointerPositionAndWorkAreaSize() {
    const data = native.getPointerPositionAndWorkAreaSize(
      this.generalSettings.get('wlrootsPointerGetTimeoutMouse'),
      this.generalSettings.get('wlrootsPointerGetTimeoutTouch')
    );
    if (data.pointerGetTimedOut) {
      console.error('Pointer get timed out');
      switch (this.defaultBehavior) {
        case 'top-left':
          data.pointerX = 0;
          data.pointerY = 0;
          break;
        case 'top-right':
          data.pointerX = data.workAreaWidth;
          data.pointerY = 0;
          break;
        case 'bottom-left':
          data.pointerX = 0;
          data.pointerY = data.workAreaHeight;
          break;
        case 'bottom-right':
          data.pointerX = data.workAreaWidth;
          data.pointerY = data.workAreaHeight;
          break;
        case 'center':
          data.pointerX = data.workAreaWidth / 2;
          data.pointerY = data.workAreaHeight / 2;
          break;
        case 'previously-reported-position':
          data.pointerX = this.previouslyReportedX;
          data.pointerY = this.previouslyReportedY;
      }
    }
    this.previouslyReportedX = data.pointerX;
    this.previouslyReportedY = data.pointerY;
    return data;
  }
}
