"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AsyncResult = exports.Result = void 0;
const zod_1 = require("zod");
const logger_1 = require("../logger");
function isZodResult(input) {
    if (typeof input !== 'object' ||
        input === null ||
        Object.keys(input).length !== 2 ||
        !('success' in input) ||
        typeof input.success !== 'boolean') {
        return false;
    }
    if (input.success) {
        return ('data' in input &&
            typeof input.data !== 'undefined' &&
            input.data !== null);
    }
    else {
        return 'error' in input && input.error instanceof zod_1.ZodError;
    }
}
function fromZodResult(input) {
    return input.success ? Result.ok(input.data) : Result.err(input.error);
}
/**
 * Class for representing a result that can fail.
 *
 * The mental model:
 * - `.wrap()` and `.wrapNullable()` are sinks
 * - `.transform()` are pipes which can be chained
 * - `.unwrap()` is the point of consumption
 */
class Result {
    res;
    constructor(res) {
        this.res = res;
    }
    static ok(val) {
        return new Result({ ok: true, val });
    }
    static err(err) {
        return new Result({ ok: false, err });
    }
    static _uncaught(err) {
        return new Result({ ok: false, err, _uncaught: true });
    }
    static wrap(input) {
        if (isZodResult(input)) {
            return fromZodResult(input);
        }
        if (input instanceof Promise) {
            return AsyncResult.wrap(input);
        }
        try {
            const result = input();
            return Result.ok(result);
        }
        catch (error) {
            return Result.err(error);
        }
    }
    static wrapNullable(input, arg2, arg3) {
        const errForNull = arg2;
        const errForUndefined = arg3 ?? arg2;
        if (input instanceof Promise) {
            return AsyncResult.wrapNullable(input, errForNull, errForUndefined);
        }
        try {
            const result = input();
            if (result === null) {
                return Result.err(errForNull);
            }
            if (result === undefined) {
                return Result.err(errForUndefined);
            }
            return Result.ok(result);
        }
        catch (error) {
            return Result.err(error);
        }
    }
    /**
     * Wraps a Zod schema and returns a parse function that returns a `Result`.
     */
    static wrapSchema(schema) {
        return (input) => {
            const result = schema.safeParse(input);
            return fromZodResult(result);
        };
    }
    /**
     * Wraps a Zod schema and returns a parse function that returns an `AsyncResult`.
     */
    static wrapSchemaAsync(schema) {
        return (input) => {
            const result = schema.safeParseAsync(input);
            return AsyncResult.wrap(result);
        };
    }
    unwrap(fallback) {
        if (this.res.ok) {
            return fallback === undefined ? this.res : this.res.val;
        }
        if (fallback !== undefined) {
            return fallback;
        }
        if (this.res._uncaught) {
            throw this.res.err;
        }
        return this.res;
    }
    /**
     * Returns the ok-value or throw the error.
     */
    unwrapOrThrow() {
        if (this.res.ok) {
            return this.res.val;
        }
        throw this.res.err;
    }
    transform(fn) {
        if (!this.res.ok) {
            return Result.err(this.res.err);
        }
        try {
            const result = fn(this.res.val);
            if (result instanceof Result) {
                return result;
            }
            if (result instanceof AsyncResult) {
                return result;
            }
            if (isZodResult(result)) {
                return fromZodResult(result);
            }
            if (result instanceof Promise) {
                return AsyncResult.wrap(result, (err) => {
                    logger_1.logger.warn({ err }, 'Result: unhandled async transform error');
                    return Result._uncaught(err);
                });
            }
            return Result.ok(result);
        }
        catch (err) {
            logger_1.logger.warn({ err }, 'Result: unhandled transform error');
            return Result._uncaught(err);
        }
    }
    catch(fn) {
        if (this.res.ok) {
            return this;
        }
        if (this.res._uncaught) {
            return this;
        }
        try {
            const result = fn(this.res.err);
            if (result instanceof Promise) {
                return AsyncResult.wrap(result, (err) => {
                    logger_1.logger.warn({ err }, 'Result: unexpected error in async catch handler');
                    return Result._uncaught(err);
                });
            }
            return result;
        }
        catch (err) {
            logger_1.logger.warn({ err }, 'Result: unexpected error in catch handler');
            return Result._uncaught(err);
        }
    }
}
exports.Result = Result;
/**
 * This class is being used when `Result` methods encounter async code.
 * It isn't meant to be used directly, but exported for usage in type annotations.
 *
 * All the methods resemble `Result` methods, but work asynchronously.
 */
class AsyncResult {
    asyncResult;
    constructor(asyncResult) {
        this.asyncResult = asyncResult;
    }
    then(onfulfilled) {
        return this.asyncResult.then(onfulfilled);
    }
    static ok(val) {
        return new AsyncResult(Promise.resolve(Result.ok(val)));
    }
    static err(err) {
        // eslint-disable-next-line promise/no-promise-in-callback
        return new AsyncResult(Promise.resolve(Result.err(err)));
    }
    static wrap(promise, onErr) {
        return new AsyncResult(promise
            .then((value) => {
            if (value instanceof Result) {
                return value;
            }
            if (isZodResult(value)) {
                return fromZodResult(value);
            }
            return Result.ok(value);
        })
            .catch((err) => {
            if (onErr) {
                return onErr(err);
            }
            return Result.err(err);
        }));
    }
    static wrapNullable(promise, errForNull, errForUndefined) {
        return new AsyncResult(promise
            .then((value) => {
            if (value === null) {
                return Result.err(errForNull);
            }
            if (value === undefined) {
                return Result.err(errForUndefined);
            }
            return Result.ok(value);
        })
            .catch((err) => Result.err(err)));
    }
    unwrap(fallback) {
        return fallback === undefined
            ? this.asyncResult.then((res) => res.unwrap())
            : this.asyncResult.then((res) => res.unwrap(fallback));
    }
    /**
     * Returns the ok-value or throw the error.
     */
    async unwrapOrThrow() {
        const result = await this.asyncResult;
        return result.unwrapOrThrow();
    }
    transform(fn) {
        return new AsyncResult(this.asyncResult
            .then((oldResult) => {
            const { ok, val: value, err: error } = oldResult.unwrap();
            if (!ok) {
                return Result.err(error);
            }
            try {
                const result = fn(value);
                if (result instanceof Result) {
                    return result;
                }
                if (result instanceof AsyncResult) {
                    return result;
                }
                if (isZodResult(result)) {
                    return fromZodResult(result);
                }
                if (result instanceof Promise) {
                    return AsyncResult.wrap(result, (err) => {
                        logger_1.logger.warn({ err }, 'AsyncResult: unhandled async transform error');
                        return Result._uncaught(err);
                    });
                }
                return Result.ok(result);
            }
            catch (err) {
                logger_1.logger.warn({ err }, 'AsyncResult: unhandled transform error');
                return Result._uncaught(err);
            }
        })
            .catch((err) => {
            // Happens when `.unwrap()` of `oldResult` throws
            return Result._uncaught(err);
        }));
    }
    catch(fn) {
        const caughtAsyncResult = this.asyncResult.then((result) => 
        // eslint-disable-next-line promise/no-nesting
        result.catch(fn));
        return AsyncResult.wrap(caughtAsyncResult);
    }
}
exports.AsyncResult = AsyncResult;
//# sourceMappingURL=result.js.map