import { Nullable } from '@hazae41/option';
import { Awaitable } from '../../libs/promises/promises.js';
import { Err } from './err.js';
import { Ok } from './ok.js';

interface Unwrappable<T = unknown> {
    getOrThrow(): T;
}
/**
 * A result whose Ok type is the same as its Err type
 * @example An `update<T>(): Fallback<T>` will return `Ok<T>` if an update was found and `Err<T>` if not
 */
type Fallback<T> = Result<T, T>;
/**
 * An object that can be either an Ok or an Err
 */
type Result<T = unknown, E = unknown> = Ok<T> | Err<E>;
declare namespace Result {
    type Infer<T> = Ok.Infer<T> | Err.Infer<T>;
    /**
     * Create a Result from a maybe Error value
     * @param inner
     * @returns `Ok<T>` if `T`, `Err<Error>` if `Error`
     */
    function from<T>(inner: T | Error): Err<Error> | Ok<T>;
    /**
     * Create a Result from a boolean
     * @param value
     * @returns
     */
    function assert(value: boolean): Ok<void> | Err<void>;
    /**
     * Rewrap any type that extends Ok into an Ok
     * @param wrapper
     */
    function rewrap<T extends Ok.Infer<T>>(wrapper: T): Ok<Ok.Inner<T>>;
    /**
     * Rewrap any type that extends Err into an Err
     * @param wrapper
     */
    function rewrap<T extends Err.Infer<T>>(wrapper: T): Err<Err.Inner<T>>;
    /**
     * Rewrap any type that extends Result into a Result
     * @param wrapper
     */
    function rewrap<T extends Result.Infer<T>>(wrapper: T): Result<Ok.Inner<T>, Err.Inner<T>>;
    /**
     * Rewrap any object with getOrThrow() into a Result
     * @param wrapper
     */
    function rewrap<T, E>(wrapper: Unwrappable<T>): Result<T, E>;
    /**
     * Catch an Err thrown from Err.throw
     * @param callback
     * @param type
     * @returns `Ok<T>` if no `Err` was thrown, `Err<E>` otherwise
     * @see Err.throw
     */
    function unthrow<R extends Result.Infer<R>>(callback: (thrower: (e: Err.Infer<R>) => void) => Awaitable<R>): Promise<R>;
    /**
     * Catch an Err thrown from Err.throw
     * @param callback
     * @param type
     * @returns `Ok<T>` if no `Err` was thrown, `Err<E>` otherwise
     * @see Err.throw
     */
    function unthrowSync<R extends Result.Infer<R>>(callback: (thrower: (e: Err.Infer<R>) => void) => R): R;
    /**
     * Run a callback and wrap any returned value in Ok<T> and any thrown error in Err<unknown>
     * @param callback
     * @returns
     */
    function runAndWrap<T>(callback: () => Awaitable<T>): Promise<Result<T, unknown>>;
    /**
     * Run a callback and wrap any returned value in Ok<T> and any thrown error in Err<unknown>
     * @param callback
     * @returns
     */
    function runAndWrapSync<T>(callback: () => T): Result<T, unknown>;
    /**
     * Run a callback and wrap any returned value in Ok<T> and any thrown error in Err<Catched>
     * @param callback
     * @returns
     */
    function runAndDoubleWrap<T>(callback: () => Awaitable<T>): Promise<Result<T, Error>>;
    /**
     * Run a callback and wrap any returned value in Ok<T> and any thrown error in Err<Catched>
     * @param callback
     * @returns
     */
    function runAndDoubleWrapSync<T>(callback: () => T): Result<T, Error>;
    /**
     * Run a callback and wrap any thrown error in Err<unknown>
     * @param callback
     * @returns
     */
    function runOrWrap<R extends Result.Infer<R>>(callback: () => Awaitable<R>): Promise<R | Err<unknown>>;
    /**
     * Run a callback and wrap any thrown error in Err<unknown>
     * @param callback
     * @returns
     */
    function runOrWrapSync<R extends Result.Infer<R>>(callback: () => R): R | Err<unknown>;
    /**
     * Run a callback and wrap any thrown error in Err<unknown>
     * @param callback
     * @returns
     */
    function runOrDoubleWrap<R extends Result.Infer<R>>(callback: () => Awaitable<R>): Promise<R | Err<Error>>;
    /**
     * Run a callback and wrap any thrown error in Err<unknown>
     * @param callback
     * @returns
     */
    function runOrDoubleWrapSync<R extends Result.Infer<R>>(callback: () => R): R | Err<Error>;
    /**
     * Transform `Iterable<Result<T,E>` into `Result<Array<T>, E>`
     * @param iterable
     * @returns `Result<Array<T>, E>`
     */
    function all<T, E>(iterable: Iterable<Result<T, E>>): Result<Array<T>, E>;
    function maybeAll<T, E>(iterable: Iterable<Nullable<Result<T, E>>>): Nullable<Result<Array<T>, E>>;
    /**
     * Transform `Iterable<Result<T,E>` into `Iterator<T, Result<void, E>>`
     * @param iterable
     * @returns `Iterator<T, Result<void, E>>`
     */
    function iterate<T, E>(iterable: Iterable<Result<T, E>>): Iterator<T, Result<void, E>>;
    function maybeIterate<T, E>(iterable: Iterable<Nullable<Result<T, E>>>): Iterator<T, Nullable<Result<void, E>>>;
    /**
     * Transform `Iterator<T, Result<void, E>>` into `Result<Array<T>, E>`
     * @param iterator `Result<Array<T>, E>`
     */
    function collect<T, E>(iterator: Iterator<T, Result<void, E>>): Result<Array<T>, E>;
    function maybeCollect<T, E>(iterator: Iterator<T, Nullable<Result<void, E>>>): Nullable<Result<Array<T>, E>>;
}

export { type Fallback, Result, type Unwrappable };
