// Local type aliases mirroring exports from `@repo/core-consent`.
// Kept inline to avoid a build-graph cycle between core-testing (tooling)
// and core-consent (optional core). Same pattern used by RecordingAuditLog,
// RecordingEventBus, and other recording doubles.

type ConsentCategory = string;
type ConsentState = "granted" | "denied" | "pending";

type ConsentGrantMeta = {
  bannerVersion?: string;
  policyVersion?: string;
  method?: string;
};

type UserConsentState = {
  readonly category: ConsentCategory;
  readonly state: ConsentState;
  readonly grantedAt?: Date;
  readonly withdrawnAt?: Date;
  readonly bannerVersion?: string;
  readonly policyVersion?: string;
  readonly method?: string;
};

/** Recorded grant call. */
export type RecordedConsentGrant = {
  category: ConsentCategory;
  meta: ConsentGrantMeta | undefined;
};

/**
 * Test-side recording double for IConsent. Records all grant/withdraw calls
 * for assertion while maintaining the same state semantics as the real impl
 * (isGranted returns true after grant, false after withdraw).
 *
 * Use directly via constructor injection:
 *   const consent = new RecordingConsent();
 *   const uc = myUseCase(consent);
 *   await uc({ ... });
 *   expect(consent.grants).toHaveLength(1);
 */
export class RecordingConsent {
  public grants: RecordedConsentGrant[] = [];
  public withdrawals: ConsentCategory[] = [];
  private state = new Map<ConsentCategory, UserConsentState>();

  isGranted(category: ConsentCategory): boolean {
    return this.state.get(category)?.state === "granted";
  }

  async grant(
    category: ConsentCategory,
    meta?: ConsentGrantMeta,
  ): Promise<void> {
    this.grants.push({ category, meta });
    const existing = this.state.get(category);
    this.state.set(category, {
      category,
      state: "granted",
      grantedAt: new Date(),
      withdrawnAt: existing?.withdrawnAt,
      bannerVersion: meta?.bannerVersion,
      policyVersion: meta?.policyVersion,
      method: meta?.method,
    });
  }

  async withdraw(category: ConsentCategory): Promise<void> {
    this.withdrawals.push(category);
    const existing = this.state.get(category);
    this.state.set(category, {
      ...existing,
      category,
      state: "denied",
      withdrawnAt: new Date(),
    });
  }

  getCategories(): UserConsentState[] {
    return Array.from(this.state.values());
  }

  reset(): void {
    this.grants = [];
    this.withdrawals = [];
    this.state.clear();
  }
}
