import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

function safeParse<T>(value: string): T | undefined {
  try {
    return JSON.parse(value) as T;
  } catch {
    return undefined;
  }
}

/**
 * Initialize value from localStorage using the initializer
 * Handles reading, applying initializer, and saving back if needed
 */
function initializeFromStorage<T>(
  key: string,
  initializer: T | ((existing: T | undefined) => T),
): T {
  // Non-browser render contexts (tests without DOM globals) get defaults.
  if (typeof localStorage === "undefined") {
    return typeof initializer === "function"
      ? (initializer as (existing: T | undefined) => T)(undefined)
      : initializer;
  }
  const item = localStorage.getItem(key);
  const existing = item ? safeParse<T>(item) : undefined;

  // Call initializer (value or function)
  const next =
    typeof initializer === "function"
      ? (initializer as (existing: T | undefined) => T)(existing)
      : (existing ?? initializer);

  // If the initializer changed the value (migration or default), save it back
  if (existing === undefined || next !== existing) {
    try {
      const stringified = JSON.stringify(next);
      localStorage.setItem(key, stringified);
    } catch {
      // Ignore errors during migration or initial save
    }
  }

  return next;
}

export function useLocalStorage<T>(
  key: string,
  initializer: T | ((existing: T | undefined) => T),
): [T, (value: T | ((prev: T) => T)) => void] {
  const queryClientInstance = useQueryClient();
  const queryKey = ["localStorage", key] as const;

  // Use TanStack Query to read from localStorage
  const { data: value } = useQuery({
    queryKey,
    queryFn: () => initializeFromStorage(key, initializer),
    initialData: () => initializeFromStorage(key, initializer),
    staleTime: Infinity, // localStorage doesn't change unless we update it
    gcTime: Infinity, // Keep in cache indefinitely
  });

  // Mutation to write to localStorage
  const mutation = useMutation({
    mutationFn: async (newValue: T) => {
      if (typeof localStorage !== "undefined") {
        localStorage.setItem(key, JSON.stringify(newValue));
      }
      return newValue;
    },
    onSuccess: (newValue) => {
      // Update the query cache optimistically
      queryClientInstance.setQueryData(queryKey, newValue);
    },
  });

  // Setter that updates localStorage via mutation
  const setLocalStorageValue = (updater: T | ((prev: T) => T)) => {
    const current = queryClientInstance.getQueryData<T>(queryKey);
    // If for some reason current is undefined (shouldn't happen due to initialData),
    // we fall back to initializer logic or just throw/ignore.
    // Assuming initialData guarantees T.
    const next =
      typeof updater === "function"
        ? (updater as (prev: T) => T)(current as T)
        : updater;

    mutation.mutate(next);
  };

  // Return the value from query (guaranteed to be T due to initialData)
  return [value as T, setLocalStorageValue];
}
