/**
 * # adapters/broker/ibkr — the IB Gateway TWS-socket TRANSPORT (kestrel-7o2.5)
 *
 * The single, shared socket session to a **client-launched IB Gateway**. This is the ADAPTER at the
 * edge (AGENTS: determinism applies to the RUNTIME PATH; the transport produces facts the core
 * folds, so it may use sockets/timers) — but it must NEVER sit on the byte-identical replay path and
 * NEVER leak wall-clock/RNG into canonical state. Its job is exactly: connect, complete the API
 * handshake, track heartbeat/server-time, surface a typed status, and fail LOUD on any drop / auth
 * failure / heartbeat loss so STAND_DOWN is reachable.
 *
 * ## Transport ONLY — no orders (ADR-0034, owner-reviewed)
 * This module places **NO orders**. It writes no `placeOrder`/submit-to-TWS code. Order placement is
 * a later bead (7o2.8 paper / 7o2.10 live) and lands ONLY behind the explicit-arm + L0 risk-clamp +
 * kill-switch envelope (kestrel-7o2.9, already landed in `src/adapters/broker.ts`). The transport is
 * the shared connection those faces will multiplex over — it exposes the ONE guarded client so the
 * feed face and the (future) broker face are two faces of ONE socket, never a second connection.
 *
 * ## Paper-first mode gate
 * `paper` is a Kestrel-enforced gate (config `mode`), not merely a port fact. A `live`-mode transport
 * REFUSES to connect here (fail-closed) because this bead ships no live routing.
 *
 * ## Determinism at the edge
 * The IB client, the wall clock, and the heartbeat scheduler are all INJECTED
 * ({@link IbkrTransportDeps}) so tests drive a fake client + a manual clock + manual timers with no
 * real sockets or timers, and so nothing here reads an ambient `Date.now()` implicitly. Production
 * wires the real {@link IBApi}, `Date.now`, and `globalThis.setInterval`.
 */
import { EventName } from "@stoqey/ib";
import type { IbkrConfig, IbkrConfigInput } from "./config.ts";
import type { IbkrFailure } from "./errors.ts";
/** A listener over the IB event bus. The transport registers narrowly-typed wrappers, so the
 * `unknown[]` here never escapes into a handler body — each `on` call passes a concrete closure. */
type IbListener = (...args: never[]) => void;
/**
 * The NARROW structural surface of {@link IBApi} the transport uses — the shared socket session's
 * request/callback multiplexer. Deliberately excludes every order-placement method (`placeOrder`,
 * `cancelOrder`, …): this bead cannot place orders even by reaching through the client. A test
 * injects a fake that implements exactly this surface (an `EventEmitter` double); production wires
 * the real `IBApi` (a structural superset).
 */
export interface IbClient {
    /** Whether the socket is currently connected (the client's own view). */
    readonly isConnected: boolean;
    /** Open the socket + begin the API handshake. */
    connect(clientId?: number): unknown;
    /** Close the socket. */
    disconnect(): unknown;
    /** Solicit the server's current time (`currentTime` event) — the transport's heartbeat probe. */
    reqCurrentTime(): unknown;
    /** Request the managed account id list (`managedAccounts` event) — part of the handshake. */
    reqManagedAccts(): unknown;
    /** Subscribe to an IB event. */
    on(event: EventName, listener: IbListener): unknown;
    /** Unsubscribe a single listener. */
    removeListener?(event: EventName, listener: IbListener): unknown;
    /** Drop all listeners (used on teardown to release the fake/real emitter). */
    removeAllListeners(event?: EventName): unknown;
}
/** A monotone-enough wall clock in epoch ms — injected so tests advance it by hand and production
 * wires `Date.now`. NEVER read from canonical state; only the edge adapter consults it. */
export type NowFn = () => number;
/** An opaque timer handle from {@link Scheduler.setInterval}. */
export type TimerHandle = unknown;
/** The heartbeat scheduler — injected so tests fire ticks manually (no real timers) and production
 * wires `globalThis.setInterval`/`clearInterval`. */
