export class Result<T, E extends Error = Error> {
  protected readonly _object?: T;
  protected readonly _error?: E;

  public readonly isSuccess: boolean;
  public readonly isFailure: boolean;

  protected constructor(props: { isSuccess: boolean; error?: E; object?: T }) {
    const { isSuccess, error, object } = props;
    if (isSuccess && error) {
      throw new Error(
        `InvalidOperation: A result cannot be successful and contain an error`,
      );
    }
    if (!isSuccess && !error) {
      throw new Error(
        `InvalidOperation: A failing result needs to contain an error message`,
      );
    }

    this.isSuccess = isSuccess;
    this.isFailure = !isSuccess;
    this._error = error;
    this._object = object;

    Object.freeze(this);
  }

  public get object(): T {
    if (this.isFailure) {
      throw new Error(`Result is not successful`);
    }

    return this._object as T;
  }

  public get error(): E {
    if (this.isSuccess) {
      throw new Error(`Result is not error`);
    }

    return this._error as E;
  }

  // Constructores públicos
  public static ok<U>(object?: U): Result<U, never> {
    return new Result<U, never>({ isSuccess: true, object });
  }

  public static fail<E extends Error = Error>(error?: E): Result<never, E> {
    return new Result<never, E>({ isSuccess: false, error });
  }

  public static combine(results: Result<any, any>[]): Result<any, any> {
    for (const result of results) {
      if (result.isFailure) {
        return result;
      }
    }

    return Result.ok<any>();
  }
}
