/**
 * This module provides enhanced promise handling capabilities beyond native JavaScript Promises.
 * It focuses on distinguishing between required and optional promises, standardizing results,
 * and simplifying error handling for complex async operations.
 */
/**
 * Symbol used to uniquely identify PromiseResult objects.
 * This allows for reliable type checking.
 */
export const PROMISE_RESULT_SYMBOL = Symbol("PromiseResult");
/**
 * Type guard to check if a value is a PromiseResult.
 *
 * @param value - The value to check
 * @returns True if the value is a PromiseResult, false otherwise
 */
function isPromiseResult(value) {
    return value !== null && typeof value === "object" && PROMISE_RESULT_SYMBOL in value;
}
/**
 * Custom error class that aggregates multiple promise failures.
 * Provides access to all underlying errors while presenting a combined error message.
 */
export class MultiPromiseError extends Error {
    constructor(reasons) {
        super(errorMessageForReasons(reasons));
        this.reasons = reasons;
        this.reasons = reasons;
    }
}
/**
 * Helper function to create a standardized fulfilled result object.
 * Ensures the result has the PROMISE_RESULT_SYMBOL for type checking.
 *
 * @param value - The value to wrap in a fulfilled result
 * @returns A standardized fulfilled result object
 */
export function makeFulfilledResult(value) {
    return {
        success: true,
        value,
        error: null,
        [PROMISE_RESULT_SYMBOL]: true,
    };
}
/**
 * Helper function to create a standardized rejected result object.
 * Ensures the result has the PROMISE_RESULT_SYMBOL for type checking.
 *
 * @param error - The error to wrap in a rejected result
 * @returns A standardized rejected result object
 */
export function makeRejectedResult(error) {
    const normalizedError = error instanceof Error ? error : new Error(String(error));
    return {
        success: false,
        value: null,
        error: normalizedError,
        [PROMISE_RESULT_SYMBOL]: true,
    };
}
/**
 * Custom error class that indicates that a required promise failed.
 */
export class RequiredPromiseError extends Error {
    constructor(reason) {
        super(reason.message);
        this.reason = reason;
        this.reason = reason;
    }
}
/**
 * Transforms a promise into one that never throws, instead returning a standardized result.
 * Use this for optional operations that shouldn't cause the overall process to fail.
 * This is a convenience alias for tryAwait with a more semantic name.
 *
 * @param promise - The promise to make optional
 * @returns A promise that resolves to a PromiseResult instead of throwing
 */
export async function optional(promise) {
    return await tryAwait(promise);
}
/**
 * Marks a promise as required (will throw on rejection).
 * This is mainly for code clarity as regular promises are already required by default.
 *
 * @param promise - The promise to mark as required
 * @returns The original promise (identity function)
 */
export async function required(promise) {
    return await promise;
}
/**
 * Executes a mix of required and optional promises, returning standardized results.
 * Regular promises are treated as required and will cause this function to throw if they fail.
 * Promises wrapped with optional() will never cause this function to throw.
 *
 * This implementation handles both regular promises and promises that resolve to PromiseResult.
 *
 * @param promises - Array of promises (both required and optional)
 * @returns Promise resolving to an array of standardized results
 * @throws Original error if a single required promise fails
 * @throws MultiPromiseError if multiple required promises fail
 * @note For homogeneous promise arrays, prefer using more specific functions:
 * - Use allRequired() when all promises should be treated as required
 * - Use allOptional() when all promises should be treated as optional
 * - Only use allMixed() when you need to combine both required and optional promises
 */
export async function allMixed(promises) {
    // Process each promise
    const results = await Promise.all(promises.map(async (promise) => {
        try {
            const value = await promise;
            // If it's a PromiseResult (from optional()/tryAwait), use it directly
            if (isPromiseResult(value)) {
                // Handle the case where T is already a PromiseResult
                // This fixes the double-wrapping issue when all promises are optional
                return value;
            }
            else {
                // It's a regular promise that succeeded
                return makeFulfilledResult(value);
            }
        }
        catch (error) {
            // This was a required promise that failed
            return makeRejectedResult(error instanceof Error ? error : new Error(String(error)));
        }
    }));
    const requiredFailures = [];
    results.forEach((result) => {
        if (!result.success) {
            requiredFailures.push(result.error);
        }
    });
    // If any required promises failed, throw an error
    if (requiredFailures.length > 0) {
        if (requiredFailures.length === 1) {
            throw requiredFailures[0];
        }
        else {
            throw new MultiPromiseError(requiredFailures);
        }
    }
    return results;
}
/**
 * Executes all promises and returns their values.
 * All promises are treated as required and will cause this function to throw if any fail.
 *
 * @param promises - Array of promises
 * @returns Promise resolving to an array of values
 * @throws Original error if a single promise fails
 * @throws MultiPromiseError if multiple promises fail
 */
export async function allRequired(promises) {
    return await Promise.all(promises);
}
/**
 * Makes all promises optional and then executes them, returning standardized results.
 * This function is specifically designed for cases where you want all promises to be treated as optional.
 * It avoids the double-wrapping issue that can occur when using allMixed with arrays of optional promises.
 *
 * @param promises - Array of promises to make optional
 * @returns Promise resolving to an array of standardized results
 * @example
 * ```
 * const promises = urls.map(fetchData);
 * const results = await allOptional(promises);
 * // Each result is a PromiseResult<T> that won't cause the overall operation to fail
 * ```
 */
export async function allOptional(promises) {
    // Use Promise.all with optional() for each promise
    return await Promise.all(promises.map(optional));
}
/**
 * Creates an error message from multiple rejection reasons.
 * Formats multiple error reasons into a single error message string,
 * handling both Error objects and other rejection values.
 *
 * @param reasons - Array of error reasons that caused promise rejections
 * @returns Concatenated error message string
 */
function errorMessageForReasons(reasons) {
    return reasons
        .map((reason) => {
        if (reason instanceof Error) {
            return reason.message;
        }
        else {
            return JSON.stringify(reason);
        }
    })
        .join("");
}
/**
 * Safely awaits a promise and returns a standardized result object.
 * Eliminates the need for repetitive try/catch blocks throughout the codebase.
 *
 * @param promiseOrFn - Either a promise or a function that returns a promise
 * @returns A standardized PromiseResult object indicating success or failure
 */
export async function tryAwait(promiseOrFn) {
    try {
        // Handle both promise and function that returns a promise
        const promise = typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn;
        const value = await promise;
        return makeFulfilledResult(value);
    }
    catch (error) {
        return makeRejectedResult(error);
    }
}
//# sourceMappingURL=promise-util.js.map