export class Result<T, E extends Error = Error> {
  private readonly _isSuccess: boolean;
  private readonly _data?: T;
  private readonly _error?: E;

  private constructor(props: { isSuccess: boolean; error?: E; data?: T }) {
    const { isSuccess, error, data } = 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._error = error;
    this._data = data;

    Object.freeze(this);
  }

  static ok<T>(data?: T): Result<T, never> {
    return new Result<T, never>({ isSuccess: true, data });
  }

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

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

    return Result.ok<any>();
  }

  get isSuccess(): boolean {
    return this._isSuccess;
  }

  get isFailure(): boolean {
    return !this._isSuccess;
  }

  get data(): T {
    return this.getData();
  }

  get error(): E {
    return this.getError();
  }

  getError(): E {
    if (this._isSuccess) {
      throw new Error("Cannot get error from a successful result.");
    }
    return this._error as E;
  }

  getData(): T {
    if (!this._isSuccess) {
      throw new Error("Cannot get value data from a failed result.");
    }
    return this._data as T;
  }

  map<U>(fn: (value: T) => U): Result<U, Error> {
    if (this.isSuccess && this._data !== undefined) {
      return Result.ok<U>(fn(this.data));
    }
    return Result.fail(this.error || new Error("Unknown error"));
  }

  /**
   * 🔹 `getOrElse(defaultValue: T): T`
   * Si el `Result` es un `ok`, devuelve `data`, de lo contrario, devuelve `defaultValue`.
   */
  getOrElse(defaultValue: any): T | any {
    return this._isSuccess ? this.data : defaultValue;
  }

  /**
   * 🔹 `match<R>(onOk: (data: T) => R, onError: (error: E) => R): R`
   * Evalúa el `Result`: ejecuta `onOk()` si es `ok` o `onError()` si es `fail`.
   */
  match<R>(onOk: (data: T) => R, onError: (error: E) => R): R {
    return this._isSuccess ? onOk(this.data) : onError(this.error);
  }
}
