import { Dispatch, SetStateAction } from 'react';

type UseAsyncFunctionState<R> = {
    loading: boolean;
    result: {
        type: 'initial';
    } | {
        type: 'success';
        data: R;
    } | {
        type: 'error';
        error: unknown;
    };
};
/**
 * Wraps an async function to provide state. Useful for mutations and form submissions.
 * Does NOT allow concurrent runs. You must wait for the previous run to finish before starting a new one.
 *
 * @param execute The async function to run and have state for
 * @returns The state of the last run, as well as a function to start new runs. The run function cannot be called if a previous run is in flight. The run function will return the result of the async function.
 *
 * @example
 * ```tsx
 * const [state, run] = useAsyncFunction(async (a: number, b: number) => {
 *     const result = await addingApi.add(a, b);
 *     return result;
 * });
 *
 * return (
 *     <>
 *         <button disabled={state.loading} onClick={() => run(1, 2)}>Run</button>
 *         {state.loading && <p>Loading...</p>}
 *         {state.type === 'success' && <p>Result: {state.data}</p>}
 *         {state.type === 'error' && <p>Error: {JSON.stringify(state.error)}</p>}
 *     </>
 * )
 * ```
 */
declare function useAsyncFunction<A extends unknown[], R>(execute: (...args: A) => Promise<R>): [
    state: UseAsyncFunctionState<R>,
    run: (...args: A) => Promise<UseAsyncFunctionState<R>['result']>,
    reset: () => void
];

/**
 * Stabilise a function - useful to use as an event listener callback or similar
 * @param fn A function to completely stabilise
 * @returns A stable reference to the function, it will never change
 */
declare function useEvent<Args extends unknown[], R>(fn: (...args: Args) => R): (...args: Args) => R;

/**
 * A completely reactive hook to run a function on an interval
 * @param callback A callback to run on a set interval
 * @param delay The interval between each tick
 */
declare function useInterval(callback: () => void, delay: number | null, options?: {
    runImmediately?: boolean;
}): void;

/**
 * Detects if the browser is currently online
 * @returns If the client is currently only. Returns false on the server
 */
declare function useIsOnline(): boolean;

/**
 * A hook to detect if the current tab is focused or not
 * @returns A boolean if the current tab is focused
 */
declare function useIsTabFocused(): boolean;
/**
 * @deprecated this has been renamed to `useIsTabFocused`
 */
declare const useTabFocused: typeof useIsTabFocused;

/**
 * Creates a ref that can be initalized with a callback (lazily).
 * This is useful so we don't have to construct a class or call a function every time we
 * call this hook in a component as that could be costly.
 *
 * Inspiration is from https://github.com/facebook/react/issues/14490
 *
 * @param init Initial value
 * @returns A React ref that was initalized with `init`
 *
 * @example
 * const ref = useLazyRef(() => new MyExpensiveClass());
 * ref.current.doSomething();
 */
declare function useLazyRef<T>(init: () => T): React.MutableRefObject<T>;

/**
 * Use a value from local storage, with a setter. Will trigger updates wherever the same key is used around your app, and even works cross tab.
 * @param key The key to store data under
 * @param init An initialiser if the key does not already exist
 * @returns A tuple containing the value from local storage, synced across tabs, as well as a setter to update the value.
 * @example
 * ```tsx
 *  import {useLocalStorage} from 'alistair/hooks';
 *
 *  function useCounter() {
 *      return useLocalStorage('counter', () => 1);
 *  }
 *
 *  function Child() {
 *      const [value] = useCounter();
 *
 *      // This will update no matter where it changes!
 *      // This can include modifying it in dev tools, or changing it in another tab with the hook.
 *      // It will **always** stay in sync
 *      return <div>{value}</div>;
 *  }
 *
 *  function App() {
 *      const [, set] = useCounter();
 *
 *      return (
 *          <>
 *              <button onClick={() => set(old => old + 1)}>click</button>
 *              <Child />
 *              <Child />
 *          </>
 *      );
 *  }
 * ```
 */
declare function useLocalStorage<T>(key: string, init: () => T): [value: T, set: Dispatch<SetStateAction<T>>];

/**
 * Throttle a value to be updated at a maximum of once per `limit` milliseconds.
 * @param value The value to throttle
 * @param limit Milliseconds to throttle by
 * @returns The value after it has been throttled
 *
 * @example
 * const [value, setValue] = useState(0);
 * // `throttledValue` will only change at a maximum of once per 1000ms
 * const throttledValue = useThrottle(value, 1000);
 */
declare function useThrottle<T>(value: T, limit?: number): T;

interface ToggleControl {
    on: () => void;
    off: () => void;
    toggle: () => void;
    reset: () => void;
}
/**
 * Store a toggle boolean state with utility methods
 * @param initialState The initial state, optional
 * @returns A tuple containing the value and controls to update it
 * @example
 * const [isOpen, {toggle}] = useToggle();
 * return <button onClick={toggle}>Toggle Something</button>;
 */
declare function useToggle(initialState?: boolean): [enabled: boolean, control: ToggleControl];

export { type ToggleControl, type UseAsyncFunctionState, useAsyncFunction, useEvent, useInterval, useIsOnline, useIsTabFocused, useLazyRef, useLocalStorage, useTabFocused, useThrottle, useToggle };
