let isLocalStorageEnabled = true;

interface MemoryStore {
  [key: string]: string;
}

const memoryStorage: MemoryStore = {};

const subscriptions = new EventTarget();

export interface LocalStorageEvent extends CustomEvent {
  key: string;
  newValue: string;
}

export const getLocalStorage = (key: string): string | null => {
  if (!isLocalStorageEnabled) {
    return Object.prototype.hasOwnProperty.call(memoryStorage, key) ? memoryStorage[key] : null;
  }

  try {
    const value = window.localStorage.getItem(key);
    if (value && memoryStorage[key] !== value) {
      memoryStorage[key] = value;
    }
    return value;
  } catch (e) {
    // eslint-disable-next-line no-console
    console.warn('localStorage is diasabled using memory storage instead.', e);
    isLocalStorageEnabled = false;
    return getLocalStorage(key);
  }
};

export const setLocalStorage = (key: string, newValue: string): void => {
  const event = new CustomEvent('local-storage-change', { detail: { key, newValue } });

  if (!isLocalStorageEnabled) {
    memoryStorage[key] = newValue;
    subscriptions.dispatchEvent(event);
    return;
  }
  try {
    window.localStorage.setItem(key, newValue);
    memoryStorage[key] = newValue;
    subscriptions.dispatchEvent(event);
  } catch (e) {
    // eslint-disable-next-line no-console
    console.warn('localStorage is diasabled using memory storage instead.', e);
    isLocalStorageEnabled = false;
    return setLocalStorage(key, newValue);
  }
};

export const isKeySet = (key: string): boolean => {
  if (isLocalStorageEnabled) {
    try {
      for (let i = 0; i < window.localStorage.length; i++) {
        if (window.localStorage.key(i) === key) {
          return true;
        }
      }
    } catch {
      // ignore the error
    }
  }
  return Object.keys(memoryStorage).includes(key);
};

// This function allows you to subscribe to localStorage changes.  By default
//  this includes changes that are made in other tabs/windows of the same
//  browser.  The 'callback' will be called whenever a change is made to
//  localStorage at the given 'key'. An unsubscibe function is returned, which
//  you can call when you are ready to unsubscribe.
export const subscribeToLocalStorage = (
  key: string,
  callback: (value: string) => void,
  options?: { excludeExternal?: boolean }
): (() => void) => {
  const { excludeExternal } = options || {};
  const updateHandler = (e: LocalStorageEvent | { detail: { key: string; newValue: string } }) => {
    const { key: eventKey, newValue } = 'detail' in e ? e.detail : e;
    if (eventKey === key) {
      callback(newValue);
    }
  };
  // subscribe to events for this instance of the page only
  subscriptions.addEventListener(
    'local-storage-change',
    updateHandler as EventListenerOrEventListenerObject
  );

  // if not excluded, subscribe to events for all instances of the page
  !excludeExternal &&
    window.addEventListener('storage', updateHandler as EventListenerOrEventListenerObject);

  // return a function that will be used to unsubscribe to these events.
  return () => {
    subscriptions.removeEventListener(
      'local-storage-change',
      updateHandler as EventListenerOrEventListenerObject
    );
    !excludeExternal &&
      window.removeEventListener('storage', updateHandler as EventListenerOrEventListenerObject);
  };
};
