
export const executeTco = <T>(v: TcoLike<T>): T => {
  let value: TcoLike<unknown> = v;
  let maps: Array<(v: unknown) => TcoLike<unknown>> = [];
  while (value instanceof Tco || value instanceof TcoThen || maps.length)
    if (value instanceof TcoThen) {
      maps.unshift(value.func);
      value = value.from;
    } else if (value instanceof Tco) {
      value = value.func();
    } else if (maps.length) {
      value = maps.shift()?.(value);
    }
  return value as T;
}

export class Tco<T> {

  constructor(public func: TcoFunc<T>) {

  }

  execute(): T {
    return executeTco(this);
  }

  then<U>(func: (val: T) => TcoLike<U>): TcoThen<T, U> {
    return new TcoThen(this, func);
  }

}

export class TcoThen<T, U> {

  constructor(public from: TcoLike<T>, public func: (val: T) => TcoLike<U>) {

  }

  execute(): U {
    return executeTco(this);
  }

  then<V>(func: (val: U) => TcoLike<V>): TcoThen<U, V> {
    return new TcoThen(this, func);
  }

}

export type TcoLike<T> = T | Tco<T> | TcoThen<any, T>;
export type TcoFunc<T> = () => TcoLike<T>;

export const tco = <T extends any>(f: TcoFunc<T>) => new Tco(f);
