import { useState, useEffect, useRef } from 'react';
import fillForm from './_fill-form';
import fillState from './_fill-state';
import findPath from './_find-path';

export const useForm = <ST extends Record<string, any>>(
  initialState: Partial<ST> = {},
  validate?: (state: Partial<ST>) => boolean,
) => {
  const [filledState, setFilledState] = useState<boolean>(false);
  const [state, setState] = useState<Partial<ST>>(initialState);
  const initializedRef = useRef(false);
  const stateRef = useRef<Partial<ST>>(initialState);
  const observerRef = useRef<MutationObserver | null>(null);
  const formRef = useRef<HTMLFormElement | null>(null);

  const onReset = () => {
    stateRef.current = {} as ST;
    setState({} as ST);
  };
  const onSubmit = (ev: SubmitEvent) => {
    ev.preventDefault();
    setState(stateRef.current);
  };

  const initialize = (theForm: HTMLFormElement) => {
    addEvents(theForm);
    fillState({ theForm, initialState, update, setFilledState, stateRef });
    fillForm({ theForm, state: stateRef.current });
    initializedRef.current = true;
  };

  const destroy = (theForm: HTMLFormElement | null) => {
    initializedRef.current = false;
    removeEventListeners(theForm);
    setState(initialState);
    stateRef.current = state;
  };

  const addEvents = (theForm: HTMLFormElement) => {
    theForm.addEventListener('input', onChange);
    theForm.addEventListener('change', onChange);
    theForm.addEventListener('reset', onReset);
    theForm.addEventListener('submit', onSubmit);
  };

  const removeEventListeners = (theForm: HTMLFormElement | null) => {
    if (!theForm) return;
    theForm.removeEventListener('input', onChange);
    theForm.removeEventListener('change', onChange);
    theForm.removeEventListener('reset', onReset);
    theForm.removeEventListener('submit', onSubmit);
  };

  useEffect(() => {
    const formElement = formRef.current;
    if (formElement && !filledState) {
      initialize(formElement);
    }
  }, [formRef, filledState]);

  // start everything
  useEffect(() => {
    const { current: formElement } = formRef;
    if (!formElement) {
      const observer = new MutationObserver(() => {
        if (formRef.current && !initializedRef.current) {
          initialize(formRef.current);
        }
        if (!formRef.current && initializedRef.current) {
          destroy(formRef.current);
        }
      });
      observerRef.current = observer;

      observer.observe(document.body, {
        subtree: true,
        childList: true,
      });

      return () => {
        observer.disconnect();
      };
    }
    return () => {};
  }, []);

  const update: StateSetter<Partial<ST>> = (param) => {
    let newState;
    if (typeof param === 'function') {
      newState = param(stateRef.current);
    } else {
      newState = param;
    }
    fillForm({ theForm: formRef.current!, state: newState });
    stateRef.current = newState;
    setState(newState);
  };

  const onChange = (ev: Event) => {
    const target = ev.target as HTMLInputElement | HTMLSelectElement;
    const { name, value, dataset = {} } = target;

    if (!name) return;
    const type = dataset['type'] || target.type;
    const path = findPath(target, formRef.current);

    const newState = { ...stateRef.current };
    let targetObj = newState as any;
    for (let prop of path) {
      targetObj[prop] = targetObj[prop] || {};
      targetObj = targetObj[prop];
    }

    const isTrueFalse =
      target.type === 'radio' &&
      formRef.current?.[name]?.length === 2 &&
      Array.from(formRef.current[name] as NodeListOf<HTMLInputElement>).every((el: HTMLInputElement) =>
        ['true', 'false'].includes(el.value),
      );

    //Case for Checkbox:
    if (isTrueFalse) {
      targetObj[name] = value === 'true' ? true : value === 'false' ? false : null;
    } else if (target.type === 'checkbox') {
      const { checked } = target;
      const count = formRef.current?.[name].length;
      if (count > 1) {
        targetObj[name] = [...formRef.current?.[name]]?.filter((c) => c.checked).map((c) => c.value);
      } else {
        targetObj[name] = checked;
      }
    } else if (target.nodeName === 'SELECT' && target.multiple) {
      const options = Array.from(target.querySelectorAll('option'));
      targetObj[name] = options.filter((o: HTMLOptionElement) => o.selected).map((o: HTMLOptionElement) => o.value);
    } else {
      if (['number', 'range'].includes(type)) {
        if (value === '') {
          targetObj[name] = null;
        } else {
          targetObj[name] = +value;
        }
      } else {
        targetObj[name] = value;
      }
    }

    stateRef.current = newState;
    setState(newState);
  };

  const clear = () => {
    setState(initialState);
    formRef.current?.reset();
  };

  const isValid = !!validate ? validate(state) : true;

  return { state, formState: state, formRef, update, reset: clear, clear, isValid };
};

export type UseFormHookType = ReturnType<typeof useForm>;

// const triggerChange = (element) => {
//   console.log('triggering on ', element);
//   const event = new Event('change');
//   element.dispatchEvent(event);
// };
