// See this for inspiration:
// https://www.falldowngoboone.com/blog/native-form-validation-with-javascript/
import React, { type ReactNode, useEffect, useRef } from 'react'
import type { ProtoFormProps, ReturnValues } from './types'
import { populateForm } from './populateFields'

type Values = {
  [key: string]: FormDataEntryValue | FormDataEntryValue[] | null
}

export interface ReactProtoFormProps<T> extends Omit<ProtoFormProps<T>, 'children' | 'e_form'> {
  onInit?: (form: ReturnValues<T>) => void
  // Override children to use ReactNode instead of HTMLElement | HTMLElement[]
  children?: ReactNode;
  // Add new property
  className?: string;
  initialValues?: T;
}


const WhenItChanges =
  (
    e_form: React.RefObject<HTMLFormElement>,
    values: Values,
    keys = new Set<string>()
  ) =>
    () => {
      const allUniqueCheckboxKeys = new Set<string>()

      e_form?.current
        ?.querySelectorAll?.('input[type=checkbox][name]')
        // @ts-ignore, since it's a checkbox, it's always a checkbox
        .forEach(e => {
          // it's more than one, then string[], otherwise, it's a string
          if (
            // @ts-ignore, since it's a checkbox, it's always a checkbox
            e_form?.current?.querySelectorAll?.(
              // @ts-ignore, since it's a checkbox, it's always a checkbox
              `input[type=checkbox][name=${e?.name}]`
            ).length > 1
          ) {
            // @ts-ignore, since it's a checkbox, it's always a checkbox
            allUniqueCheckboxKeys.add(e.name)
          }
        })

      // @ts-ignore
      let formData = undefined

      if (e_form.current) {
        formData = new FormData(e_form.current)
        for (const key of formData.keys()) {
          keys.add(key)
        }

        keys.forEach(key => {
          if (allUniqueCheckboxKeys.has(key)) {
            values[key] = formData.getAll(key)
          } else {
            values[key] = formData.get(key)
          }
        })
      }

      return { values }
    }

export function ReactProtoForm<T>(props: ReactProtoFormProps<T>) {
  const e_form = useRef<HTMLFormElement>(null)


  useEffect(() => {
    if (!e_form.current) return

    if (props.initialValues) {
      populateForm(e_form.current, props.initialValues)
    }

    if (props.noValidate) e_form.current.noValidate = true

    const values: Values = {}
    const keys = new Set<string>()
    const blurredKeys = new Set<string>()

    const whenItChanges = WhenItChanges(e_form, values, keys)

    // Values are generally a string, but can be a string[] for checkboxes
    // since they have multiple values, it might be needed for multiselects too,
    // come back to this if needed

    const onSubmit = (e: Event) => {
      e.preventDefault()

      const { values } = whenItChanges()
      if (checkValidity()) {
        props.onSubmit?.({
          values: values as T,
          blurredKeys,
          keys,
          e_form: e_form.current as HTMLFormElement,
          lastTouched: '',
        })
      }
    }

    function checkValidity() {
      if (e_form.current?.checkValidity()) {
        props.onIsValid?.({
          values: values as T,
          blurredKeys,
          keys,
          e_form: e_form.current as HTMLFormElement,
          lastTouched: '',
        })
        return true
      } else {
        props.onIsInvalid?.({
          values: values as T,
          blurredKeys,
          keys,
          e_form: e_form.current as HTMLFormElement,
          lastTouched: '',
        })
        return false
      }
    }

    const checkForError = (e: Event) => {
      const target = e.target as HTMLInputElement
      if (!target) return

      // if the id is set, then we can inject the error message there
      // otherwise, we can still mark it as invalid
      // That allows us complete customization of the error message
      let e_error = e_form.current?.querySelector(
        `#${target.dataset.validationid}`
      ) as HTMLElement | null

      // if (checkValidity()) {
      if (target.checkValidity()) {
        target.removeAttribute('aria-invalid')
      } else {
        target.setAttribute('aria-invalid', 'true')
        if (e_error) {
          e_error.innerHTML = target.validationMessage
        }
      }

      checkValidity()
    }

    const onChange = (e: Event) => {
      const { values } = whenItChanges()
      props.onChange?.({
        values: values as T,
        blurredKeys,
        keys,
        e_form: e_form.current as HTMLFormElement,
        lastTouched: e?.name || e?.target?.name || e?.currentTarget?.name,
      })
      checkValidity()
    }

    const onBlur = (e: Event) => {
      const { values } = whenItChanges() // updates the values
      // @ts-ignore, since it always has a name
      blurredKeys.add(e.name || e.target.name)
      props.onBlur?.({
        // @ts-ignore, since it always has a name
        name: e.name || e.target.name,
        blurredKeys,
        values: values as T,
        keys,
        e_form: e_form.current as HTMLFormElement,
        lastTouched: e?.name || e?.target?.name || e?.currentTarget?.name || '',
      })

      checkForError(e)
    }

    const onKeyUp = (e: Event) => {
      checkForError(e)
    }

    // IMMEDIATE CHECK
    // console.log('checking immediately')
    if (checkValidity()) {
      let thing = whenItChanges()
      props?.onInit?.({
        values: thing.values as T,
        blurredKeys,
        keys,
        e_form: e_form.current as HTMLFormElement,
        lastTouched: '',
      })
    }

    e_form.current.addEventListener('change', onChange)
    e_form.current.addEventListener('submit', onSubmit)

    e_form.current
      .querySelectorAll?.('input[name], select[name], textarea[name]')
      .forEach(e => {
        e.addEventListener('blur', onBlur)
        e.addEventListener('keyup', onKeyUp)
        e.addEventListener('change', onChange)
      })

    return () => {
      if (!e_form.current) return
      e_form.current.removeEventListener('change', onChange)
      e_form.current.removeEventListener('submit', onSubmit)
      e_form.current
        .querySelectorAll?.('input[name], select[name], textarea[name]')
        .forEach(e => {
          e.removeEventListener('blur', onBlur)
          e.removeEventListener('keyup', onKeyUp)
          e.removeEventListener('change', onChange)
        })
    }
  }, [e_form])

  return (
    <form ref={e_form} className={props.className}>
      {props.children}
    </form>
  )
}