import React, { useState, useRef } from "react"
import PropTypes from "prop-types"

const noop = () => {}

/**
 * This component was originally developed to support an input for an MFA token.
 *
 * Go ahead and evolve this component per your needs - I'm sure we'll learn more about how this should behave.
 *
 * If we discover that this approach isn't really working, Joseph has also suggested a different approach for us to try.
 * [Check it out](https://codesandbox.io/s/old-wood-rqwij6?file=/src/MultiBox.js)
 */
const MultiBoxInput = ({
  name,
  numberOfCells,
  inputAttrs = {},
  cellClasses = "",
  containerClasses = "",
  onChange = noop,
  value: initialValue
}) => {
  const [value, setValue] = useState(Array.from(initialValue || ""))
  const inputContainerRef = useRef(null)

  const handlePaste = event => {
    const text = event.clipboardData.getData('text')

    // truncate the pasted value to the number of cells available
    const newValue = Array.from(text).slice(0, numberOfCells)
    setValue(newValue)

    // move the cursor to the last cell
    focusInput(newValue.length - 1)

    onChange({ value: newValue.join("") })
  }

  const updateCell = (index, event) => {
    const newValue = Object.assign([], value, {[index]: event.target.value})
    setValue(newValue)

    // if cell was filled, and there is a next cell,
    // move to next cell
    if(newValue[index] !== "" && index < numberOfCells - 1) {
      focusInput(index + 1)
    }

    // call parent hook
    onChange({ value: newValue.join("") })
  }

  const handleKeyStroke = (index, event) => {
    if ((event.ctrlKey || event.metaKey)) {
      switch(event.key) {
        case "a":
          selectAll(event)
          break
        case "c":
          copyAll(event)
          break
        case "x":
          cutAll(event)
          break
        case ("Backspace" || "Delete"):
          deleteAll(event)
          break
      }
    }

    if (event.key === "Backspace" || event.key === "Delete") {
      const selection = window.getSelection()
      const inputContainerNode = inputContainerRef.current

      const inputs = Array.from(inputContainerNode.querySelectorAll('input'))

      const allSelected = inputs.every(input => selection.containsNode(input))

      if (allSelected) {
        deleteAll(event)
      } else {
        moveToPreviousCell(index)
      }
    }
  }

  // If the current cell is empty, there is a previous cell,
  // move focus to the previous cell
  const moveToPreviousCell = index => {
    if (value[index] !== "" && value[index] !== undefined) return

    focusInput(index - 1)
  }

  const selectAll = event => {
    event.preventDefault()
    event.stopPropagation()

    const selection = window.getSelection()
    const inputContainerNode = inputContainerRef.current

    selection.selectAllChildren(inputContainerNode)
  }

  const copyAll = event => {
    event.preventDefault()
    event.stopPropagation()

    const input = value.join("")

    navigator.clipboard.writeText(input)
  }

  const cutAll = event => {
    event.preventDefault()
    event.stopPropagation()

    const input = value.join("")
    navigator.clipboard.writeText(input)

    setValue(Array.from({ length: numberOfCells }))

    focusInput(0)
  }

  const deleteAll = event => {
    event.preventDefault()
    event.stopPropagation()

    setValue(Array.from({ length: numberOfCells }))

    focusInput(0)
  }

  const focusInput = index => {
    const inputContainerNode = inputContainerRef.current
    const input = inputContainerNode.querySelectorAll('input')[index]

    if (!input) return

    input.focus()
  }

  const cellClassNames = `outline-none border border-gray-200 focus:border-blue-500 leading-10 px-2 md:px-4 rounded mr-1 text-4xl w-full ${cellClasses}`

  return(
    <div aria-label={name} className={`flex flex-row ${containerClasses}`} ref={inputContainerRef}>
      {
        Array.from({ length: numberOfCells }).map((_, index) => {
          return(
            <input
              style={{ maxWidth: 64, height: 80 }}
              autoFocus={index == 0 ? true : false}
              key={`multibox-cell-${index}`}
              maxLength={1}
              className={cellClassNames}
              value={value[index] || ""}
              onKeyDown={e => handleKeyStroke(index, e)}
              onChange={e => updateCell(index, e)}
              onPaste={handlePaste}
              inputMode="numeric"
              autoComplete="one-time-code"
              aria-required={true}
              {...inputAttrs}
            />
          )
        })
      }
    </div>
  )
}

MultiBoxInput.propTypes = {
  /** Name that will be applied to the component, used in a11y tools. */
  name: PropTypes.string.isRequired,
  /** How many cells should the input comprise. */
  numberOfCells: PropTypes.number.isRequired,
  /** HTML attributes that will be applied to each individual input cell */
  inputAttrs: PropTypes.object,
  /** CSS classNames that will be applied to each individual cell */
  cellClasses: PropTypes.string,
  /** CSS classNames that will be applied to the container that wraps the cells */
  containerClasses: PropTypes.string,
  /** Function that will be called when the input's value changes. The argument passed is the component's value as a string. */
  onChange: PropTypes.func,
  /** Value as a string. It will be split into an array of chars */
  value: PropTypes.string
}

export default MultiBoxInput
