"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AsyncResult = exports.Result = void 0;
/* eslint-disable promise/no-nesting */
const logger_1 = require("../logger");
/**
 * 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 (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 nullError = arg2;
        const undefinedError = arg3 ?? arg2;
        if (input instanceof Promise) {
            return AsyncResult.wrapNullable(input, nullError, undefinedError);
        }
        try {
            const result = input();
            if (result === null) {
                return Result.err(nullError);
            }
            if (result === undefined) {
                return Result.err(undefinedError);
            }
            return Result.ok(result);
        }
        catch (error) {
            return Result.err(error);
        }
    }
    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;
    }
    transform(fn) {
        if (!this.res.ok) {
            return Result.err(this.res.err);
        }
        try {
            const res = fn(this.res.val);
            if (res instanceof Result) {
                return res;
            }
            if (res instanceof Promise) {
                return new AsyncResult((resolve) => {
                    res
                        .then((newResult) => newResult instanceof Result
                        ? resolve(newResult)
                        : resolve(Result.ok(newResult)))
                        .catch((err) => {
                        logger_1.logger.warn({ err }, 'Result: unhandled async transform error');
                        resolve(Result._uncaught(err));
                    });
                });
            }
            return Result.ok(res);
        }
        catch (err) {
            logger_1.logger.warn({ err }, 'Result: unhandled transform error');
            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 extends Promise {
    constructor(executor) {
        super(executor);
    }
    static ok(val) {
        return new AsyncResult((resolve) => resolve(Result.ok(val)));
    }
    static err(err) {
        return new AsyncResult((resolve) => resolve(Result.err(err)));
    }
    static wrap(promise) {
        return new AsyncResult((resolve) => {
            promise
                .then((value) => {
                if (value instanceof Result) {
                    return resolve(value);
                }
                return resolve(Result.ok(value));
            })
                .catch((err) => resolve(Result.err(err)));
        });
    }
    static wrapNullable(promise, nullError, undefinedError) {
        return new AsyncResult((resolve) => {
            promise
                .then((value) => {
                if (value === null) {
                    return resolve(Result.err(nullError));
                }
                if (value === undefined) {
                    return resolve(Result.err(undefinedError));
                }
                return resolve(Result.ok(value));
            })
                .catch((err) => resolve(Result.err(err)));
        });
    }
    unwrap(fallback) {
        return fallback === undefined
            ? this.then((res) => res.unwrap())
            : this.then((res) => res.unwrap(fallback));
    }
    transform(fn) {
        return new AsyncResult((resolve) => {
            this.then((oldResult) => {
                const { ok, val: value, err: error } = oldResult.unwrap();
                if (!ok) {
                    return resolve(Result.err(error));
                }
                try {
                    const newResult = fn(value);
                    if (newResult instanceof Result) {
                        return resolve(newResult);
                    }
                    if (newResult instanceof Promise) {
                        return newResult
                            .then((asyncRes) => asyncRes instanceof Result
                            ? resolve(asyncRes)
                            : resolve(Result.ok(asyncRes)))
                            .catch((err) => {
                            logger_1.logger.warn({ err }, 'AsyncResult: unhandled async transform error');
                            return resolve(Result._uncaught(err));
                        });
                    }
                    return resolve(Result.ok(newResult));
                }
                catch (err) {
                    logger_1.logger.warn({ err }, 'AsyncResult: unhandled transform error');
                    return resolve(Result._uncaught(err));
                }
            }).catch((err) => {
                // Happens when `.unwrap()` of `oldResult` throws
                resolve(Result._uncaught(err));
            });
        });
    }
}
exports.AsyncResult = AsyncResult;
//# sourceMappingURL=result.js.map