export class Result<T, E = Error> {
  private constructor(
    private readonly _isOk: boolean,
    private readonly _value?: T,
    private readonly _error?: E,
  ) {}

  get isOk(): boolean {
    return this._isOk;
  }

  get isErr(): boolean {
    return !this._isOk;
  }

  static ok<T, E = Error>(value: T): Result<T, E> {
    return new Result<T, E>(true, value);
  }

  static err<T, E = Error>(error: E): Result<T, E> {
    return new Result<T, E>(false, undefined, error);
  }

  unwrap(): T {
    if (this._isOk) return this._value as T;
    throw this._error;
  }

  unwrapErr(): E {
    if (!this._isOk) return this._error as E;
    throw new Error('Called unwrapErr on an Ok result');
  }

  map<U>(fn: (value: T) => U): Result<U, E> {
    if (this._isOk) return Result.ok(fn(this._value as T));
    return Result.err(this._error as E);
  }

  mapErr<F>(fn: (error: E) => F): Result<T, F> {
    if (!this._isOk) return Result.err(fn(this._error as E));
    return Result.ok(this._value as T);
  }

  andThen<U>(fn: (value: T) => Result<U, E>): Result<U, E> {
    if (this._isOk) return fn(this._value as T);
    return Result.err(this._error as E);
  }

  unwrapOr(defaultValue: T): T {
    return this._isOk ? (this._value as T) : defaultValue;
  }

  match<U>(handlers: { ok: (value: T) => U; err: (error: E) => U }): U {
    return this._isOk ? handlers.ok(this._value as T) : handlers.err(this._error as E);
  }
}
