import type { Guard, GuardJSON } from "../guard.js";
import { Checkpoint } from "../index.js";
import { MemoryFrame } from "../memory/frame.js";
import { ThreadStoreJSON } from "./threadStore.js";
export type BranchState = {
    stack: StateStack;
    interruptId?: string;
    interruptData?: any;
    checkpoint?: Checkpoint;
    result?: {
        result: any;
    };
    abortController?: AbortController;
    guardsRehydrated?: boolean;
};
export type BranchStateJSON = {
    stack: StateStackJSON;
    interruptId?: string;
    interruptData?: any;
    result?: {
        result: any;
    };
};
export declare class State {
    args: Record<string, any>;
    locals: Record<string, any>;
    threads: ThreadStoreJSON | null;
    step: number;
    private branches?;
    private deletedBranches?;
    scopedCallbacks?: Array<{
        name: string;
        fn: any;
    }>;
    constructor(opts?: {
        args?: Record<string, any>;
        locals?: Record<string, any>;
        threads?: ThreadStoreJSON | null;
        step?: number;
        branches?: Record<string, BranchState>;
    });
    /** Delete all entries in locals whose key starts with the given prefix.
     * Used by loops to reset nested tracking variables (condbranch, substep, iteration)
     * at the end of each iteration. */
    clearLocalsWithPrefix(prefix: string): void;
    /** Reset all loop tracking state for a given loop identified by its subKey.
     * Resets the substep counter to 0 and clears all nested condbranch, substep,
     * and iteration tracking variables. Used at the end of each loop iteration
     * and before break/continue statements. */
    resetLoopIteration(subKey: string): void;
    removeDebugFlags(): void;
    /** The sanctioned way to register a scoped callback on this frame. Initializes
     *  the array on first call so frames with no callbacks pay no overhead. */
    addScopedCallback(name: string, fn: any): void;
    newBranch(key: string): BranchState;
    getBranch(key: string): BranchState | undefined;
    getOrCreateBranch(key: string): BranchState;
    getBranchOrThrow(key: string): BranchState;
    deleteBranch(key: string): void;
    setResultOnBranch(key: string, result: any): void;
    setInterruptOnBranch(key: string, interruptId: string, interruptData: any, checkpoint?: Checkpoint): void;
    popBranches(): void;
    deserializeMode(): void;
    toJSON(): StateJSON;
    static fromJSON(json: StateJSON): State;
}
export type StateJSON = {
    args: Record<string, any>;
    locals: Record<string, any>;
    threads: ThreadStoreJSON | null;
    step: number;
    branches?: Record<string, BranchStateJSON>;
    scopedCallbacks?: Array<{
        name: string;
        fn: any;
    }>;
};
export type StateStackJSON = {
    stack: StateJSON[];
    mode: "serialize" | "deserialize";
    other: Record<string, any>;
    deserializeStackLength: number;
    nodesTraversed: string[];
    localCost?: number;
    localTokens?: number;
    seedCost?: number;
    seedTokens?: number;
    /** Branch-owned guards only. Inherited (parent-owned) guards are
     *  serialized on the parent's snapshot and re-prepended at resume by
     *  `runBatch`. See StateStack.guards. */
    guards?: GuardJSON[];
    /** Number of parent-owned guard references that were prepended onto
     *  this stack's `guards` at branch creation. NOT a count of entries
     *  in the serialized `guards` array (those are branch-owned only).
     *  Used by `StateStack.rehydrateInheritedGuardsFrom` on resume to
     *  validate that the parent's guard stack hasn't drifted. Always 0
     *  for the root stack. */
    inheritedGuardCount?: number;
};
export declare class StateStack {
    stack: State[];
    mode: "serialize" | "deserialize";
    other: Record<string, any>;
    deserializeStackLength: number;
    nodesTraversed: string[];
    interrupted: boolean;
    hasChildInterrupts: boolean;
    abortSignal?: AbortSignal;
    localCost: number;
    localTokens: number;
    seedCost: number;
    seedTokens: number;
    guards: Guard[];
    inheritedGuardCount: number;
    /** Lazy accessor for the frame array, creating it on first push.
     *  Created with sentinel-marking semantics: once the array exists
     *  (even if popped to empty), `hasMemoryFrameStack()` returns true,
     *  which distinguishes "user explicitly disabled memory" from
     *  "stack restored from a pre-memoryFrames checkpoint". */
    private memoryFramesArr;
    /** True iff the memory-frame stack has been touched on this
     *  StateStack (push or pop has run at least once). Returns false
     *  for stacks restored from pre-memoryFrames checkpoints, which is
     *  the only legitimate trigger for the JSON-config re-seed back
     *  in `getActiveMemoryManager`. */
    hasMemoryFrameStack(): boolean;
    /**
     * Push a memory frame onto this stack. Frames live in `other.memoryFrames`
     * so they serialize and fork-clone for free via the existing
     * `other` deepClone path. Callers MUST NOT touch the underlying
     * array directly — go through these methods.
     *
     * Same-`configKey` as the active frame is a no-op so the common
     * pattern `static const _ = enableMemory(...)` + an
     * `enableMemory(...)` in `main()` doesn't double-stack the same
     * frame. Different-dir pushes stack on top; pop with
     * `popMemoryFrame()` or use the `memory(){}` block for lexical
     * scoping.
     *
     * Returns `true` if the frame was actually pushed, `false` if
     * deduped. The block form (`memory(){}` in stdlib/memory.agency)
     * pairs this with `popMemoryFrame()` so a no-op push doesn't
     * unbalance the pop. ALWAYS materializes `other.memoryFrames` (even
     * on dedup) so `hasMemoryFrameStack()` flips true on first push —
     * checkpoints from this point on are recognised as "memory-aware".
     */
    pushMemoryFrame(frame: MemoryFrame): boolean;
    /** Pop the top memory frame, including the JSON-seeded bottom frame.
     *  Returns the popped frame or `undefined` if the stack is already
     *  empty.
     *
     *  When the last frame is popped the array stays present (but
     *  empty) so `activeMemoryFrame()` returns `undefined` and the
     *  old-checkpoint re-seed in `getActiveMemoryManager()` knows the
     *  user explicitly disabled memory rather than restored an
     *  ancient snapshot. */
    popMemoryFrame(): MemoryFrame | undefined;
    /** The active memory frame, or `undefined` if memory is currently off. */
    activeMemoryFrame(): MemoryFrame | undefined;
    pushGuard(guard: Guard): void;
    popGuard(): Guard | undefined;
    /**
     * Walk active guards innermost-first and throw the first
     * `GuardExceededError` returned by `guard.check(this)`. Used by
     * `prompt.ts` as both the pre-call gate (refuse to issue a request
     * we're already over budget for) and the post-call check (trip after
     * the response cost has been billed).
     *
     * Innermost-first means the most recently pushed guard reports its
     * trip first. This is a stable, scope-local rule rather than a
     * global-minimum search; a shallower outer guard with a tighter
     * budget would still trip on a later LLM call if the inner doesn't
     * fail first.
     */
    enforceGuards(): void;
    /**
     * Charge every active guard with this call's cost. Shared parent
     * guards (CostGuard.cloneForBranch returns `this`) accumulate
     * descendant spend in real time — this is what makes mid-fork trip
     * detection work without waiting for the fork to settle. TimeGuard's
     * charge is a no-op.
     */
    chargeGuards(amount: number): void;
    /**
     * Prepend the parent stack's inherited guard references onto this
     * (child) stack's `guards` array, and stamp `inheritedGuardCount` so
     * future serialization slices them off.
     *
     * Handles both fresh and resumed branches:
     *  - Fresh branch: `inheritedGuardCount` is 0; the recomputed count
     *    is stamped onto the stack.
     *  - Resumed branch: `inheritedGuardCount` was restored from JSON;
     *    the recomputed count is validated against it. A mismatch (e.g.
     *    parent pushed an extra guard between snapshot and resume) throws
     *    rather than silently inheriting a different set of guards.
     *
     * Always invokes `guard.cloneForBranch(parent, child)` on each parent
     * guard so per-guard semantics (CostGuard returns `this`; TimeGuard
     * returns `undefined`) drive what gets prepended. Slicing the parent
     * by `inheritedGuardCount` would lose this filter information.
     *
     * Caller (runBatch) owns the per-execution idempotency check via
     * `BranchState.guardsRehydrated` — this method itself is NOT idempotent;
     * calling it twice on the same stack will double-prepend.
     */
    rehydrateInheritedGuardsFrom(parentStack: StateStack): void;
    constructor(stack?: State[], mode?: "serialize" | "deserialize");
    getNewState(): State;
    deserializeMode(): void;
    pop(): State | undefined;
    lastFrame(): State;
    /** The frame one below the top. Top is the "current" frame for whatever code
     *  is running right now; the caller's frame is what owns scoped registrations
     *  made by the current call. Falls back to the root frame at the top level. */
    callerFrame(): State;
    /** True when the only frame on the stack is the currently-running call's own
     *  frame (i.e. there is no caller). This happens during module-level
     *  initialization (e.g. inside `__initializeGlobals`) where calls like
     *  `callback(...)` have no real caller frame to register against. */
    isGlobalContext(): boolean;
    /** All scoped callbacks registered anywhere in the active stack for this hook,
     *  ordered innermost first (deepest frame's callbacks come first). */
    collectScopedCallbacks(name: string): any[];
    static lastFrameJSON(json: StateStackJSON): StateJSON;
    currentNodeId(): string | undefined;
    /**
     * Advance the step or substep counter for a given step path so that on
     * interrupt resume we skip past the current debug step block.
     *
     * - stepPath "3"     → top-level step → increment stack.step
     * - stepPath "4.0"   → substep inside step 4 → set __substep_4 = 0 + 1
     * - stepPath "4.0.2" → sub-substep → set __substep_4.0 = 2 + 1
     *
     * The naming convention matches the builder's generated code:
     * all segments except the last form the substep variable name (__substep_X.Y),
     * and the value is set to lastSegment + 1 to advance past it.
     */
    advanceDebugStep(stepPath: string): void;
    toJSON(): StateStackJSON;
    private branchToJSON;
    static fromJSON(json: StateStackJSON): StateStack;
}
