const modifierState = {
  Alt: false,
  AltGraph: false,
  Control: false,
  Meta: false,
  Shift: false,
}

const modifierToInit = {
  Alt: "altKey",
  AltGraph: "modifierAltGraph",
  Control: "ctrlKey",
  Meta: "metaKey",
  Shift: "shiftKey",
}

function eventInit(overrides = {}) {
  let init = { bubbles: true, ...overrides }
  for (let [modifier, field] of Object.entries(modifierToInit)) {
    if (modifierState[modifier]) {
      init[field] = true
    }
  }
  return init
}

function keyInfo(input) {
  if (/^Key[A-Z]$/.test(input)) {
    return { key: input.slice(3).toLowerCase(), code: input }
  }
  if (/^F\d+$/.test(input)) {
    return { key: input, code: input }
  }
  if (modifierToInit[input]) {
    return { key: input, code: `${input}Left` }
  }
  if (input.length === 1) {
    return { key: input, code: `Key${input.toUpperCase()}` }
  }
  return { key: input, code: input }
}

function dispatch(target, type, input) {
  let info = keyInfo(input)
  target.dispatchEvent(new KeyboardEvent(type, eventInit(info)))
}

async function keyboard(input, target = window) {
  let tokens = input.match(/\{[^}]+\}|./g) || []

  for (let token of tokens) {
    if (token.startsWith("{/")) {
      let modifier = token.slice(2, -1)
      dispatch(target, "keyup", modifier)
      modifierState[modifier] = false
    } else if (token.startsWith("{") && token.endsWith(">}" )) {
      let modifier = token.slice(1, -2)
      modifierState[modifier] = true
      dispatch(target, "keydown", modifier)
    } else {
      let key = token.startsWith("{") ? token.slice(1, -1) : token
      dispatch(target, "keydown", key)
      dispatch(target, "keyup", key)
    }
  }
}

export const userEvent = {
  keyboard(input) {
    return keyboard(input, window)
  },
  type(element, input) {
    return keyboard(input, element)
  },
}
