import { Type, DynamicModule } from '@nestjs/common';

/**
 * EventsModule — DynamicModule factory for the event bus subsystem.
 *
 * Register once in AppModule:
 * ```typescript
 * @Module({
 *   imports: [
 *     EventsModule.forRoot({ backend: 'drizzle' }),
 *   ],
 * })
 * export class AppModule {}
 * ```
 *
 * Tests swap to the memory backend without touching application code:
 * ```typescript
 * Test.createTestingModule({
 *   imports: [EventsModule.forRoot({ backend: 'memory' })],
 * });
 * ```
 *
 * Per-pool drain isolation (EVT-4):
 * ```typescript
 * EventsModule.forRoot({ backend: 'drizzle', pools: ['events_change'] });
 * ```
 * Each process restricts its drain loop to the pools listed here. `pools`
 * is undefined by default → drain all pending rows (backwards-compatible).
 *
 * Typed facade + multi-tenancy (EVT-6):
 * - `TYPED_EVENT_BUS` resolves to the generated `TypedEventBus` wrapping
 *   whichever backend is selected.
 * - `multiTenant: true` makes `TypedEventBus.publish` throw
 *   `MissingTenantIdError` when the caller forgets `metadata.tenantId`.
 *
 * `global: true` means entity modules do not need to import EventsModule
 * individually — the EVENT_BUS and TYPED_EVENT_BUS tokens are available
 * project-wide.
 *
 * Async configuration (`forRootAsync`):
 * The async factory returns `EventsModuleOptions`; the EVENT_BUS provider
 * then receives its backend dependencies — DRIZZLE for the drizzle
 * backend, REDIS_URL for the redis backend, the resolved options for the
 * memory backend — through a proper `useFactory` so Nest DI wires them
 * correctly. Earlier revisions hand-constructed backends with
 * `new Class()` which silently left `db` / `redisUrl` undefined
 * (issue #108).
 */

interface EventsModuleOptions {
    backend: 'drizzle' | 'memory' | 'redis';
    /**
     * Redis connection URL used when `backend` is `'redis'`.
     * Falls back to the REDIS_URL environment variable, then
     * `redis://localhost:6379` if neither is set.
     */
    redisUrl?: string;
    /**
     * Restrict the drain loop to these pools. Each pool name matches the
     * `domain_events.pool` column (populated from `event.metadata.pool` at
     * publish time). Leave undefined to drain all pending rows.
     *
     * Typical lane split: one process per `events_inbound` /
     * `events_change` / `events_outbound` so a slow outbound handler
     * cannot stall change-event propagation (see ADR-022).
     */
    pools?: string[];
    /**
     * LISTEN-NOTIFY-1 — when `true` (drizzle backend only), the drainer holds a
     * dedicated listener connection and LISTENs on `codegen_events_wake`. Each
     * `publish`/`publishMany` emits an in-tx `pg_notify(codegen_events_wake,
     * <pool>)` so the drainer wakes the moment the publishing transaction commits,
     * instead of waiting for the next poll tick. Polling continues unchanged as
     * the fallback heartbeat; a lost notify degrades to poll latency, never to
     * lost work. Defaults to `false`.
     *
     * Ignored by the memory + redis backends (memory dispatches inline; redis has
     * its own fan-out). Requires a direct (non-transaction-pooler) connection —
     * see the events/jobs config block re: PgBouncer.
     */
    listenNotify?: boolean;
    /**
     * Multi-tenancy opt-in (EVT-6).
     *
     * When `true`, every `TypedEventBus.publish()` call must supply
     * `opts.metadata.tenantId` — otherwise it throws `MissingTenantIdError`.
     * The tenantId is preserved on `event.metadata` and, for the Drizzle
     * backend, written to `domain_events.tenant_id` (EVT-4).
     *
     * Drain-side tenant filtering is deferred — the tenant-context model
     * (per-process vs. per-request vs. async-local-storage) is still
     * unsettled; see ADR-024 §Multi-tenancy. Only the publish-side
     * requirement ships here.
     *
     * Defaults to `false`.
     */
    multiTenant?: boolean;
    /**
     * The generated `TypedEventBus` class to bind to `TYPED_EVENT_BUS`.
     *
     * **Package mode (ADR-037).** When the runtime is imported from
     * `@pattern-stack/codegen` (not vendored), the bundled `./generated/bus`
     * `TypedEventBus` is typed to an EMPTY event union and reads the bundled
     * empty `eventRegistry` — a consumer's `events/*.yaml` are scanned into
     * `src/generated/events/bus.ts` (typed to THEIR union, reading THEIR
     * registry), which the package can't import. The generated subsystem barrel
     * therefore threads that class in here:
     * `EventsModule.forRoot({ ..., typedBus: TypedEventBus })`. Nest constructs
     * it with this module's `EVENT_BUS` + `EVENTS_MULTI_TENANT` providers (the
     * generated class injects the same string-valued tokens, which match across
     * the package boundary).
     *
     * Omitted (vendored mode / tests) ⇒ falls back to the bundled
     * `./generated/bus`, which in a vendored tree IS the consumer's generated
     * file. Without this, a package-mode consumer's typed `publish<'…'>()` calls
     * resolve against the empty union and their events never get the right
     * `pool` / `direction` stamped.
     *
     * Only consulted by `forRoot` (the path the barrel emits); `forRootAsync`
     * keeps the bundled bus.
     */
    typedBus?: Type<unknown>;
}
interface EventsModuleAsyncOptions {
    useFactory: (...args: unknown[]) => Promise<EventsModuleOptions> | EventsModuleOptions;
    inject?: unknown[];
    imports?: unknown[];
}
declare class EventsModule {
    static forRootAsync(asyncOptions: EventsModuleAsyncOptions): DynamicModule;
    static forRoot(options?: EventsModuleOptions): DynamicModule;
}

export { EventsModule, type EventsModuleAsyncOptions, type EventsModuleOptions };