export interface Scheduler {
    setInterval(fn: () => void, ms: number): TimerHandle;
    clearInterval(handle: TimerHandle): void;
}
/** Construction deps for {@link IbkrTransport}. Every non-determinism source is injected. */
export interface IbkrTransportDeps {
    /** Build the shared IB client for a resolved config. Defaults to a real {@link IBApi}. */
    readonly makeClient?: (config: IbkrConfig) => IbClient;
    /** Wall clock (epoch ms). Defaults to `Date.now`. */
    readonly now?: NowFn;
    /** Heartbeat scheduler. Defaults to `globalThis.setInterval`/`clearInterval`. */
    readonly scheduler?: Scheduler;
    /** Heartbeat probe cadence in ms (how often the watchdog solicits + checks server time). */
    readonly heartbeatIntervalMs?: number;
    /** How stale (ms since last server-time reply) before the heartbeat is declared LOST → degraded. */
    readonly heartbeatStalenessMs?: number;
    /** Optional redacted-diagnostic sink (defaults to no-op — the transport never logs a secret; when
     * wired it receives only already-redacted strings). */
    readonly log?: (line: string) => void;
}
/**
 * The typed status of the shared broker transport (kestrel-7o2.5). A single observable snapshot: is
 * the socket connected, is it degraded (and why), which account did the handshake report, and when
 * did the last heartbeat land. `degraded` + `reason` are how a drop / auth failure / heartbeat loss
 * becomes reachable as STAND_DOWN. The `account` is REDACTED here — the full id never leaves the
 * transport's memory.
 */
export interface BrokerStatus {
    /** True once the API handshake completed and the socket is live and not degraded. */
    readonly connected: boolean;
    /** True once a drop / auth failure / heartbeat loss has occurred; latches until reconnect. */
    readonly degraded: boolean;
    /** The redacted account id the gateway's `managedAccounts` reported (never the full number). */
    readonly account: string;
    /** Server epoch-ms of the last heartbeat reply, or `undefined` before the first. */
    readonly lastHeartbeat: number | undefined;
    /** The server time (epoch ms) from the most recent `currentTime` reply, or `undefined`. */
    readonly serverTime: number | undefined;
    /** The logged reason for degradation, or `null` while healthy. NEVER carries a secret. */
    readonly reason: string | null;
}
/**
 * The IB Gateway TWS-socket transport (kestrel-7o2.5). ONE shared session; the feed + (future)
 * broker faces multiplex over its single {@link client}. Fail-closed throughout: any drop / auth
 * failure / heartbeat loss flips {@link status} to `degraded` and throws a typed
 * {@link IbkrConnectionError} on use. Places NO orders.
 */
