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

import { isArray, isNullOrUndefined, isObject } from '@/utils/validator';

const hasAnyTrue = (obj: { [key: string]: boolean }): boolean => {
  for (const key in obj) {
    if (obj.hasOwnProperty(key) && obj[key] === true) {
      return true;
    }
  }
  return false;
};

function getDifferences(
  obj1: object,
  obj2: object
): { [key: string]: boolean } {
  // Create an empty object to store the differences
  const result: { [key: string]: boolean } = {};
  // Recursive function to compare values
  function compareValues(value1: any, value2: any): boolean {
    // If both values are objects, compare their properties recursively
    if (isObject(value1) && isObject(value2)) {
      return hasAnyTrue(getDifferences(value1, value2));
    }
    // If both values are arrays, compare their elements
    if (isArray(value1) && isArray(value2)) {
      //console.log(`Check is array: ${JSON.stringify(value1)} =?= ${JSON.stringify(value2)}`);
      if (value1.length !== value2.length) {
        return true;
      }
      for (let i = 0; i < value1.length; i++) {
        if (compareValues(value1[i], value2[i])) {
          return true;
        }
      }
      return false;
    }
    // Otherwise, compare the values directly
    //console.log(`compare : ${value1} =?= ${value2}`);
    return value1 !== value2;
  }

  // Get all keys from both objects
  const allKeys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);

  // Iterate over all keys
  for (const key of allKeys) {
    if (compareValues(obj1[key], obj2[key])) {
      result[key] = true;
    } else {
      result[key] = false;
    }
  }
  return result;
}

export const useFormidable = <TYPE extends object = object>({
  initialValues = {} as TYPE,
}: {
  initialValues?: TYPE;
}) => {
  const [values, setValues] = useState<TYPE>({ ...initialValues } as TYPE);
  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));
        return newValues;
      });
    },
    [setValues, initialData]
  );
  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 }: { omit?: string[]; only?: string[] }) => {
      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,
  };
};

export type UseFormidableReturn = ReturnType<typeof useFormidable>;
