/**
 * Uso:
 * 
 *  const maybeNumber = Maybe.some(10);
 *  const doubled = maybeNumber.map(n => n * 2);
 *  console.log(doubled.getValue()); // 20
   
 *  const noValue = Maybe.none<number>();
 *  console.log(noValue.isSome()); // false
 **/

export interface IMaybe<T> {
  isSome(): boolean;
  isNone(): boolean;
  unwrap(): T | undefined;
  getOrUndefined(): T | undefined;
  map<U>(fn: (value: T) => U): IMaybe<U>;
  match<U>(someFn: (value: T) => U, noneFn: () => U): U;
}

export class Maybe<T> implements IMaybe<T> {
  private constructor(private readonly value?: T) {}

  static fromNullable<T>(value?: T): Maybe<T> {
    return value === undefined || value === null ? Maybe.none() : Maybe.some(value);
  }

  static some<T>(value: T): Maybe<T> {
    return new Maybe<T>(value);
  }

  static none<T>(): Maybe<T> {
    return new Maybe<T>();
  }

  isSome(): boolean {
    return this.value !== undefined;
  }

  isNone(): boolean {
    return !this.isSome();
  }

  unwrap(): T | undefined {
    return this.value;
  }

  getOrUndefined(): T | undefined {
    return this.unwrap();
  }

  map<U>(fn: (value: T) => U): Maybe<U> {
    return this.isSome() ? Maybe.some(fn(this.value as T)) : Maybe.none();
  }

  match<U>(someFn: (value: T) => U, noneFn: () => U): U {
    return this.isSome() ? someFn(this.value as T) : noneFn();
  }
}
