export interface SourceInspectionToken {
  readonly assetId: string;
  readonly generation: number;
  readonly signal: AbortSignal;
}

/**
 * Tracks the exact runtime import that owns queued or active inspection work.
 *
 * Project asset IDs can be reused when a missing source is reattached, so an
 * ID-only check is insufficient: a late result from the old File must not be
 * applied to the replacement. Tokens are intentionally kept outside React
 * state so removal invalidates work synchronously.
 */
export class SourceInspectionRegistry {
  readonly #currentByAsset = new Map<
    string,
    { readonly generation: number; readonly controller: AbortController }
  >();
  #nextGeneration = 0;
  #activeToken: SourceInspectionToken | undefined;

  register(assetId: string): SourceInspectionToken {
    const controller = new AbortController();
    const token = Object.freeze({
      assetId,
      generation: ++this.#nextGeneration,
      signal: controller.signal,
    });
    this.#currentByAsset.get(assetId)?.controller.abort();
    this.#currentByAsset.set(assetId, {
      generation: token.generation,
      controller,
    });
    return token;
  }

  isCurrent(token: SourceInspectionToken): boolean {
    return (
      this.#currentByAsset.get(token.assetId)?.generation === token.generation
    );
  }

  activate(token: SourceInspectionToken): boolean {
    if (!this.isCurrent(token)) return false;
    this.#activeToken = token;
    return true;
  }

  finish(token: SourceInspectionToken): void {
    if (
      this.#activeToken?.assetId === token.assetId &&
      this.#activeToken.generation === token.generation
    ) {
      this.#activeToken = undefined;
    }
  }

  remove(assetId: string): boolean {
    const current = this.#currentByAsset.get(assetId);
    current?.controller.abort();
    this.#currentByAsset.delete(assetId);
    return (
      this.#activeToken?.assetId === assetId &&
      this.#activeToken.generation === current?.generation
    );
  }

  clear(): boolean {
    for (const current of this.#currentByAsset.values()) {
      current.controller.abort();
    }
    this.#currentByAsset.clear();
    return this.#activeToken !== undefined;
  }
}
