import { enforce } from "vscript";

export class RollbackStack {
  private mHandlers: Array<() => Promise<void>> = [];
  private mArmed = true;

  public push(f: () => Promise<void>) {
    enforce(this.mArmed);
    this.mHandlers.push(f);
  }

  public async rollback() {
    if (!this.mArmed) return;
    this.mArmed = false;
    const msgs: string[] = [];
    for (const handler of this.mHandlers.reverse()) {
      try {
        await handler();
      } catch (e: any) {
        let msg = "Failure during rollback";
        if (typeof e.toString() === "function") {
          msg += `: ${e.toString()}`;
        }
        if (typeof e.stack === "string") {
          msg += e.stack;
        }
        msgs.push(msg);
      }
    }
    if (msgs.length !== 0)
      throw new Error(
        `${msgs.length} failure${msgs.length > 1 ? "s" : ""} during rollback: ${msgs.join(",\n")}`,
      );
  }

  // assumes atomicity -- rollback handler will only be registered if f succeeds
  public async with_rollback_do<T>(f: () => Promise<T>, fInverse: () => Promise<void>): Promise<T> {
    const result = await f();
    this.push(fInverse);
    return result;
  }
}
