import { nextFrame } from '@open-wc/testing'
import { sendMouse } from '@web/test-runner-commands'

export interface Point {
  x: number
  y: number
}

// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class UiMock {
  static keyDown(element: EventTarget, code: string, opts: KeyboardEventInit = {}): void {
    const defaults: KeyboardEventInit = {
      bubbles: true,
      cancelable: true,
      composed: true,
    }
    const options = { ...defaults, ...opts, ...{ code } }
    const down = new KeyboardEvent('keydown', options)
    element.dispatchEvent(down)
  }

  static keyUp(element: EventTarget, code: string, opts: KeyboardEventInit = {}): void {
    const defaults: KeyboardEventInit = {
      bubbles: true,
      cancelable: true,
      composed: true,
    }
    const options = { ...defaults, ...opts, ...{ code } }
    const down = new KeyboardEvent('keyup', options)
    element.dispatchEvent(down)
  }

  static async keyPress(element: EventTarget, code: string, opts?: KeyboardEventInit): Promise<void> {
    this.keyDown(element, code, opts)
    await nextFrame()
    this.keyUp(element, code, opts)
  }

  static getMiddleOfElement(element: Element): Point {
    const { x, y, width, height } = element.getBoundingClientRect()

    return {
      x: Math.floor(x + window.screenX + width / 2),
      y: Math.floor(y + window.scrollY + height / 2),
    }
  }

  /**
   * Moves the mouse from the `from` point to the `to` point using the Playwright API.
   * This means the mouse will actually move in the browser.
   *
   * @param from The {x,y} of the starting point
   * @param to The {x,y} of the ending point
   * @param steps The number of steps to take while moving
   */
  static async moveMouse(from: Point, to: Point, steps = 10): Promise<void> {
    const dx = to.x - to.x
    const dy = to.y - to.y
    const ix = Math.round(dx / steps)
    const iy = Math.round(dy / steps)
    let currentX = to.x
    let currentY = to.y

    await sendMouse({ type: 'move', position: [from.x, from.y] })
    // await executeServerCommand('take-screenshot', { name: 'move-start' });
    for (let i = 0; i < steps; i++) {
      currentX += ix
      currentY += iy
      await nextFrame()
      await sendMouse({ type: 'move', position: [currentX, currentY] })
      // await executeServerCommand('take-screenshot', { name: `pos${i}` });
    }

    // just making sure that we are not off
    await sendMouse({ type: 'move', position: [to.x, to.y] })
    // await executeServerCommand('take-screenshot', { name: `move-end` });
  }

  /**
   * Performs a drag and drop in the browser using the Playwright API.
   *
   * @param source The source element to drag
   * @param target The target where to drop the `source`
   * @param steps The number of mouse moves when dragging the `sources`
   */
  static async dragAndDrop(source: HTMLElement, target: HTMLElement, steps = 10): Promise<void> {
    const from = UiMock.getMiddleOfElement(source)
    const to = UiMock.getMiddleOfElement(target)

    await sendMouse({ type: 'move', position: [from.x, from.y] })
    await sendMouse({ type: 'down' })
    // await executeServerCommand('take-screenshot', { name: `after-down` });
    await nextFrame()
    await UiMock.moveMouse(from, to, steps)
    await sendMouse({ type: 'up' })
    // await executeServerCommand('take-screenshot', { name: `after-up` });
  }

  static getFileDragEvent(type: string, opts: { file?: File } = {}): DragEvent {
    let file: File
    if (opts.file) {
      file = opts.file
    } else {
      file = new File(['test'], 'test.txt', { type: 'text/plain' })
    }
    const dt = new DataTransfer()
    dt.items.add(file)
    return new DragEvent(type, {
      dataTransfer: dt,
      cancelable: true,
      composed: true,
      bubbles: true,
    })
  }
}
