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

import { getLocalStorage, subscribeToLocalStorage, setLocalStorage } from './localStorage';

export const AVAILABLE_KEYS = [
  "stats",
  "name",
  "password"
] as const;
export type LocalStorageKey = typeof AVAILABLE_KEYS[number];
interface LocalStorageOptions<T> {
  excludeExternal?: boolean;
  defaultValue?: T;
}

export const useLocalStorage = <T extends string = string>(
  key: LocalStorageKey,
  options: LocalStorageOptions<T> = {}
): [T | null, (value: T) => void] => {
  // We track the localStorage value as state so that we can re-render when it
  // is updated (see the subscription useEffect for how that works)
  const [valueState, setValueState] = useState<T | null>(
    (getLocalStorage(key) as T) || options.defaultValue || null
  );

  // This  ref is needed because we don't want to ever redefine the setValue
  // function but we want to be able to use whatever the latest key is when we
  // do setValue
  const keyRef = useRef<string>(key);
  useEffect(() => {
    keyRef.current = key;
  }, [key]);

  // We want this set function to never be redefined, so we use useCallback with
  // an empty array and only reference the key by ref
  const setValue = useCallback((value: T) => {
    setLocalStorage(keyRef.current, value);
  }, []);

  // subscribe to localstorage changes and set the `currentValue` state on
  // change.
  useEffect(() => {
    // make sure to return.  The subscribe function returns an unsubscribe
    // function
    return subscribeToLocalStorage(key, setValueState as (val: string) => void, options);
  }, [key, options]);

  return [valueState, setValue];
};

export const useJSONLocalStorage = <T>(
  key: LocalStorageKey,
  defaultValue: T,
  options?: LocalStorageOptions<T>
): [T, (newValue: T) => void] => {
  const [stringValue, setStringValue] = useLocalStorage(key, {
    ...options,
    defaultValue: defaultValue && JSON.stringify(defaultValue)
  });

  const value = useMemo(() => {
    try {
      return JSON.parse(stringValue as string);
    } catch {

      return defaultValue;
    }
  }, [stringValue, defaultValue]);

  const setValue = (newValue: T) => setStringValue(JSON.stringify(newValue));

  return [value, setValue];
};

export default useLocalStorage;
