export interface IRequestContext {
  generation: number;
  isCurrent: () => boolean;
}

interface IInFlightRequest {
  generation: number;
  promise: Promise<unknown>;
}

/**
 * Runs keyed requests outside Smartstate's per-part action queue.
 * Same-key requests share a promise; different keys run concurrently.
 */
export class RequestScheduler {
  private generation = 0;
  private inFlight = new Map<string, IInFlightRequest>();

  public get currentGeneration(): number {
    return this.generation;
  }

  public invalidate(): number {
    this.generation++;
    this.inFlight.clear();
    return this.generation;
  }

  public isCurrent(generationArg: number): boolean {
    return generationArg === this.generation;
  }

  public hasInFlight(keyArg: string): boolean {
    const existing = this.inFlight.get(keyArg);
    return existing?.generation === this.generation;
  }

  public run<T>(
    keyArg: string,
    taskArg: (contextArg: IRequestContext) => Promise<T>,
  ): Promise<T> {
    const generation = this.generation;
    const existing = this.inFlight.get(keyArg);
    if (existing?.generation === generation) {
      return existing.promise as Promise<T>;
    }

    const context: IRequestContext = {
      generation,
      isCurrent: () => this.isCurrent(generation),
    };
    let promise!: Promise<T>;
    promise = Promise.resolve()
      .then(() => taskArg(context))
      .finally(() => {
        const current = this.inFlight.get(keyArg);
        if (current?.generation === generation && current.promise === promise) {
          this.inFlight.delete(keyArg);
        }
      });
    this.inFlight.set(keyArg, { generation, promise });
    return promise;
  }
}
