/**
 * Special symbol that indicates that a function produced no value.
 * Effectively, the rest of the pipeline is not used.
 */
export declare const none: unique symbol;

/**
 * Special symbol that indicates that the pipeline should be stopped.
 * Just like {@link none}, it produces no value.
 */
export declare const stop: unique symbol;

/** Used internally to mark a value as a final value. */
export declare const finalSymbol: unique symbol;
/** Used internally to mark a value as multiple values. */
export declare const manySymbol: unique symbol;
/** Used internally to mark a function as capable of being flushed. */
export declare const flushSymbol: unique symbol;
/** Used internally to mark a function as being derived from a function list. */
export declare const fListSymbol: unique symbol;

/**
 * An exception that indicates that the pipeline should be stopped.
 */
export declare class Stop extends Error {}

/**
 * Interface for a value that has been marked as a final value.
 */
export interface FinalValue<T = any> {
  [finalSymbol]: 1;
  value: T;
}
/**
 * Type predicate for `FinalValue`.
 * @param o object to test
 * @returns `true` if `o` is a `FinalValue`
 */
export declare function isFinalValue(o: object): o is FinalValue;
/**
 * Creates a `FinalValue`
 * @param value the wrapped value
 * @returns a `FinalValue`
 */
export declare function finalValue<T>(value: T): FinalValue<T>;
/**
 * Retrieves the value of a `FinalValue`
 * @param o a `FinalValue` object
 * @returns the wrapped value
 */
export declare function getFinalValue<T>(o: FinalValue<T>): T;
/**
 * Alias for {@link finalValue}
 */
export declare const final = finalValue;

/**
 * Interface for a value that has been marked as multiple values.
 * It is used to return multiple values from a regular (non-generator) function.
 */
export interface Many<T = any> {
  [manySymbol]: 1;
  values: T[];
}
/**
 * Type predicate for `Many`.
 * @param o object to test
 * @returns `true` if `o` is a `Many`
 */
export declare function isMany(o: unknown): o is Many;
/**
 * Creates a `Many`
 * @param values the wrapped values
 * @returns a `Many`
 */
export declare function many<T>(values: T[]): Many<T>;
/**
 * Retrieves the values of a `Many`
 * @param o a `Many` object
 * @returns the wrapped values
 */
export declare function getManyValues<T>(o: Many<T>): T[];

/**
 * Interface for a function that can be flushed.
 * If it is marked as flushable, it will be called with the special {@link none} value
 * when the pipeline is stopped so it can produce the last value.
 */
export interface Flushable<I = any, O = unknown> {
  (value: I, ...rest: any[]): O;
  [flushSymbol]: 1;
}
/**
 * Type predicate for `Flushable`.
 * @param o function to test
 * @returns `true` if `o` is a `Flushable`
 */
export declare function isFlushable<I, O>(o: (value: I, ...rest: any[]) => O): o is Flushable<I, O>;
/**
 * Creates a `Flushable`
 * @param write function to be marked as flushable
 * @param final an optional function to be called when the pipeline is stopped
 * @returns a `Flushable`
 * @remarks If `final` is not provided, `write` will be called with {@link none} when the pipeline is stopped
 */
export declare function flushable<I, O>(
  write: (value: I, ...rest: any[]) => O,
  final?: () => O
): Flushable<I, O>;

/**
 * Interface for a function that can be derived from a function list.
 * `chain` can use the list instead of the original function.
 */
export interface FunctionList<
  T extends (...args: readonly any[]) => unknown,
  I = any,
  O = unknown
> {
  (value: I, ...rest: any[]): O;
  [fListSymbol]: 1;
  fList: T[];
}
/**
 * Type predicate for `FunctionList`.
 * @param o function to test
 * @returns `true` if `o` is a `FunctionList`
 */
export declare function isFunctionList<I, O>(
  o: (value: I, ...rest: readonly any[]) => O
): o is FunctionList<(...args: readonly any[]) => unknown, I, O>;
/**
 * Sets a function list creating a `FunctionList` structure.
 * @param o function to be marked as a function list
 * @param fns function list
 * @returns `o` as a `FunctionList`
 */
export declare function setFunctionList<
  T extends (...args: readonly any[]) => unknown,
  F extends (...args: readonly any[]) => unknown
>(
  o: F,
  fns: T[]
): F extends (value: infer I, ...rest: any[]) => infer O ? FunctionList<T, I, O> : never;
/**
 * Retrieves the function list of a `FunctionList`
 * @param o a `FunctionList` object
 * @returns the function list
 */
export declare function getFunctionList<
  T extends (...args: readonly any[]) => unknown,
  I = any,
  O = unknown
>(o: FunctionList<T, I, O>): T[];
/**
 * Clears the function list from a `FunctionList`
 * @param o a `FunctionList` object
 * @returns `o` as a `FunctionList`
 */
export declare function clearFunctionList<
  T extends (...args: readonly any[]) => unknown,
  I = any,
  O = unknown
>(o: FunctionList<T, I, O>): (value: I, ...rest: any[]) => O;

/**
 * Convert a value to `Many`.
 * @param value the value to convert
 * @returns a `Many` containing the value
 * @remarks `Many` is used to return multiple values from a regular (non-generator) function.
 */
export declare function toMany<T>(value: readonly Many<T>): Many<T>;
export declare function toMany(value: typeof none): Many<never>;
export declare function toMany<T>(value: readonly T): Many<T>;
/**
 * Normalize a value by unbundling it if it is a `Many`.
 * @param value the value to normalize
 * @returns the normalized value
 */
