import { useCallback, useEffect, useState } from 'react';

import { isNullOrUndefined } from '@/utils/validator';

import { getDifferences, hasAnyTrue } from './utils';

export type FormidableDeltaConfig<TYPE> = {
  omit?: string[]; // (keyof TYPE)[];
  only?: string[]; // (keyof TYPE)[];
};

export type FormidableConfig = {
  enableReset?: boolean;
  enableModifyNotification?: boolean;
  singleLineForm?: boolean;
};
const initialFormConfig: Required<FormidableConfig> = {
  enableReset: true,
  enableModifyNotification: true,
  singleLineForm: false,
};

export const useFormidable = <TYPE extends object = object>({
  initialValues = {} as TYPE,
  configuration: inputConfiguration = initialFormConfig,
  deltaConfig,
  resolver = (_data: TYPE) => {
    return {};
  },
}: {
  initialValues?: TYPE;
  configuration?: FormidableConfig;
  deltaConfig?: FormidableDeltaConfig<TYPE>;
  resolver?: (data: any) => Record<string, string>;
}) => {
  const configuration: Required<FormidableConfig> = {
    ...initialFormConfig,
    ...inputConfiguration,
  };
  const [values, setValues] = useState<TYPE>({ ...initialValues } as TYPE);
  const [errors, setErrors] = useState<object>({});
  const [initialData, setInitialData] = useState<TYPE>(initialValues);
  const [isModify, setIsModify] = useState<{ [key: string]: boolean }>({});
  const [isFormModified, setIsFormModified] = useState<boolean>(false);
  useEffect(() => {
    setInitialData((previous) => {
      //console.log(`FORMIDABLE: useMemo initial Values(${JSON.stringify(initialValues)})`);
      const previousJson = JSON.stringify(previous);
      const newJson = JSON.stringify(initialValues);
      if (previousJson === newJson) {
        return previous;
      }
      //console.log(`FORMIDABLE:     ==> update new values`);
      setValues({ ...initialValues });
      const ret = getDifferences(initialValues, initialValues);
      setIsModify(ret);
      setIsFormModified(hasAnyTrue(ret));
      return initialValues;
    });
  }, [
    initialValues,
    setInitialData,
    setValues,
    setIsModify,
    setIsFormModified,
  ]);
  const restoreValues = useCallback(() => {
    setValues({ ...initialData });
  }, [setValues, initialData]);
  const setValuesExternal = useCallback(
    (data: object) => {
      //console.log(`FORMIDABLE: setValuesExternal(${JSON.stringify(data)}) ==> keys=${Object.keys(data)}`);
      setValues((previous) => {
        const newValues = { ...previous, ...data };
        const ret = getDifferences(initialData, newValues);
        setIsModify(ret);
        setIsFormModified(hasAnyTrue(ret));
        setErrors(resolver(newValues));
        return newValues;
      });
    },
    [setValues, initialData, setErrors, setIsFormModified, setIsModify]
  );
  const restoreValue = useCallback(
    (data: object) => {
      setValues((previous) => {
        const keysInPrevious = Object.keys(previous);
        const newValue = { ...previous };
        let countModify = 0;
        //console.log(`restore value ${JSON.stringify(data, null, 2)}`);
        for (const key of Object.keys(data)) {
          if (!keysInPrevious.includes(key)) {
            continue;
          }
          if (data[key] === false) {
            continue;
          }
          newValue[key] = initialValues[key];
          countModify++;
        }
        if (countModify === 0) {
          return previous;
        }
        //console.log(`initialData data ${JSON.stringify(initialData, null, 2)}`);
        //console.log(`New data ${JSON.stringify(newValue, null, 2)}`);
        const ret = getDifferences(initialData, newValue);
        setIsModify(ret);
        setIsFormModified(hasAnyTrue(ret));
        return newValue;
      });
    },
    [setValues, initialData, setIsFormModified, setIsModify]
  );
  const getDeltaData = useCallback(
    ({ omit = [], only }: FormidableDeltaConfig<TYPE> = {}) => {
      const out = {};
      Object.keys(isModify).forEach((key) => {
        if (omit.includes(key) || (only && !only.includes(key))) {
          return;
        }
        if (!isModify[key]) {
          return;
        }
        const tmpValue = values[key];
        if (isNullOrUndefined(tmpValue)) {
          out[key] = null;
        } else {
          out[key] = tmpValue;
        }
      });
      return out;
    },
    [isModify, values]
  );
  return {
    getDeltaData,
    isFormModified,
    isModify,
    restoreValues,
    restoreValue,
    setValues: setValuesExternal,
    values,
    errors,
    configuration,
    deltaConfig,
  };
};

export type UseFormidableReturn = ReturnType<typeof useFormidable>;
