import { DrizzleTransaction } from './runtime/subsystems/events/event-bus.protocol.js';
import { JobRunRow } from './runtime/subsystems/jobs/job-orchestration.schema.js';
import { Logger } from '@nestjs/common';
import { EventTypeName, EventOfType } from './runtime/subsystems/events/event-registry.js';

/**
 * Handler base class, JobContext, @JobHandler decorator, and policy types
 * for the job orchestration domain (ADR-022, JOB-2).
 *
 * User-authored jobs subclass `JobHandlerBase<TInput, TOutput>` and decorate
 * the class with `@JobHandler<TInput>('job_type', meta)`. The decorator
 *   1. stores metadata via `Reflect.defineMetadata` so Nest's reflector can
 *      pick it up at module boot, and
 *   2. populates `JOB_HANDLER_REGISTRY` — a module-singleton map consumed by
 *      `JobWorkerModule` (JOB-5) to materialise `job` rows and resolve
 *      handler classes during claim/execute.
 *
 * No runtime orchestration lives here; this file is a pure type + decorator
 * surface so downstream PRs (JOB-3..JOB-5) can implement against a stable
 * shape.
 */

/**
 * What happens to running child runs when a parent enters a terminal state.
 * Stored on the child at spawn; changes to the parent after spawn do NOT
 * retroactively rewrite children.
 */
declare enum ParentClosePolicy {
    Terminate = "terminate",
    Cancel = "cancel",
    Abandon = "abandon"
}
interface RetryPolicy {
    attempts: number;
    backoff: 'fixed' | 'exponential';
    baseMs: number;
    nonRetryableErrors?: string[];
}
/**
 * Concurrency lane key (JOB-FN-KEY, 0.16.2).
 *
 * Two authoring forms, both honored end-to-end (the typed function form was
 * previously dropped to `null` at registration — see `upsertJobRows` — so
 * `collisionMode` silently never engaged):
 *
 *   - **`string`** — a `{{field}}` template evaluated against the start
 *     payload by `evaluateKeyTemplate` (single-key substitution, no dotted
 *     paths). Persisted verbatim to `job.concurrency_key_template`.
 *   - **`(input) => string`** — an arbitrary function of the input. Persisted
 *     as the `FN_KEY_SENTINEL` marker so the definition-hash gate stays stable
 *     and the collision path engages; `start()` re-resolves the live function
 *     from `JOB_HANDLER_REGISTRY` and evaluates it against the payload.
 *
 * Both forms produce a per-lane key; same key + in-flight incumbent ⇒
 * `collisionMode` ('queue' | 'reject' | 'replace') decides.
 */
type JobKeySelector<TInput> = string | ((input: TInput) => string);
interface ConcurrencyPolicy<TInput> {
    key: JobKeySelector<TInput>;
    collisionMode: 'queue' | 'reject' | 'replace';
}
interface DedupePolicy<TInput> {
    key: JobKeySelector<TInput>;
    windowMs: number;
}
/**
 * Declarative scope reference. `TScope` is parameterised so JOB-7 can narrow
 * `entity` to the generated `ScopeEntityType` union at the call site without
 * modifying this file (OQ-1 resolution, 2026-04-20).
 */
interface ScopeRef<TInput, TScope extends string = string> {
    entity: TScope;
    from: (input: TInput) => string;
}
/**
 * Bridge trigger authoring shape (BRIDGE-6 follow-up — BRIDGE-6 shipped the
 * generator + runtime for `@JobHandler({ triggers })` but never added the
 * authoring field to this type; the generator's tests scan source as strings,
 * so a real decorator was never compiled and the gap went uncaught).
 *
 * Declared on `@JobHandler({ triggers })`; the codegen bridge-registry
 * generator (`src/cli/shared/bridge-registry-generator.ts`) scans these from
 * source and emits `bridge/generated/registry.ts`, validating each `event`
 * against the generated `eventRegistry` at `gen-all`. The distributed union
 * narrows `map`/`when` per `event`, so callbacks are typed against the event
 * payload (ADR-023, "typed against PayloadOfType<T>").
 *
 * Typed against events' generated types — the same `import type` coupling the
 * bridge already has (erased at runtime). `jobs` must NOT import `bridge`, so
 * the post-gen `BridgeTriggerEntry` is deliberately not referenced here;
 * `triggerId`/`jobType` are computed by the generator, not authored.
 */