export declare function normalizeMany(value: readonly unknown): unknown;
/**
 * Combine two values into a `Many`.
 * @param a the first value
 * @param b the second value
 * @returns a `Many` containing both values
 */
export declare function combineMany(a: typeof none, b: typeof none): Many<never>;
export declare function combineMany<T>(a: typeof none, b: readonly Many<T>): Many<T>;
export declare function combineMany<T>(a: readonly Many<T>, b: typeof none): Many<T>;
export declare function combineMany<T>(a: typeof none, b: readonly T): Many<T>;
export declare function combineMany<T>(a: readonly T, b: typeof none): Many<T>;
export declare function combineMany<T, U>(a: readonly Many<T>, b: readonly Many<U>): Many<T | U>;
export declare function combineMany<T, U>(a: readonly T, b: readonly Many<U>): Many<T | U>;
export declare function combineMany<T, U>(a: readonly Many<T>, b: readonly U): Many<T | U>;
export declare function combineMany<T, U>(a: readonly T, b: readonly U): Many<T | U>;
/**
 * Combine two values into a `Many` mutably.
 * @param a the first value
 * @param b the second value
 * @returns a `Many` containing both values
 * @remarks if `a` or `b` are `Many`, they can be modified in-place
 */
export declare function combineManyMut(a: typeof none, b: typeof none): Many<never>;
export declare function combineManyMut<T>(a: typeof none, b: readonly Many<T>): Many<T>;
export declare function combineManyMut<T>(a: readonly Many<T>, b: typeof none): Many<T>;
export declare function combineManyMut<T>(a: typeof none, b: readonly T): Many<T>;
export declare function combineManyMut<T>(a: readonly T, b: typeof none): Many<T>;
export declare function combineManyMut<T, U>(a: readonly Many<T>, b: readonly Many<U>): Many<T | U>;
export declare function combineManyMut<T, U>(a: readonly T, b: readonly Many<U>): Many<T | U>;
export declare function combineManyMut<T, U>(a: readonly Many<T>, b: readonly U): Many<T | U>;
export declare function combineManyMut<T, U>(a: readonly T, b: readonly U): Many<T | U>;

// generic utilities: unpacking types

/**
 * Generic utility for getting the return type of a function including async and generators.
 */
export type UnpackReturnType<F extends (...args: readonly any[]) => unknown> =
  ReturnType<F> extends Promise<unknown>
    ? Awaited<ReturnType<F>>
    : ReturnType<F> extends AsyncGenerator<infer O, unknown, unknown>
    ? O
    : ReturnType<F> extends Generator<infer O, unknown, unknown>
    ? O
    : ReturnType<F>;

/**
 * `stream-chain`-specific utility for getting the type from functions used in a function list.
 */
export type UnpackType<T> = T extends Many<infer U>
  ? U
  : T extends FinalValue<infer U>
  ? U
  : Exclude<T, typeof none | typeof stop>;

/**
 * Unpacking the return type of a function as a combination of {@link UnpackType} and {@link UnpackReturnType}.
 */
export type OutputType<F extends function> = UnpackType<UnpackReturnType<F>>;

// generic utilities: working with tuples

/**
 * Returns the first element of a tuple or `never`.
 */
export type First<L extends readonly unknown[]> = L extends readonly [
  infer T,
  ...(readonly unknown[])
]
  ? T
  : never;
/**
 * Returns the last element of a tuple or `never`.
 */
export type Last<L extends readonly unknown[]> = L extends readonly [
  ...(readonly unknown[]),
  infer T
]
  ? T
  : never;
/**
 * Flattens a tuple of tuples recursively returning a flat tuple.
 */
export type Flatten<L extends readonly unknown[]> = L extends readonly [infer T, ...infer R]
  ? T extends readonly unknown[]
    ? readonly [...Flatten<T>, ...Flatten<R>]
    : readonly [T, ...Flatten<R>]
  : L;
/**
 * Filters a tuple removing all elements of a specified type returning a filtered tuple.
 */
export type Filter<L extends readonly unknown[], X> = L extends readonly [infer T, ...infer R]
  ? T extends X
    ? Filter<R, X>
    : readonly [T, ...Filter<R, X>]
  : L;
/**
 * Flattens and filters a tuple. See {@link Flatten} and {@link Filter}.
 */
export type AsFlatList<L extends readonly unknown[]> = Filter<Flatten<L>, null | undefined>;

// generic utilities: working with functions

/**
 * A generic one-argument function. Used internally.
 */
export type Fn = (arg: any, ...args: readonly unknown[]) => unknown;

/**
 * Returns the first argument of a function or a function list or `never`.
 */
export type Arg0<F> = F extends readonly unknown[]
  ? AsFlatList<F> extends readonly [infer F1, ...(readonly unknown[])]
    ? Arg0<F1>
    : AsFlatList<F> extends readonly []
    ? any
    : AsFlatList<F> extends readonly (infer F1)[]
    ? Arg0<F1>
    : never
  : F extends (...args: readonly any[]) => unknown
  ? Parameters<F>[0]
  : never;

/**
 * Returns the unpacked return type of a function or a function list or `never`.
 */
export type Ret<F, Default = any> = F extends readonly unknown[]
  ? AsFlatList<F> extends readonly [...unknown[], infer F1]
    ? Ret<F1, Default>
    : AsFlatList<F> extends readonly []
    ? Default
    : AsFlatList<F> extends readonly (infer F1)[]
    ? Ret<F1, Default>
    : never
  : F extends Fn
  ? OutputType<F>
  : never;
