import * as React from "react";

function dispatchStorageEvent(key, newValue) {
  window.dispatchEvent(new StorageEvent("storage", { key, newValue }));
}

const setLocalStorageItem = (key, value) => {
  if (typeof window === "undefined") {
    return;
  }
  const stringifiedValue = JSON.stringify(value);
  window.localStorage.setItem(key, stringifiedValue);
  dispatchStorageEvent(key, stringifiedValue);
};

const removeLocalStorageItem = (key) => {
  if (typeof window === "undefined") {
    return;
  }
  window.localStorage.removeItem(key);
  dispatchStorageEvent(key, null);
};

const getLocalStorageItem = (key) => {
  if (typeof window === "undefined") {
    return null;
  }
  const json = window.localStorage.getItem(key);
  if (json === null) {
    return null;
  }
  try {
    return JSON.parse(json);
  } catch (e) {
    console.warn(e);
    return json ?? null;
  }
};

// Cache the localStorage state to prevent infinite loops
let cachedLocalStorage = {};
let cachedKeys: string[] | null = null;

const useLocalStorageGetSnapshot = () => {
  if (typeof window === "undefined") {
    return {};
  }

  // Create a stable snapshot by caching the keys and values
  const currentKeys = Object.keys(window.localStorage);

  // Check if keys have changed
  const keysChanged =
    !cachedKeys ||
    cachedKeys.length !== currentKeys.length ||
    !currentKeys.every((key, index) => key === cachedKeys![index]);

  if (keysChanged) {
    // Update cache if keys have changed
    cachedKeys = currentKeys;
    cachedLocalStorage = currentKeys.reduce((acc, key) => {
      acc[key] = window.localStorage.getItem(key);
      return acc;
    }, {});
  } else {
    // Check if any values have changed
    let valuesChanged = false;
    for (const key of currentKeys) {
      const currentValue = window.localStorage.getItem(key);
      if (cachedLocalStorage[key] !== currentValue) {
        valuesChanged = true;
        cachedLocalStorage[key] = currentValue;
      }
    }
  }

  return cachedLocalStorage;
};

const useLocalStorageSubscribe = (callback) => {
  window.addEventListener("storage", callback);
  return () => window.removeEventListener("storage", callback);
};

const getLocalStorageServerSnapshot = () => {
  return {};
};

export function useLocalStorage() {
  React.useSyncExternalStore(
    useLocalStorageSubscribe,
    useLocalStorageGetSnapshot,
    getLocalStorageServerSnapshot,
  );
  const hasItem = React.useCallback((key: string) => {
    return getLocalStorageItem(key) !== null;
  }, []);
  const setItem = React.useCallback((key: string, value: string) => {
    try {
      if (value === null || value === undefined) {
        removeLocalStorageItem(key);
      } else {
        setLocalStorageItem(key, value);
      }
    } catch (e) {
      console.warn(e);
    }
  }, []);
  return React.useMemo(
    () => ({ getItem: getLocalStorageItem, hasItem, setItem }),
    [hasItem, setItem],
  );
}
