import { Dispatch, SetStateAction, DependencyList } from 'react';

interface Atom<out T, in out W> {
    /**
     * Gets a value from the atom
     * @returns The current value of the atom
     */
    get: () => T;
    /**
     * The setter function for the atom
     */
    set: Dispatch<W>;
    /**
     * Subscribes to the atom, and fires a callback when the atom changes
     * @param notify A callback that will be fired when the atom changes, passes the new value
     * @returns A function to unsubscribe from the atom
     */
    subscribe: (notify: (value: T) => void) => () => void;
}
declare function atom<T>(initialValue?: T): Atom<T, SetStateAction<T>>;
/**
 * Creates an atom with a writable interface. This is a more of an advanced API, and should only be reached for
 * if you need logic that is more complex than what `atom` provides.
 * @param init A value to initialize the atom with
 * @param toAction A function that takes the next value and returns the next state or a function that takes the previous state and returns the next state
 * @returns An atom with a writable interface
 *
 * @example
 * ```ts
 * function atomWithUpdatedAt<T>(initialValue: T) {
 * 	// First generic is the value when read, second is the value to be set
 * 	return writable<{value: T; updatedAt: number}, T>(
 * 		// Initial value:
 * 		{value: initialValue, updatedAt: Date.now()},
 *
 * 		// Calculate the next state
 * 		next => ({value: next, updatedAt: Date.now()}),
 * 	);
 * }
 *
 * const atom = atomWithUpdatedAt(0);
 *
 * atom.get(); // {value: 0, updatedAt: <timestamp>}
 *
 * ```
 *
 * You can even take this a futher and write a reducer pattern, since the reducer has to return a `SetStateAction` (allowing you to access previous state).
 *
 * ```ts
 * type Action = {type: 'increment'} | {type: 'decrement'} | {type: 'set'; value: number};
 *
 * function reducer(state: number, action: Action): number {
 * 	switch (action.type) {
 * 		case 'increment':
 * 			return state + 1;
 * 		case 'decrement':
 * 			return state - 1;
 * 		case 'set':
 * 			return action.value;
 * 	}
 * }
 *
 * // Must pass `<number, Action>` type arguments
 * const atom = writable<number, Action>(0, next => old => reducer(old, next));
 *
 * atom.set({type: 'increment'});
 * atom.get(); // 1
 *
 * atom.set({type: 'set', value: 5});
 * atom.get(); // 5
 *
 * atom.set({type: 'decrement'});
 * atom.get(); // 4
 * ```
 */
declare function writable<T, W = T>(init: T, toAction: (value: W) => SetStateAction<T>): Atom<T, W>;
/**
 * Hook to get the current value of an atom
 * @param atom The atom to consume
 * @returns The current value of the atom
 */
declare function useAtomValue<T, W>(atom: Atom<T, W>): T;
/**
 * Use the value of an atom
 * @param atom The atom to consume
 * @returns The current state of the atom, as well as a stable writer
 */
declare function useAtom<T, W>(atom: Atom<T, W>): [value: T, write: Dispatch<W>];
/**
 * Get immediately notified when an atom's value changes
 * @param atom The atom to listen to
 * @param callback A callback that will be fired when the atom changes.
 */
declare function useAtomDidChange<T, W>(atom: Atom<T, W>, callback: (value: T) => void): void;
/**
 * Selects/computes a value from an atom, and re-renders the component when the selected value changes
 *
 * The selector function can be unstable, but the value it returns should be stable. This means you could do something like this
 * `const totalMessages = useSelectAtomValue(chatAtom, chat => chat.messages.length);`
 *
 * Or this is also okay, because you're not computing a new value, just selecting a value from the atom:
 * `const message = useSelectAtomValue(chatAtom, chat => chat.messages);`
 *
 * But NOT this, because we make a new array every time the selector is called (which will trigger a re-render):
 * `const totalMessages = useSelectAtomValue(chatAtom, chat => [...chat.messages, "hello"]);`
 *
 * If you need this kind of behaviour, you should use this in combination with `useMemo`:
 *
 * ```ts
 * const messages = useSelectAtomValue(chatAtom, chat => chat.messages);
 * const mappedMessages = useMemo(() => [...messages, "hello"], [messages]);
 * ```
 *
 * @param atom The atom to select a value from-inherited from the atom
 * @param selector The selector function
 * @returns The selected value
 *
 * @example
 * ```ts
 * const messages = useSelectAtomValue(chatAtom, chat => chat.messages);
 * console.log('messages', messages);
 * ```
 */
declare function useSelectAtomValue<T, W, U>(atom: Atom<T, W>, selector: (atom: T) => U, dependencies?: DependencyList): U;

export { type Atom, atom, useAtom, useAtomDidChange, useAtomValue, useSelectAtomValue, writable };
