import React, {
  createContext,
  useContext,
  useRef,
  useState,
  useEffect,
} from 'react';

// Type for the atom state
type AtomKey = string;
type Subscriber = () => void;

type Store = {
  state: Map<AtomKey, any>;
  subscribers: Map<AtomKey, Subscriber[]>;
  get: <T>(key: AtomKey) => T | undefined;
  set: <T>(key: AtomKey, newValue: T) => void;
  subscribe: (key: AtomKey, callback: Subscriber) => () => void;
};

const StoreContext = createContext<Store | null>(null);

export const StoreProvider = ({children}: {children: React.ReactNode}) => {
  const state = useRef<Map<AtomKey, any>>(new Map());
  const subscribers = useRef<Map<AtomKey, Subscriber[]>>(new Map());

  const get = <T,>(key: AtomKey): T | undefined => {
    return state.current.get(key);
  };

  const set = <T,>(key: AtomKey, newValue: T) => {
    state.current.set(key, newValue);
    const subs = subscribers.current.get(key);
    if (subs) {
      subs.forEach(cb => cb());
    }
  };

  const subscribe = (key: AtomKey, callback: Subscriber) => {
    if (!subscribers.current.has(key)) {
      subscribers.current.set(key, []);
    }
    subscribers.current.get(key)!.push(callback);

    // Return unsubscribe fn
    return () => {
      const subs = subscribers.current.get(key);
      if (subs) {
        subscribers.current.set(
          key,
          subs.filter(cb => cb !== callback),
        );
      }
    };
  };

  const store: Store = {
    get,
    set,
    subscribe,
    state: state.current,
    subscribers: subscribers.current,
  };

  return (
    <StoreContext.Provider value={store}>{children}</StoreContext.Provider>
  );
};

// Hook to read and subscribe to atom state
export function useAtom<T>(
  key: AtomKey,
  defaultValue?: T,
): [T | undefined, (newValue: T) => void] {
  const store = useContext(StoreContext);
  if (!store) {
    throw new Error('useAtom must be used within a StoreProvider');
  }

  const [value, setValue] = useState<T | undefined>(() => {
    const existing = store.get<T>(key);
    if (existing === undefined && defaultValue !== undefined) {
      store.set(key, defaultValue);
      return defaultValue;
    }
    return existing;
  });

  useEffect(() => {
    // subscribe to changes on this atom key
    const unsubscribe = store.subscribe(key, () => {
      setValue(store.get<T>(key));
    });
    return () => {
      unsubscribe();
    };
  }, [key, store]);

  const setter = (newValue: T) => {
    store.set(key, newValue);
  };

  return [value, setter];
}
