import { LocalStorage } from '@/utils';
import _ from 'lodash';
import { useEffect, useRef, useState } from 'react';
import * as yup from 'yup';

const localStorageInstance = new LocalStorage();

type ISetter = (newValue: unknown) => unknown;

function buildSetter(key: string, currentValue: unknown, callback: ISetter): ISetter {
  return function (newValue: unknown) {
    const updatedValue = _.isFunction(newValue) ? newValue(currentValue) : newValue;
    localStorageInstance.set(key, updatedValue);
    callback(updatedValue);
    return updatedValue;
  };
}

/**
 * React hook for synchronizing a state value with browser LocalStorage.
 *
 * This hook provides a stateful value and a setter function, similar to `useState`,
 * but persists the value in LocalStorage under the specified key. The value is
 * automatically updated when LocalStorage changes (including from other tabs/windows).
 *
 * Use this hook only when you need to persist state across sessions or tabs.
 * If you only need to share state within a single session, consider using `useSessionStorage`
 *
 * Example:
 * ```tsx
 * const [value, setValue] = useLocalStorage('myKey', 'defaultValue');
 *
 * // Read the value
 * console.log(value);
 *
 * // Update the value (persists to LocalStorage)
 * setValue('newValue');
 * ```
 *
 * @param key - The LocalStorage key to store the value under. Must be a non-empty string.
 * @param defaultValue - The default value to use if the key does not exist in LocalStorage.
 * @returns A tuple containing the current value and a setter function to update it.
 *
 */
function useLocalStorage(key: string, defaultValue?: unknown): [unknown, (newValue: unknown) => unknown] {
  yup.string().required().validateSync(key);
  const [state, setState] = useState(localStorageInstance.get(key) ?? defaultValue);
  const defaultValueRef = useRef<unknown | undefined>(defaultValue);
  const storedDefaultValue = _.isEqual(defaultValueRef.current, defaultValue) ? defaultValueRef.current : defaultValue;
  const setterRef = useRef<{ key: string; value: unknown; setter: ISetter } | undefined>();
  const storedSetter = setterRef.current;

  useEffect(() => {
    setState(localStorageInstance.get(key) ?? storedDefaultValue);
  }, [key, storedDefaultValue, setState]);

  useEffect(() => {
    function listener(value: unknown) {
      setState(value);
    }
    localStorageInstance.watch(key, listener);
    return () => {
      localStorageInstance.unwatch(key, listener);
    };
  }, [key, setState]);

  if (key !== storedSetter?.key || !_.isEqual(state, storedSetter?.value)) {
    setterRef.current = { key, value: state, setter: buildSetter(key, state, setState) };
  }

  return [state, setterRef.current?.setter ?? _.noop];
}

export { useLocalStorage };
