/**
 * The connection state machine, expressed as a declarative RULE TABLE.
 *
 * Driving an OAuth-style link from "nothing yet" to "active" is a small loop with
 * a handful of terminal and non-terminal states. The naive way to write it is an
 * `if`-cascade inside the poll loop — "if active return; else if failed throw;
 * else if expired re-initiate; else wait and poll again" — which buries the policy
 * inside control flow and is easy to get subtly wrong. We pull that policy out into
 * {@link CONNECT_RULES}: one row per normalized {@link ConnectionStatus}, each row
 * naming the {@link ConnectAction} the driver should take next. The poll loop then
 * does nothing but *consult the table* and obey.
 *
 * {@link planConnect} is the pure function at the heart of it: state in, action
 * out, no I/O. {@link initiateAndAwait} is the one impure driver that opens a
 * connection through the {@link SaasBackend} port and polls it to a terminal
 * outcome, asking {@link planConnect} at every step what to do. Because the policy
 * is data, a test can assert the whole decision matrix without touching a backend.
 */
import type { ConnectionState, ConnectionStatus, InitiateOptions, SaasBackend } from "../core/index";
/**
 * What the driver should do once it has observed a connection's current state.
 *
 * - `await-auth` — the user still has to act (visit `authUrl`); keep polling.
 * - `poll` — the link is progressing on the backend; check again shortly.
 * - `done` — the link is `active`; the carried `accountId` is ready to use.
 * - `expired` — the attempt lapsed; the caller may re-initiate from scratch.
 * - `failed` — the attempt terminally failed; surface the reason and stop.
 */
export type ConnectAction = {
    readonly kind: "await-auth";
    readonly authUrl?: string;
} | {
    readonly kind: "poll";
} | {
    readonly kind: "done";
    readonly accountId?: string;
} | {
    readonly kind: "expired";
} | {
    readonly kind: "failed";
    readonly reason: string;
};
/** Whether an action is one the driver stops on rather than loops past. */
export declare function isTerminal(action: ConnectAction): boolean;
/**
 * One row of the connection decision matrix: a normalized status mapped to the
 * action the driver should take when it observes that status. Keeping `decide` as
 * a thunk over the live {@link ConnectionState} lets a row read the state's
 * `accountId` / `authUrl` without the table itself needing to branch.
 */
interface ConnectRule {
    /** The normalized status this row governs. */
    readonly when: ConnectionStatus;
    /** Produce the next action from the observed state. */
    decide(state: ConnectionState): ConnectAction;
}
/**
 * The connection rule table — the whole policy as data.
 *
 * `active` → done (carrying the linked account); `pending` → either wait for the
 * user when an `authUrl` is outstanding, or poll the backend otherwise; `expired`
 * and `failed` are the two terminal failure shapes. There is exactly one row per
 * member of {@link ConnectionStatus}, so the matrix is total by construction.
 */
export declare const CONNECT_RULES: readonly ConnectRule[];
/**
 * Decide the next {@link ConnectAction} for an observed {@link ConnectionState}.
 *
 * Pure: it consults {@link CONNECT_RULES} and applies the matching row. Because
 * the table is total over {@link ConnectionStatus}, the only way to miss is an
 * out-of-vocabulary status, which we treat as a terminal failure rather than a
 * silent no-op.
 */
export declare function planConnect(state: ConnectionState): ConnectAction;
/** How the {@link initiateAndAwait} driver paces and bounds its polling. */
export interface AwaitOptions extends InitiateOptions {
    /** Milliseconds between polls of a still-pending connection. Default 1500. */
    readonly pollIntervalMs?: number;
    /** Maximum number of polls before giving up with a timeout. Default 40. */
    readonly maxPolls?: number;
    /** Injectable delay, so tests can drive the loop without real time passing. */
    readonly sleep?: (ms: number) => Promise<void>;
}
/**
 * The terminal outcome of driving a connection attempt to completion.
 *
 * `action` is always one for which {@link isTerminal} holds. `requestId` and the
 * final `authUrl` (if the user still needed to act) are carried for surfacing.
 */
export interface ConnectOutcome {
    /** The connection request that was driven. */
    readonly requestId: string;
    /** The toolkit the request targeted. */
    readonly toolkit: string;
    /** The terminal action the state machine settled on. */
    readonly action: ConnectAction;
    /** The most recent authorization URL observed, if any. */
    readonly authUrl?: string;
}
/**
 * Open a connection to `toolkit` and poll it to a terminal state via the backend.
 *
 * The driver is deliberately thin: it initiates once, then loops — observe state,
 * ask {@link planConnect}, and either stop (terminal action) or wait-and-poll
 * (`poll` / `await-auth`). Polling is bounded by `maxPolls`; exhausting the budget
 * yields a `failed` outcome rather than hanging. All policy lives in the rule
 * table; this function only sequences I/O around it.
 *
 * Note it does *not* re-initiate on `expired` — that decision belongs to the
 * caller, who may want to surface the lapse to the user first; the outcome simply
 * reports `expired` so the caller can choose.
 */
export declare function initiateAndAwait(backend: SaasBackend, toolkit: string, options?: AwaitOptions): Promise<ConnectOutcome>;
export {};
