import { OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { IEventBus, DomainEvent, DrizzleTransaction } from './event-bus.protocol.js';
import { IEventReadPort, ListEventsQuery, EventPage } from './event-read.protocol.js';
import { DrizzleClient } from '../../types/drizzle.js';
import { EventsModuleOptions } from './events.module.js';
import { IBridgeOutboxDrainHook } from '../bridge/bridge.protocol.js';
import 'drizzle-orm/node-postgres';
import 'drizzle-orm';
import './event-registry.js';
import '../bridge/bridge-delivery.schema.js';
import 'drizzle-orm/pg-core';

/**
 * DrizzleEventBus — Postgres-backed event bus using the transactional outbox pattern.
 *
 * Events are inserted into the `domain_events` table within the caller's
 * Drizzle transaction. A background polling loop (started on module init)
 * reads unprocessed events and dispatches them to registered subscribers.
 *
 * When the transaction rolls back, the event is never persisted — no
 * phantom events.
 *
 * Pool awareness (EVT-4):
 * - On `publish`/`publishMany` the backend writes `metadata.pool`,
 *   `metadata.direction`, and `metadata.tenantId` into the first-class
 *   `pool` / `direction` / `tenant_id` columns (metadata JSON is still
 *   written unchanged for protocol stability).
 * - The drain loop filters by `opts.pools` when provided, so separate
 *   processes (e.g. one per `events_inbound` / `events_change` /
 *   `events_outbound`) can claim only their own lane. `pools: undefined`
 *   drains all pending rows (backwards-compatible behaviour).
 *
 * EVT-Q7: No stale-event sweeper. `FOR UPDATE SKIP LOCKED` is
 * self-healing — the row is only locked for the duration of the
 * enclosing polling transaction; the `status='processed'` update happens
 * within that same transaction. There is no `claimed_at` semantic (unlike
 * jobs), so no stale rows can exist.
 *
 * This backend is suitable until you need real-time fan-out or very high
 * throughput. At that point, swap the backend for Redis Streams or similar
 * via EventsModule.forRoot({ backend: '...' }) without touching use cases.
 */

declare class DrizzleEventBus implements IEventBus, IEventReadPort, OnModuleInit, OnModuleDestroy {
    private readonly db;
    /**
     * Bridge subsystem hook (BRIDGE-4). Optional — when the bridge
     * subsystem is not installed in the consuming app, this token is
     * undefined and the drain skips the bridge block entirely (preserves
     * EVT-4 baseline behaviour).
     *
     * When provided, `processEvent` is invoked once per drained event
     * INSIDE the per-event tx, before `processed_at` is stamped. The
     * hook owns all knowledge of `bridge_delivery + wrapper job_run`
     * shapes; the events subsystem stays unaware of bridge schemas.
     */
    private readonly bridgeHook;
    private readonly logger;
    private polling;
    private pollTimer;
    private readonly handlers;
    private readonly opts;
    private notifyListener;
    /** True while a wake-driven drain is in flight (debounce gate). */
    private wakeDraining;
    /** A notify arrived mid-drain → re-drain once when the current drain ends. */
    private wakeRecheckPending;
    constructor(db: DrizzleClient, opts?: EventsModuleOptions, 
    /**
     * Bridge subsystem hook (BRIDGE-4). Optional — when the bridge
     * subsystem is not installed in the consuming app, this token is
     * undefined and the drain skips the bridge block entirely (preserves
     * EVT-4 baseline behaviour).
     *
     * When provided, `processEvent` is invoked once per drained event
     * INSIDE the per-event tx, before `processed_at` is stamped. The
     * hook owns all knowledge of `bridge_delivery + wrapper job_run`
     * shapes; the events subsystem stays unaware of bridge schemas.
     */
    bridgeHook?: IBridgeOutboxDrainHook | null);
    onModuleInit(): Promise<void>;
    onModuleDestroy(): Promise<void>;
    /**
     * Wake handler — a `codegen_events_wake` notification arrived. A pool-filtered
     * drainer (`opts.pools` set) ignores payloads naming a pool it doesn't own; an
     * all-pools drainer wakes for any. Debounced: a notify mid-drain just flags a
     * re-check so a burst collapses to at most one extra drain (D3).
     */
    private onWake;
    private drainOnWake;
    publish(event: DomainEvent, tx?: DrizzleTransaction): Promise<void>;
    publishMany(events: DomainEvent[], tx?: DrizzleTransaction): Promise<void>;
    /**
     * Emit one in-tx `pg_notify(codegen_events_wake, <pool>)` per distinct pool in
     * the just-inserted batch. No-op unless `listenNotify` is on. Best-effort: a
     * notify failure is non-fatal (interval polling still drains the rows), so we
     * log + swallow rather than failing the publish.
     */
    private emitWakeNotify;
    findById(eventId: string): Promise<DomainEvent | null>;
    subscribe<T extends DomainEvent = DomainEvent>(eventType: string, handler: (event: T) => Promise<void>): () => void;
    listEvents(query?: ListEventsQuery): Promise<EventPage>;
    /**
     * Test-only hook. Runs exactly one drain cycle and returns. Production
     * code goes through `onModuleInit` → `schedulePoll`, which calls the
     * same `processBatch` under a timer.
     */
    drainOnce(): Promise<void>;
    private schedulePoll;
    /**
     * Drain one batch (BRIDGE-4 restructure of EVT-4).
     *
     * Two-phase per drained event:
     *
     *   1. **Per-event transaction** — bridge fanout (`bridgeHook.processEvent`)
     *      + `processed_at` stamp. Both write through the same `tx`. A throw
     *      inside the tx (only infra-level failures should reach here, since
     *      the hook tolerates null direction and registry misses inline)
     *      rolls back the bridge inserts AND the `processed_at` stamp; the
     *      event re-claims on the next drain cycle. Bridge `UNIQUE
     *      (event_id, trigger_id)` makes the retry idempotent.
     *
     *   2. **After commit** — dispatch in-process subscribers (`IEventBus.subscribe`
     *      handlers). This deliberately runs OUTSIDE the per-event tx (lead
     *      decision 2026-04-22): subscribers are best-effort and must not
     *      gate forward progress or roll back bridge fanout. Subscriber
     *      errors are caught + logged; `processed_at` is already committed.
     *      The old `MAX_RETRIES=3` in-process retry loop and the
     *      `failed`-stamping path were removed in BRIDGE-4 along with their
     *      coupling.
     *
     * The `processed_at` UPDATE carries `AND status='pending'` (BRIDGE-4
     * tightening — without it, a hypothetical double-claim could double-stamp
     * the timestamp). The per-event tx + `FOR UPDATE SKIP LOCKED` claim
     * make this defensive belt-and-suspenders.
     */
    private processBatch;
    private dispatch;
}

export { DrizzleEventBus };
