import { DetectionConfig } from './detection-config.schema.js';
import { IChangeSource, IntegrationSubscriptionView, Change } from './integration-change-source.protocol.js';
import { ChangeMiddleware } from './integration-middleware.protocol.js';
import 'zod';

/**
 * Integration subsystem — `WebhookChangeSource<T>` primitive (#226-4, ADR-033).
 *
 * Generic webhook-mode `IChangeSource<T>` implementation parameterized by a
 * parsed `DetectionConfig` (webhook mode) and a consumer-supplied
 * `WebhookFetchCallback<T>` that iterates a consumer-owned inbound staging
 * queue. The primitive owns:
 *
 *   - canonical `Change<T>.source = 'webhook'` stamping;
 *   - `dedupKey` derivation, preferring the `eventId` yielded alongside the
 *     record by the queue iterator, and falling back to the configured
 *     `webhook.eventIdField` on the emitted record when no `eventId` is yielded
 *     (precedence: yielded `eventId` > `eventIdField` record extraction >
 *     undefined `dedupKey`);
 *   - `externalId` derivation: the mapping entry whose `target === 'external_id'`
 *     names — via its `source` — the field on the emitted record that carries
 *     the canonical external id (mirrors `PollChangeSource`);
 *   - middleware-chain composition via the locked `ChangeMiddleware<T>` shape
 *     (#226-1) — same composition seam as the poll primitive.
 *
 * The primitive is **passive**: it iterates whatever the consumer-owned
 * queue yields. It does NOT synchronously drive the orchestrator, does NOT
 * own a transport, and does NOT manage acks. The inbound staging table
 * schema is consumer-owned and deferred per ADR-0002 §Phase 4 — the
 * `WebhookFetchCallback<T>` is the queue contract the consumer injects.
 *
 * Shape locks (decision memo Q5, mirrored from poll primitive):
 *   - `WebhookFetchContext = { subscription, cursor }` — explicitly NO
 *     `userId` / `tenantId`. Run-scope identity is closed over by the
 *     consumer at queue construction or resolved inside the callback via
 *     consumer services. There are no `filters` on the webhook context —
 *     filtering is done at registration / on the staging row, not at the
 *     port seam (the queue is already filtered by the time the primitive
 *     iterates).
 *
 * Long-lived streaming CDC primitives (SFDC Pub-Sub gRPC, Debezium/Kafka,
 * Postgres logical replication) are deferred to `#226-8` — they need a
 * fundamentally different lifecycle (`subscribe(onChange, onError)`,
 * server-paced backpressure, ack-on-yield) and shouldn't be retrofitted
 * into either this primitive or the poll primitive.
 */

/**
 * Opaque webhook cursor shape. Webhook mode typically has a cursor of
 * `{ ts: ISO-string }` (last drained staging-row timestamp) but the
 * primitive treats it as opaque. Consumer-owned queue iterators interpret
 * it however the staging schema needs.
 */
type WebhookCursor = unknown;
/**
 * Context the primitive forwards to the queue iterator. Locked to exactly
 * two fields per the same Q5 reasoning that locks `PollFetchContext` — no
 * `userId` / `tenantId`.
 */
interface WebhookFetchContext {
    readonly subscription: IntegrationSubscriptionView;
    readonly cursor: WebhookCursor | null;
}
/**
 * Consumer-supplied queue iterator. Returns an async iterable of
 * `{ record, eventId?, cursor? }` tuples — the consumer drains the inbound
 * staging queue and emits already-mapped canonical records `T`. The primitive
 * stamps `source: 'webhook'` and derives `dedupKey` with this precedence:
 *
 *   1. the yielded `eventId` (vendor delivery metadata — the queue is the
 *      right channel for it: a vendor's event id should never need a field
 *      on the vendor-neutral canonical record);
 *   2. else the record field named by `webhook.eventIdField`, when configured;
 *   3. else `undefined`.
 *
 * Yielding `eventId` is the safe channel when one canonical record identity
 * (the `external_id`) can recur across distinct vendor events in a single
 * drain batch — e.g. a message create and its later edit share an
 * `external_id` but are different events. Reading dedup identity off the
 * record (`eventIdField`) collapses those into one `dedupKey`; the yielded
 * `eventId` keeps them distinct. The consumer is the one who decided when a
 * staging row is "ready" to drain.
 *
 * Webhook mode has no per-record cursor advance — the staging-row drain
 * order is consumer policy (FIFO by ingestion timestamp, by event id, etc.)
 * and is opaque to the primitive. The orchestrator's last-yielded cursor
 * is whatever the consumer chooses to surface, if anything.
 */
type WebhookFetchCallback<T> = (ctx: WebhookFetchContext) => AsyncIterable<{
    record: T;
    eventId?: string;
    cursor?: WebhookCursor;
}>;
interface WebhookChangeSourceOptions<T> {
    /** Consumer-supplied inbound queue iterator. */
    readonly queue: WebhookFetchCallback<T>;
    /**
     * Parsed detection config. MUST be `mode: 'webhook'`; the constructor
     * throws if a poll config is supplied. Codegen-emitted factories call
     * `DetectionConfigSchema.parse(...)` upstream so this is a safety net,
     * not the primary validation point.
     */
    readonly config: DetectionConfig;
    /**
     * Optional middleware chain. Same shape and composition rules as
     * `PollChangeSource` — first element is the outermost layer.
     */
    readonly middlewares?: ReadonlyArray<ChangeMiddleware<T>>;
    /**
     * Optional human label for run logs (e.g. `'stripe-webhook-charge'`).
     * Defaults to a derived label based on the mapping at construction.
     */
    readonly label?: string;
}
declare class WebhookChangeSource<T> implements IChangeSource<T> {
    readonly label: string;
    private readonly queue;
    private readonly externalIdSourceField;
    /**
     * Record field carrying the event id, when `webhook.eventIdField` is
     * configured. Used only as the fallback when the queue iterator does NOT
     * yield an `eventId` — see {@link WebhookFetchCallback} for the precedence.
     */
    private readonly eventIdSourceField;
    private readonly composed;
    constructor(opts: WebhookChangeSourceOptions<T>);
    listChanges(subscription: IntegrationSubscriptionView, cursor: unknown | null): AsyncIterable<Change<T>>;
    private fetch;
    /**
     * Resolve `Change<T>.dedupKey` with the precedence: yielded `eventId` >
     * `webhook.eventIdField` record extraction > `undefined`. A non-empty
     * yielded `eventId` always wins; otherwise the configured field is read off
     * the record (and must be a non-empty string when the field is configured);
     * with neither, `dedupKey` is `undefined` (the orchestrator then has no
     * delivery-level dedup signal for this change).
     */
    private deriveDedupKey;
}

export { WebhookChangeSource, type WebhookChangeSourceOptions, type WebhookCursor, type WebhookFetchCallback, type WebhookFetchContext };