type JobTrigger<TInput> = {
    [T in EventTypeName]: {
        /** Event type that fires this trigger. Validated against `eventRegistry`. */
        event: T;
        /** Maps the event to the job input. Inlined verbatim into the registry. */
        map: (event: EventOfType<T>) => TInput;
        /** Optional guard; `false` → wrapper records `status='skipped'`. */
        when?: (event: EventOfType<T>) => boolean;
    };
}[EventTypeName];
interface JobHandlerMeta<TInput> {
    pool?: string;
    scope?: ScopeRef<TInput>;
    retry?: RetryPolicy;
    concurrency?: ConcurrencyPolicy<TInput>;
    dedupe?: DedupePolicy<TInput>;
    timeoutMs?: number;
    replayFrom?: 'scratch' | 'last_step' | 'last_checkpoint';
    /**
     * Bridge triggers (ADR-023 Tier 3). Codegen scans these into `bridgeRegistry`;
     * the framework `BridgeDeliveryHandler` starts this job per matched event.
     * Absent for jobs started directly or via `IEventFlow.publishAndStart`.
     */
    triggers?: readonly JobTrigger<TInput>[];
}
interface StepOptions {
    retry?: RetryPolicy;
    timeoutMs?: number;
}
interface SpawnChildOptions {
    closePolicy?: ParentClosePolicy;
    runAt?: Date;
    priority?: number;
    tags?: Record<string, string>;
}
interface JobContext<TInput> {
    readonly input: TInput;
    readonly run: JobRun;
    step<TOutput>(stepId: string, fn: () => Promise<TOutput>, opts?: StepOptions): Promise<TOutput>;
    spawnChild(type: string, input: unknown, opts?: SpawnChildOptions): Promise<JobRun>;
    readonly logger: Logger;
}
declare abstract class JobHandlerBase<TInput, TOutput = unknown> {
    abstract run(ctx: JobContext<TInput>): Promise<TOutput>;
}
/**
 * Module-singleton map keyed by job type. Populated by the `@JobHandler`
 * decorator at class definition time; consumed by `JobWorkerModule` (JOB-5)
 * to upsert `job` rows and resolve handler classes during claim/execute.
 */
declare const JOB_HANDLER_REGISTRY: Map<string, {
    type: string;
    meta: JobHandlerMeta<unknown>;
    handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;
}>;
declare const JOB_HANDLER_METADATA_KEY: unique symbol;
/**
 * Class decorator that registers a handler with the job type, the full
 * metadata shape, and the target class constructor.
 *
 * Duplicate-type behaviour (OQ-3, resolved 2026-04-18):
 *   - `NODE_ENV === 'production'` → throw; silent overwrite in prod is a
 *     correctness bug.
 *   - `NODE_ENV === 'test'`       → silent overwrite (tests intentionally
 *     re-register handlers).
 *   - otherwise (dev)             → `console.warn` + overwrite. `console`
 *     is used intentionally instead of the Nest `Logger` — decorators run
 *     at module-load time before any Nest container exists.
 */
declare function JobHandler<TInput>(type: string, meta: JobHandlerMeta<TInput>): ClassDecorator;
/**
 * Single entry shape returned by `HandlerRegistry.getAll()` / `.get()` and
 * exposed to `JobWorkerModule.onModuleInit` for boot-time upserts.
 *
 * Structurally compatible with `IJobOrchestrator.upsertJobRows`'s
 * `JobUpsertEntry` so the worker module can pass entries through verbatim
 * without re-mapping.
 */
interface HandlerRegistryEntry {
    type: string;
    meta: JobHandlerMeta<unknown>;
    handlerClass: new (...args: unknown[]) => JobHandlerBase<unknown>;
}
/**
 * Read facade over `JOB_HANDLER_REGISTRY`. The decorator's write path is
 * unchanged; this namespace exists so consumers (the worker module, tests)
 * don't import the raw `Map` and accidentally mutate it.
 */
declare namespace HandlerRegistry {
    /** All registered entries in insertion order. */
    function getAll(): HandlerRegistryEntry[];
    /** Lookup by job type, or `undefined` if no `@JobHandler` is registered. */
    function get(type: string): HandlerRegistryEntry | undefined;
}
/**
 * Sentinel persisted to `job.concurrency_key_template` / `dedupe_key_template`
 * when the authored `key` is a function rather than a `{{field}}` template.
 *
 * Why a sentinel (not `null`): the collision/dedupe paths in both backends gate
 * on `definition.concurrencyKeyTemplate != null`. A function key persisted as
 * `null` (the pre-0.16.2 bug) left those columns empty, so `collisionMode` /
 * the dedupe window never engaged — the job ran with NO key. A stable sentinel
 * keeps the column non-null (path engages) AND keeps the definition-hash gate
 * (`upsertJobRows`' `IS DISTINCT FROM` clause) stable across boots, since the
 * function identity itself can't be hashed. `start()` detects the sentinel and
 * re-resolves the live function from `JOB_HANDLER_REGISTRY`.
 *
 * Chosen as an angle-bracketed token so it can never collide with a real
 * `{{field}}` template (which never contains a literal `<`).
 */
