import React, { FC, useRef, useState, FormEvent, FocusEvent, MutableRefObject } from 'react'

import { formatClassList, joinStrings } from '../../utils'


type FormFieldProps = {
  id?: string,
  invalidMessage: string,
  label: string,
  name?: string,
  optional: boolean,
  setValid(valid: boolean): void,
  setValue(value: string): void,
  type?: string, 
  validator(
    elem:
      HTMLInputElement
      | HTMLTextAreaElement
      | HTMLDivElement
      | null
  ): boolean,
  [other:string]: unknown
}

const LABEL: string = `
  block
  mt-4
  mb-1
  leading-tight
  text-base
  text-bscs-gray-600
  text-left
  tracking-wider
`

const INPUT: string = `
  block
  focus:bg-bscs-gray-100
  duration-300
  ease-in-out
  focus
  min-h-2
  outline-none
  p-2
  rounded
  md:rounded-md
  shadow-inner
  text-base
  focus:text-bscs-gray-800
  transform
  transition
  w-full
`

const UNTOUCHED: string = `
  bg-bscs-gray-200
`

const INVALID: string = `
  ${INPUT}
  bg-bscs-red-100
`

const VALID: string = `
  ${INPUT}
  bg-bscs-green-100
`

const INVALID_MESSAGE: string = `
  leading-tight
  mb-3
  text-base
  text-bscs-red-800
  text-left
  tracking-wider

  transition-all
  duration-300
  ease-in-out
  -z-1
`

const handleValidation = (
  e: FormEvent | FocusEvent,
  optional: boolean,
  elem: MutableRefObject<HTMLInputElement | HTMLTextAreaElement |  null>,
  setValid: (valid: boolean) => void,
  setValue: (value: string) => void,
  validator: (
    elem:
      HTMLInputElement
      | HTMLTextAreaElement
      | HTMLDivElement
      | null
  ) => boolean,
  setShowInvalidMessage: (show: boolean) => void,
  touched: boolean,
  showInvalidMessage: boolean
): boolean => {
  e.preventDefault()

  if (!elem || !elem.current) {
    return false
  }

  setValue(elem.current.value)
  
  const valid: boolean = validator(elem.current)

  if ((!optional && valid)
      || (optional && (!elem.current.value || valid))
  ) {
    setValid(true)
    elem.current.classList.value = formatClassList(VALID)
    
    if (touched && showInvalidMessage) { 
      setShowInvalidMessage(false)
    }
    return true
  }
  
  setValid(false)
  elem.current.classList.value = formatClassList(INVALID)
  
  if (touched && !showInvalidMessage) { 
    setShowInvalidMessage(true)
  }
  
  return false
}

const FormField:FC<FormFieldProps> = ({
  id,
  invalidMessage,
  label,
  name,
  optional,
  setValid,
  setValue,
  type='text',
  validator,
  ...other
}: FormFieldProps) => {
  const ref = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null)

  const [showInvalidMessage, setShowInvalidMessage] = useState(false)
  const [touched, setTouched] = useState(false)

  const labelClassList: string = formatClassList(LABEL)
  const inputClassList: string = formatClassList(joinStrings(' ', INPUT, UNTOUCHED))
  const invalidMessageClassList: string = formatClassList(INVALID_MESSAGE)

  const maxLength: number | undefined = type === 'text' ? 50 : undefined
  const minLength: number | undefined = type === 'text' ? 1  : undefined

  const Tag = type === 'textarea' ? 'textarea' : 'input'
  
  return (
    <div {...other}>
      <label
        htmlFor={id}
        className={labelClassList}
      >
        {optional
          ? joinStrings(' ', label, '(Optional)')
          : label
        }
      </label>
      <Tag
        ref={(instance: any) => ref.current = instance}
        className={inputClassList}
        onBlur={(e: FocusEvent) => {
          const valid = handleValidation(
            e,
            optional,
            ref,
            setValid,
            setValue,
            validator,
            setShowInvalidMessage,
            true,
            showInvalidMessage
          )
          
          if (!touched && !valid) {
            setTouched(true)
          }

          if (
            touched
            && (valid || (e.target as HTMLInputElement).value === "")
          ) {
            setTouched(false)
          }
        }}
        onInput={(e: FormEvent) => {
          handleValidation(
            e,
            optional,
            ref,
            setValid,
            setValue,
            validator,
            setShowInvalidMessage,
            touched,
            showInvalidMessage
          )
        }}
        id={id} 
        type={type}
        name={name}
        minLength={minLength}
        maxLength={maxLength}
        style={{ maxWidth: '100%' }}
      />
      <div
        className={invalidMessageClassList}
        style={{
          marginTop: showInvalidMessage
            ? '0.25rem'
            : '-2rem'
        }}
      >
        {invalidMessage}
      </div>
    </div>
  )
}

export default FormField