export declare class IbkrTransport {
    #private;
    readonly config: IbkrConfig;
    constructor(config: IbkrConfig, deps?: IbkrTransportDeps);
    /** Build a transport straight from env/override — resolves the config, then constructs. */
    static resolve(input?: IbkrConfigInput, deps?: IbkrTransportDeps): IbkrTransport;
    /** The typed transport status (kestrel-7o2.5) — the observable snapshot the session/broker faces
     * poll. The account is redacted. `degraded` + `reason` are the STAND_DOWN signal. */
    status(): BrokerStatus;
    /** True once the handshake completed and the session is healthy (not degraded/closed). */
    get connected(): boolean;
    /**
     * The ONE shared IB client the feed + (future) broker faces multiplex over — guarded. Throws a
     * typed {@link IbkrConnectionError} if the session is not connected or has gone degraded, so a
     * caller can never use a dead socket (fail-closed; STAND_DOWN reachable). Returning the same client
     * to every face guarantees a SINGLE connection — never a second socket. Note: the narrow
     * {@link IbClient} surface has NO order-placement method, so this cannot place an order.
     */
    client(): IbClient;
    /** Throw the typed connection error unless the session is connected and healthy. The reconnect
     * verb for a caller that catches this is STAND_DOWN, then a fresh {@link connect}. */
    assertReady(): void;
    /**
     * Connect to the client-launched IB Gateway and complete the API handshake (kestrel-7o2.5): open
     * the socket, then resolve only once BOTH `nextValidId`/`connected` AND `managedAccounts` have
     * landed and the first `currentTime` heartbeat has replied. Rejects with a typed
     * {@link IbkrConnectionError} on a fatal IB error (auth/connect failure), a disconnect, or a
     * timeout — never hangs silently. On success, starts the heartbeat watchdog. Places NO orders.
     *
     * @param timeoutMs handshake deadline; a lapse rejects `connect-failed` (fail-closed).
     */
    connect(timeoutMs?: number): Promise<BrokerStatus>;
    /** Record a fresh server-time heartbeat. IB `currentTime` is epoch SECONDS; we hold epoch-ms and
     * stamp the local staleness anchor with the injected clock (never an ambient `Date.now`). */
    private recordHeartbeat;
    /** Start the heartbeat watchdog: each tick solicits a fresh `currentTime` and, if the last reply
     * is older than the staleness bound, declares the heartbeat LOST → degraded (fail-closed). Uses the
     * injected scheduler so tests fire ticks by hand. */
    private startHeartbeat;
    private stopHeartbeat;
    /**
     * One heartbeat watchdog step (exposed for the owner smoke + deterministic tests): solicit a fresh
     * server time, then check staleness against the injected clock. If the socket dropped, or no reply
     * has landed within the staleness bound, flip to `degraded` with a typed reason. A no-op once
     * degraded/closed.
     */
    pulse(): void;
    /** Flip the session to degraded with a typed, secret-free reason (kestrel-7o2.5). Idempotent —
     * the FIRST failure/reason/cause wins (a latch), so a cascade of drops does not overwrite the root
     * cause. The failure KIND is RECORDED, not discarded: a later {@link assertReady}/{@link client}
     * rethrows THIS failure, so a hung-up socket reports `disconnected` and only a genuinely missed
     * heartbeat reports `heartbeat-lost`. Stops the heartbeat; the shared client stays referenced only
     * so `status()` stays truthful. STAND_DOWN is reachable: every subsequent use throws. */
    degrade(failure: IbkrFailure, reason: string, options?: {
        cause?: unknown;
        code?: number | undefined;
    }): void;
    /** Cleanly close the shared session (kestrel-7o2.5): stop the heartbeat, drop every listener, and
     * disconnect the socket. Fail-closed and idempotent — safe to call from a catch/STAND_DOWN path. */
    disconnect(): void;
    /**
     * Tear the CURRENT `#client` down completely and forget it: every listener off, then the socket
     * closed. The single socket-teardown truth, shared by {@link disconnect} and by {@link connect}'s
     * zombie-kill — so the reconnect path and the shutdown path cannot drift (the drift is what
     * kestrel-7o2.17 was: `disconnect()` closed the socket, `connect()` only detached listeners, and the
     * leaked socket kept the clientId IB would then refuse to grant twice).
     *
     * Order matters: listeners come off BEFORE `disconnect()`, so the socket's own death rattle
     * (`disconnected`/`connectionClosed`) cannot reach a handler and degrade the session we are closing
     * on purpose — or, on the reconnect path, the fresh one about to replace it.
     *
     * Idempotent and fail-closed: a disconnect on an already-dead socket is a no-op, never a crash.
     * Clearing `#client` is safe for {@link status} truthfulness — status reads only `#phase`/`#reason`/
     * the account/heartbeat fields, never the client — while `pulse`/`client` are already gated on phase.
     */
    private closeClient;
    /**
     * Remove EVERY listener this transport registered on the CURRENT `#client` and empty the ledger.
     * The listener half of {@link closeClient}'s teardown (which both {@link disconnect} and
     * {@link connect}'s zombie-kill route through): it is a per-listener removal, never the blanket
     * `removeAllListeners()` hammer, so it stays honest on a client the transport does not exclusively
     * own. Idempotent (the ledger empties).
     */
    private detachListeners;
    /** Register a narrowly-typed listener and remember it for teardown. The `as` casts bridge the
     * concrete closure to the client's broad `IbListener` surface — the args are the exact IB event
     * shapes documented on `IBApi.on`. */
    private register;
}
export {};
//# sourceMappingURL=transport.d.ts.map