declare const FN_KEY_SENTINEL = "<fn>";
/**
 * Registration-time projection: collapse an authored `JobKeySelector` to the
 * string stored in the `job` definition row. A string template is stored
 * verbatim; a function is stored as `FN_KEY_SENTINEL`; absence stays `null`.
 */
declare function keySelectorToTemplate(key: JobKeySelector<unknown> | undefined): string | null;
/** Which meta policy a key belongs to — selects the live fn at `start()`. */
type KeyKind = 'concurrency' | 'dedupe';
/**
 * `start()`-time resolution shared by every backend. Turns the persisted
 * template column into the concrete per-run key for the given payload.
 *
 *   - `template == null` → `null` (no key; caller skips the collision/dedupe path).
 *   - `template === FN_KEY_SENTINEL` → look the live `@JobHandler` meta up in
 *     `JOB_HANDLER_REGISTRY`, pull `meta[kind].key`, and invoke it against the
 *     payload. The registry is the runtime source of truth (the worker already
 *     resolves handler classes the same way), so the function survives the DB
 *     round-trip even though it can't be persisted.
 *   - otherwise → a `{{field}}` template, evaluated via the injected
 *     `evaluateTemplate` (each backend passes its own copy to avoid a runtime
 *     import cycle).
 *
 * Throws `JobKeyFunctionUnavailableError` if the sentinel is present but no
 * live function can be found (e.g. the registry was reset, or a function key
 * was persisted by a newer build and read by an older one). Failing loud beats
 * silently degrading to no-key — the exact regression this fix exists to kill.
 */
declare function resolveJobKey(kind: KeyKind, type: string, template: string | null, payload: Record<string, unknown>, evaluateTemplate: (template: string, payload: Record<string, unknown>) => string): string | null;
/**
 * Raised when a `${FN_KEY_SENTINEL}` template is read but the live function
 * key is missing from `JOB_HANDLER_REGISTRY`. Kept here (not in `jobs-errors`)
 * so `job-handler.base` stays import-cycle-free.
 */
declare class JobKeyFunctionUnavailableError extends Error {
    readonly jobType: string;
    readonly kind: KeyKind;
    constructor(jobType: string, kind: KeyKind);
}

/**
 * Public return type for orchestrator reads. Re-exported as `JobRun` so
 * protocols and consumer code don't import the raw Drizzle row name.
 */
type JobRun = JobRunRow;
interface StartOptions {
    /**
     * Optional scope attachment. `listForScope` queries use this pair; the
     * column is free-text (no CHECK constraint) — type safety for `entityType`
     * lives at the TS layer via JOB-7's generated `ScopeEntityType` union.
     */
    scope?: {
        entityType: string;
        entityId: string;
    };
    /** Overrides the pool declared in `@JobHandler({ pool })` metadata. */
    pool?: string;
    /** Schedule the run. When omitted, run as soon as the worker claims it. */
    runAt?: Date;
    /** 0 = default; higher values claimed first by `ORDER BY priority DESC`. */
    priority?: number;
    /** Free-form routing/audit tags. Persisted on `job_run.tags`. */
    tags?: Record<string, string>;
    /** Must align with `triggerSourceEnum` values landed in JOB-1. */
    triggerSource?: 'manual' | 'schedule' | 'event' | 'parent';
    /** Optional reference to the triggering event, schedule, etc. */
    triggerRef?: string;
    /**
     * What happens to this run's children if this run reaches a terminal
     * state. Stored on the child at spawn time; see `ParentClosePolicy`.
     */
    parentClosePolicy?: ParentClosePolicy;
    /** Internal — set by `ctx.spawnChild`. User code should not pass this. */
    parentRunId?: string;
    /**
     * Multi-tenancy opt-in (JOB-8). When `JobsDomainModule` is configured
     * with `multiTenant: true`, this field is required:
     *   - `string` — tenant the run belongs to (written to `job_run.tenant_id`).
     *   - `null`   — cross-tenant background work; row persisted with NULL.
     *   - `undefined` — throws `MissingTenantIdError` at the backend.
     * When `multiTenant: false`, the field is ignored and the column is
     * always written as `NULL`.
     */
    tenantId?: string | null;
}
interface CancelOptions {
    /**
     * Conceptually defaults to `true` for root cancellation — cascading via
     * `root_run_id` is the expected behaviour when an operator cancels a
     * run. Backends in JOB-3/JOB-4 implement the default; callers passing
     * `false` opt into "cancel only this node, leave descendants".
     */
    cascade?: boolean;
    reason?: string;
    /**
     * Multi-tenancy gate (JOB-8). When `multiTenant: true`, the backend
     * additionally filters `WHERE tenant_id = :tenantId` — cancelling a run
     * that belongs to a different tenant is a **no-op** (not an error), so
     * cross-tenant cancellation attempts are silent rather than leaking
     * existence information. `undefined` throws `MissingTenantIdError`;
     * explicit `null` matches `tenant_id IS NULL` rows.
     */
    tenantId?: string | null;
}
/**
 * Boot-time upsert payload — one entry per registered `@JobHandler` class.
 * Constructed by `JobWorkerModule.onModuleInit` from `HandlerRegistry.getAll()`
 * and handed to the orchestrator so each backend can persist `job` definitions
 * in whatever way it stores them (Drizzle: `ON CONFLICT (type) DO UPDATE`
 * gated by metadata content; memory: populate `MemoryJobStore.jobs`).
 */
