import type pg from 'pg';
import { type Kysely, type Transaction } from 'kysely';
import type { Database } from '../db/types.js';
import type { LockFile, WorkflowDecision } from '@kici-dev/engine';
import type { EventStore } from './event-store.js';
import type { EventCircuitBreaker } from './circuit-breaker.js';
import type { TrustStore } from './trust-store.js';
import type { RegistrationIndex } from '../registration/registration-index.js';
import type { EventRouterConfig, StoredEvent } from './types.js';
/**
 * Input for emitting an internal event.
 */
export interface EmitEventInput {
    eventName: string;
    payload: Record<string, unknown>;
    sourceRepo?: string;
    sourceRoutingKey?: string;
    sourceRunId?: string;
    sourceJobId?: string;
    /** Optional cross-repo targeting: deliver only to registrations matching these repos */
    target?: {
        repos?: string[];
    };
    chainDepth?: number;
}
/**
 * Context about the registration that matched an event.
 */
export interface EventMatchContext {
    /** Routing key (e.g., "github:42") for provider resolution */
    routingKey: string;
    /** Repository identifier (e.g., "owner/repo") */
    repoIdentifier: string;
    /** Provider-specific credentials captured at registration time */
    providerContext: Record<string, unknown>;
}
export interface EventRouterOptions {
    db: Kysely<Database>;
    pool: pg.Pool;
    eventStore: EventStore;
    circuitBreaker: EventCircuitBreaker;
    trustStore: TrustStore;
    config: EventRouterConfig;
    onEventMatched: (event: StoredEvent, lockFile: LockFile, matchedWorkflows: WorkflowDecision[], context?: EventMatchContext) => Promise<void>;
    /** Registration index for DB-backed subscription matching. Events match against persistent registrations. */
    registrationIndex: RegistrationIndex;
    /** Stable identifier for this orchestrator process — written into `kici_events.claimed_by` for diagnostics. */
    nodeId: string;
}
/**
 * EventRouter delivers internal events to matching workflows via PostgreSQL LISTEN/NOTIFY.
 *
 * Follows the WebhookSecretManager pattern: raw pg.PoolClient for LISTEN,
 * Kysely for queries.
 *
 * On start, performs a catch-up scan for events missed during downtime.
 * On notification, leases the event from DB, matches against registered
 * workflows via the RegistrationIndex, and calls onEventMatched for each
 * match. Failures schedule a retry via `recordDispatchFailure`; events that
 * exhaust `maxDispatchAttempts` are moved to the DLQ via `markDlq`.
 */
export declare class EventRouter {
    private readonly db;
    private readonly pool;
    private readonly eventStore;
    private readonly circuitBreaker;
    private readonly trustStore;
    private readonly config;
    private readonly onEventMatched;
    private readonly registrationIndex;
    private readonly nodeId;
    private client;
    private lastProcessedEventId;
    constructor(options: EventRouterOptions);
    /**
     * Start listening for events via PostgreSQL LISTEN/NOTIFY.
     * Runs catch-up on start to process events missed during downtime.
     */
    start(): Promise<void>;
    /**
     * Stop listening and release the dedicated pg client.
     */
    stop(): Promise<void>;
    /**
     * Emit an internal event using the default DB handle.
     *
     * Wraps `emitInTx` in its own transaction. Callers that need to combine
     * the emit with other transactional work (cron-fire being the canonical
     * case) should call `emitInTx` directly with their own tx so all writes
     * commit or roll back together.
     */
    emit(event: EmitEventInput): Promise<string>;
    /**
     * Emit an internal event inside a caller-provided transaction.
     *
     * Circuit-breaker checks (chain depth + rate limit) run BEFORE the
     * transaction so they fail fast without burning DB roundtrips. The
     * `kici_events` insert and the `pg_notify` are issued on the supplied
     * `tx` so they only become visible (and the notification is only
     * delivered) on commit. Postgres queues NOTIFYs issued inside a
     * transaction and releases them at COMMIT; if the tx rolls back, no
     * listener is woken — exactly what we need to make cron-fire atomic.
     */
    emitInTx(event: EmitEventInput, tx: Transaction<Database>): Promise<string>;
    /**
     * Handle a LISTEN/NOTIFY notification for a new event.
     *
     * Lease-based dispatch (replaces the previous "flip processed=true upfront"
     * pattern that silently lost events when dispatch threw):
     *
     *  1. Take a dispatch lease (claimed_at + claimed_by, attempts++).
     *     Fails fast if the event is already processed, in DLQ, or held by a
     *     fresh lease on another node.
     *  2. Run dispatch. On success: markProcessed (clears lease, processed=true).
     *  3. On failure: either schedule a retry (recordDispatchFailure → leader
     *     scanner re-publishes pg_notify when next_retry_at elapses) or move
     *     to the DLQ (markDlq) when attempts exceed maxDispatchAttempts.
     *
     * If the dispatching node crashes between lease and finalisation, the
     * leader-only retry scanner releases the lease after leaseDurationMs and
     * re-publishes pg_notify so a healthy node can re-dispatch.
     */
    private onNotification;
    /**
     * Run dispatch for a leased event and record the outcome (processed,
     * retry, or DLQ). Used by both the live notification handler and the
     * catch-up loop so the failure semantics stay identical.
     */
    private dispatchAndRecord;
    /**
     * Match an event against all registered workflows and dispatch matches.
     *
     * Looks up registrations by trigger type via the RegistrationIndex,
     * builds per-registration lock files, and runs trigger matching against them.
     *
     * Errors propagate so the lease wrapper (`dispatchAndRecord`) can either
     * schedule a retry or move the event to the DLQ. Swallowing the error
     * here would silently lose the event — exactly the failure mode the
     * lease pattern fixes.
     */
    private processSubscriptions;
    /**
     * Map a stored event to a trigger type string for RegistrationIndex lookup.
     *
     * System events (__workflow_complete, __job_complete) map to their type without __ prefix.
     * Custom events map to 'kici_event'.
     */
    private eventToTriggerType;
    /**
     * Build a SimulatedEvent from a StoredEvent for trigger matching.
     *
     * System events (__workflow_complete, __job_complete) map their type by
     * stripping the __ prefix and pass the payload through directly (the
     * matcher checks payload.workflowName, payload.status, etc.).
     *
     * Custom events use type 'kici_event'. The matcher reads eventName and
     * the user payload from event.payload.eventName / event.payload.payload,
     * so we wrap the stored event's metadata into the expected structure.
     */
    private buildSimulatedEvent;
    /**
     * Catch-up: process unprocessed events missed during downtime.
     *
     * Uses lease-based dispatch identical to the live path, so a catch-up
     * dispatch failure schedules a retry via the leader-only scanner instead
     * of being silently dropped.
     */
    private catchUp;
}
/**
 * Compute the next retry timestamp using exponential backoff with full
 * jitter. `attempts` is the count AFTER the most recent failed attempt.
 *
 *   target = min(maxBackoffMs, baseBackoffMs * 2^(attempts - 1))
 *   actual = random in [0, target]
 *
 * Full jitter (vs. equal jitter or no jitter) gives the best balance
 * between worst-case retry concentration and average latency, per the AWS
 * "Exponential backoff and jitter" article. baseBackoffMs and
 * maxBackoffMs come from EventRouterConfig so tests can shrink them.
 */
export declare function computeNextRetryAt(attempts: number, baseBackoffMs: number, maxBackoffMs: number, now?: () => number): Date;
//# sourceMappingURL=event-router.d.ts.map