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

const sessionStorageInstance = new SessionStorage();

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;
    sessionStorageInstance.set(key, updatedValue);
    callback(updatedValue);
    return updatedValue;
  };
}

/**
 * React hook for synchronizing a state value with browser SessionStorage.
 *
 * This hook provides a stateful value and a setter function, similar to `useState`,
 * but persists the value in SessionStorage under the specified key across the same session.
 *
 * Use this hook only when you need to persist state across tabs within the same session.
 * If you need to persist state across sessions, consider using `useLocalStorage`
 * instead.
 *
 * Example:
 * ```tsx
 * const [value, setValue] = useSessionStorage('myKey', 'defaultValue');
 *
 * // Read the value
 * console.log(value);
 *
 * // Update the value (persists to SessionStorage)
 * setValue('newValue');
 * ```
 *
 * @param key - The SessionStorage 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 SessionStorage.
 * @returns A tuple containing the current value and a setter function to update it.
 *
 */
function useSessionStorage(key: string, defaultValue?: unknown): [unknown, (newValue: unknown) => unknown] {
  yup.string().required().validateSync(key);
  const [state, setState] = useState(sessionStorageInstance.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(sessionStorageInstance.get(key) ?? storedDefaultValue);
  }, [key, storedDefaultValue, setState]);

  useEffect(() => {
    function listener(value: unknown) {
      setState(value);
    }
    sessionStorageInstance.watch(key, listener);
    return () => {
      sessionStorageInstance.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 { useSessionStorage };