interface JobUpsertEntry {
    type: string;
    meta: JobHandlerMeta<unknown>;
    /**
     * Handler class constructor — the memory backend keeps a reference for
     * `tick()` execution. Drizzle backend ignores this (worker resolves the
     * class via `JOB_HANDLER_REGISTRY` at claim time).
     */
    handlerClass: new (...args: unknown[]) => unknown;
}
/**
 * Pool definition surface as the orchestrator needs it for boot-time row
 * materialisation. Defined locally here (not imported from
 * `pool-config.loader.ts`) so the protocol layer keeps zero dependencies on
 * runtime config wiring — the loader's `PoolDefinition` is structurally
 * compatible.
 */
interface JobPoolDef {
    queue: string;
    concurrency: number;
    reserved: boolean;
    description?: string;
}
interface IJobOrchestrator {
    /**
     * Create a `pending` `job_run` row and return it. Does NOT block waiting
     * for the worker to pick the run up; consumers that need completion
     * semantics should subscribe to the emitted completion event.
     *
     * **Optional `tx` last-arg** (added 2026-04-22 for ADR-023 BRIDGE-7):
     * pass an in-flight Drizzle transaction to thread the row insert + any
     * dedupe/collision lookups onto an existing tx. Used by
     * `EventFlowService.publishAndStart` to bundle the outbox insert,
     * eager `job_run`, and `bridge_delivery` Case-B pre-write into one
     * atomic transaction. Memory backend ignores the parameter (its
     * "transaction" is a process-wide mutex). Drizzle backend uses the
     * standard `tx ?? this.db` pattern.
     */
    start(type: string, input: unknown, opts?: StartOptions, tx?: DrizzleTransaction): Promise<JobRun>;
    /**
     * Cancel a run (and, by default, its entire root-run subtree). Idempotent
     * — cancelling an already-terminal run is a no-op.
     */
    cancel(runId: string, opts?: CancelOptions): Promise<void>;
    /**
     * Re-run from the policy declared in `@JobHandler({ replayFrom })`.
     * Returns the new `job_run` row (replay always spawns a fresh row —
     * the original is preserved for audit).
     */
    replay(runId: string): Promise<JobRun>;
    /**
     * Boot-time materialisation of `job` definitions from `@JobHandler`
     * metadata. Called once per process by `JobWorkerModule.onModuleInit`.
     *
     * Drizzle backend: hash-gated `INSERT … ON CONFLICT (type) DO UPDATE …
     * WHERE` (Q3 resolution 2026-04-19). The `UPDATE` branch executes only
     * when one of the persisted metadata fields differs from the incoming
     * payload; `version` bumps only on a real change; concurrent boots with
     * identical content are idempotent no-ops.
     *
     * Memory backend: populates `MemoryJobStore.jobs` and the in-process
     * handler-class registry consumed by `MemoryJobOrchestrator.tick`.
     *
     * Returns the orphaned types — types present in DB but absent from
     * `entries`. The caller (boot validator) decides whether to throw or
     * warn. Memory backend always returns `[]` (Q4 resolution 2026-04-19 —
     * validator skipped in memory mode).
     */
    upsertJobRows(entries: JobUpsertEntry[], poolConfig: ReadonlyMap<string, JobPoolDef>): Promise<{
        orphaned: string[];
    }>;
}

export { type CancelOptions as C, type DedupePolicy as D, FN_KEY_SENTINEL as F, HandlerRegistry as H, type IJobOrchestrator as I, JOB_HANDLER_METADATA_KEY as J, type KeyKind as K, ParentClosePolicy as P, type RetryPolicy as R, type ScopeRef as S, type ConcurrencyPolicy as a, type HandlerRegistryEntry as b, JOB_HANDLER_REGISTRY as c, type JobContext as d, JobHandler as e, JobHandlerBase as f, type JobHandlerMeta as g, type JobPoolDef as h, type JobRun as i, type JobUpsertEntry as j, type SpawnChildOptions as k, type StartOptions as l, type StepOptions as m, JobKeyFunctionUnavailableError as n, type JobKeySelector as o, type JobTrigger as p, keySelectorToTemplate as q, resolveJobKey as r };
