import { type Caller } from '../context/session_context.ts';
import type { OpRecord } from '../observe/record.ts';
import { FileChangeKind, type FileStat, PathSpec } from '../types.ts';
import { type FileCacheMixin } from './file/mixin.ts';
import type { Evicted } from './index/config.ts';
import type { IndexCacheStore } from './index/store.ts';
import { type Measured } from './types.ts';
import { VersionScope } from './versions.ts';
/**
 * Post-mutation cache coherence for one mount.
 *
 * A backend mutation has two cache consequences: the file-cache entry
 * for the path is stale, and the parent directory listing in the index
 * cache (including negative knowledge that the path does not exist) is
 * stale. This class discharges both, synchronously, at the mutation
 * site: core backend mutators report through `cache/context.ts` so
 * invalidation happens before the next command in a pipeline runs
 * instead of after the whole command tree.
 */
export declare class CacheManager {
    private readonly readTtl;
    private readonly onGone?;
    private readonly mayServeListing?;
    private readonly excludedPrefixes;
    private readonly conditional;
    readonly versionScope: VersionScope;
    private readonly fileCache;
    private readonly index;
    private readonly clock;
    private readonly prefix;
    private readonly cachesReads;
    private readonly ownsPath;
    private readGeneration;
    private view;
    private readonly written;
    private readonly probed;
    private probeBound;
    private readonly checked;
    private readonly refused;
    private readonly checking;
    private checkBound;
    constructor(fileCache: FileCacheMixin | null, index: IndexCacheStore | null, prefix: string, cachesReads: boolean, ownsPath?: (path: string) => boolean, readTtl?: number, onGone?: ((gone: readonly Evicted[]) => Promise<void>) | undefined, mayServeListing?: ((folder: string, version: string | null) => Promise<boolean>) | undefined, excludedPrefixes?: () => readonly string[], conditional?: boolean);
    private millis;
    /** Drain raw backend index access before mount cache eviction. */
    mutation<T>(call: () => Promise<T>): Promise<T>;
    /**
     * Drop this mount's listings and bodies, path unspecified.
     *
     * For a write that went past the dispatcher, so nothing here can say which
     * file changed, only that this mount may no longer match its backend:
     * native code (an external program, a remote runtime line), an account CLI
     * writing to its service by id, or a mount command writing through its own
     * client. Both caches go, because the two hide different writes: a stale
     * listing hides a create or a delete, a stale body an edit. It also retires
     * what the running command's probes saw.
     *
     * The index is expired rather than cleared, while this mount still owns it:
     * a cleared index reads exactly like one that was never filled, so a
     * backend whose index *is* its listing (github seeds the whole tree once)
     * could not tell the drop from an empty repository. Over-evicts when this
     * mount is the root and another mount sits beneath it, since keys are
     * compared by prefix; a refetch is the safe direction to be wrong in.
     * Mirrors Python's `CacheManager.drop`.
     *
     * @param bodies drop the bodies too; false when the caller clears the whole
     *   file cache itself.
     */
    drop(bodies?: boolean): Promise<void>;
    /**
     * Retire every in-flight read and every remembered probe answer.
     *
     * The one step every cache drop takes: a read that began before it must not
     * stamp the cache after it, and a probe answer from before it must not be
     * served after it.
     */
    retire(): void;
    private goneLocked;
    /**
     * This mount's index, its backend metadata writes bound to the mount's
     * lifetime; null when the mount has none.
     *
     * The view is reused because refill locks are keyed by index identity.
     */
    scopeIndex(): IndexCacheStore | null;
    private viewOptions;
    private noteWritten;
    /**
     * Whether the running command fetched `folder`'s listing itself.
     *
     * Stricter than `listingTrusted`: outside a command no listing is the
     * caller's own, and inside one only a listing fetched under the same command
     * stamp is: not one another session fetched meanwhile, nor one a nested
     * `$(...)` or function body fetched. A listing the command fetched stays its
     * own until the command ends, so a long-running command does not see a name
     * another client creates after it listed the folder. Mirrors Python's
     * `listed_this_command`.
     */
    listedThisCommand(folder: string): boolean;
    /**
     * Whether `folder`'s listing is recent enough to serve under fresh.
     *
     * Inside a command: only if it was fetched since the command started, so
     * one command re-lists a folder once however often it reads it. Outside
     * any command (FUSE, a programmatic op) there is no command to belong to,
     * so a listing fetched within `LISTING_TRUST_WINDOW` seconds is trusted
     * instead: one `ls -l` over FUSE is a burst of calls that can share a
     * re-list until the window expires.
     *
     * Every view of the mount, shared or lock-held, records into one map, so
     * a glob's write counts for the `ls` that follows it.
     */
    listingTrusted(folder: string): boolean;
    private pruneChecks;
    /**
     * Whether a version check is recent enough to answer for the caller.
     *
     * The rule `listingTrusted` applies to listings: inside a command, only a
     * check sent after the command started, since one sent before may predate a
     * change the command must see; outside any command, one sent within
     * `LISTING_TRUST_WINDOW` seconds.
     */
    private sentRecently;
    /**
     * The backend's listing version for `key`, asking at most once.
     *
     * A check recent enough for the caller (`sentRecently`) that answered
     * `stored` is reused, so one command checks a mount once however many of
     * its folders it lists. Otherwise a check in flight that is recent enough
     * is shared, and only then is a new one sent; the newest in flight is the
     * one later callers find. A remembered answer that differs from `stored` is
     * asked again rather than trusted, since the listing may have been written
     * since, unless that answer already refused this same `stored`: nothing
     * about that listing changed. `check` answers null when the backend gives
     * no version.
     */
    checkedVersion(key: string, stored: string, check: () => Promise<string | null>): Promise<string | null>;
    private sendCheck;
    /**
     * Remember what a probe or a direct stat got from the backend.
     *
     * Called only with an answer the backend gave: the reconciler's probe, and
     * under any read policy a stat that asked the backend itself, since its
     * point observation can expire the parent listing the next lookup would
     * otherwise walk again. A stat served from an index row -- which may carry
     * no content token -- never lands here. A path the backend reports gone
     * records nothing: the probe asks the backend only when no answer is
     * servable, so there is nothing left to take back.
     */
    noteProbed(path: PathSpec, stat: FileStat): void;
    private pruneProbes;
    /** Mutation generation, captured before a freshness probe starts. */
    get generation(): number;
    /**
     * The backend's answer for `path` from this command's probe.
     *
     * A read command stats its own operand after the probe already asked the
     * backend; asking again resolves through listings the command has not
     * re-checked (or that its observation expired), and re-lists every folder
     * on the path. The answer is
     * served only inside the command that probed, and only while no cache drop
     * has landed since: a write in the command (`sed -i`, `> f`), the clear
     * after an external program, and a re-list that found the path gone all
     * retire it (`retire()`), so the next stat goes back to the backend.
     */
    probedStat(path: PathSpec): FileStat | null;
    /** Retain a live fallback row only while its predecessor still owns the slot. */
    retainResolvedEntry(path: PathSpec, generation: number, predecessor: string, entry: FileStat): Promise<void>;
    /**
     * A view for a caller already inside `mutation`.
     *
     * Never share or retain it beyond that hold. A distinct refill lock
     * avoids lock inversion with readers of the shared view.
     */
    private lockedIndex;
    /**
     * Drop one directory's cached listing.
     *
     * Both spellings of the directory go, because a backend may have keyed
     * it with or without its trailing slash and an eviction that hits no
     * key is silent.
     */
    private evictDir;
    /**
     * Cache key for a path, derived rather than inferred.
     *
     * Both caches this class evicts from are keyed by the mount-absolute virtual
     * path, so that is what this returns: the mount prefix still attached, not
     * the mount-relative spelling `mountKey` produces on the way there.
     *
     * Only `virtual` is read, and the key is rebuilt against this manager's own
     * prefix, exactly as `Mount.call` rebuilds one before handing a path to
     * a backend. The caller's `vfsPath` is deliberately ignored: it is not
     * a fact this class can trust, because `PathSpec.fromStrPath` fabricates one
     * ("assumed root-mounted") for any caller that does not know its mount.
     *
     * The earlier version inferred which convention had arrived by comparing the
     * two strings, which cannot be done: under a `/d` mount a mount-relative `/d`
     * and an absolute `/d` are the same characters naming different files.
     * Inferring wrong is quiet rather than loud -- a key one level off simply
     * evicts nothing -- which is why it survived. Deriving asks no question that
     * has no answer.
     *
     * Mirrors Python `CacheManager._cache_key`.
     */
    private cacheKey;
    /** The file cache this manager may read `key` from, if any. */
    private readableCache;
    /** A browser fallback cannot attribute an overlapping result to a session. */
    private sessionHistory;
    /**
     * Remember a refusal: the version it lost on stands until a read; null
     * forgets it.
     */
    lose(path: PathSpec, version: string | null): void;
    /**
     * Record that `caller`'s session found no file at `path` on a read or stat.
     * It lifts a refusal the running line marked on `path` earlier, as a read
     * that finds the file does. A miss no one session owns (overlapping
     * sessions on the fallback storage) is recorded nowhere, so the next write
     * goes as it would have. Mirrors Python's `observe_miss`.
     */
    observeMiss(path: PathSpec, caller: Caller | undefined): void;
    /** Record this session's exact result, never a peer's cached token. */
    observe(rec: OpRecord, owner?: Caller): void;
    /**
     * Retain the refused condition in its session, or in the cache without a
     * session. ABSENT, a refused create, is kept only in session history.
     */
    keepVersion(path: PathSpec, version: Measured): Promise<void>;
    /**
     * Conditional mounts use the live session's own observations, with
     * current-line refusals first. Without a session, and on unconditional
     * mounts, use current-line records then the shared cache's token.
     * Cache eviction never clears a session's history.
     */
    readVersion(path: PathSpec): Promise<Measured | null>;
    /** `readVersion` for many paths; session history needs no I/O. */
    readVersions(paths: readonly PathSpec[]): Promise<(Measured | null)[]>;
    /**
     * Run a cold whole-file read and keep its bytes for the next one.
     *
     * For the dispatcher, which probed the cache itself. A write that lands
     * while the fetch runs
     * retires the generation, so the bytes it read are not kept; an answer
     * that is not bytes is returned and kept nowhere. `keep`, when given, is
     * asked after the fetch with the cache's mutation lock held and has the
     * last say over whether the bytes are kept; the dispatcher passes "no
     * renderer resolves for the read". It must be synchronous; omitted, the
     * bytes are kept. Mirrors Python's `CacheManager.fill`.
     */
    fill<T>(path: PathSpec, fetch: () => Promise<T>, keep?: () => boolean): Promise<T>;
    /**
     * Pass a cold streamed read through and keep its bytes for the next one.
     *
     * The streamed twin of `fill`: the bytes are kept once the last chunk is
     * pulled, under the same rules (a write that lands while the stream runs
     * retires the generation, and `keep` has the last say), labelled with the
     * token this stream's backend recorded (`records`). A stream closed early,
     * or larger than the cache's drain budget, keeps nothing, so the cache never
     * reads past what the caller pulled and never holds more than it could.
     * Mirrors Python's `fill_stream`.
     */
    fillStream(path: PathSpec, source: AsyncIterable<Uint8Array>, records: readonly OpRecord[], keep?: () => boolean): AsyncIterable<Uint8Array>;
    private filling;
    private admits;
    /**
     * Keep a write's bytes for the next read, as a read keeps its own.
     * `fingerprint` is the token the write's backend answered, null when it
     * vouched for none. Mirrors Python's `keep_written`.
     */
    keepWritten(path: PathSpec, data: Uint8Array, fingerprint: string | null): Promise<void>;
    /**
     * Store bytes a read or a write settled on, unless they went stale.
     *
     * The one way bytes enter the file cache. The checks run under the cache's
     * mutation lock, so a name this mount stopped owning or caching keeps
     * nothing, nor do bytes `admit` refuses. Bytes over the whole cache limit
     * would evict every warm entry, so the copy held for the key, now stale,
     * goes instead; a store that refuses the bytes (out of memory, a value over
     * its size limit) costs only the next read a fetch, so the refusal is
     * logged, never thrown, and the stale copy goes too. Mirrors Python's
     * `_keep`.
     */
    private keep;
    /**
     * Return each cached render's byte length, in path order, without
     * revalidating or fetching the bytes.
     *
     * The size backfill a render-dependent backend cannot answer for itself
     * (the dispatcher's stat) runs only where the backend reported no size,
     * which is exactly the API mounts, so gating it would turn a stat into a
     * backend stat. It answers a length rather than content, so nothing can
     * serve unverified bytes through it.
     */
    cachedSizes(paths: readonly PathSpec[]): Promise<(number | null)[]>;
    /**
     * Whether a read of `path` would be served from the cache, asked of the
     * store without fetching the bytes.
     */
    isCached(path: PathSpec): Promise<boolean>;
    /**
     * Drop what a change at `path` made stale. The kind decides how far down it
     * reaches:
     *
     * - CREATE, UPDATE, and MOVE at its landing: the body, and the path's entry
     *   in its parent's listing. A CREATE that answered its new entry's stat
     *   (`row`) keeps that entry where the parent's listing names it instead, so
     *   a listing of the parent and the next level of a nested create need no
     *   request; a row naming another file (a store that matches names without
     *   case wrote the one it holds) proves nothing, so the parent is listed
     *   again.
     * - DELETE: the body, the path's own listing and its parent entry, and what
     *   is cached beneath it when a listing at or under it is still cached. That
     *   probe is for a remover that cannot say it removed a folder (a notified
     *   DELETE, the vacated side of a MOVE): a folder's nested listings and
     *   bodies were cached under keys of their own, while a plain file must not
     *   pay a subtree drop, which on a Redis file cache scans the whole keyspace.
     *   A remover that knows nothing was below (`leaf`: unlink, rmdir) skips it.
     *   A folder with no listing cached at or under it keeps the bodies cached
     *   beneath it until their ttl.
     * - UNKNOWN: everything cached at and below the path, as a push notification
     *   that names only a folder, a recursive delete or a directory rename needs.
     *
     * Then the path's ancestors drop their entries in their parents' listings: a
     * keyed store materializes every missing level of a key in one put, so those
     * listings gained entries too. Each affected listing becomes partial and
     * keeps its unaffected siblings. Every change retires this mount's in-flight
     * reads and probe answers, all but `probed`. Mirrors Python's
     * `CacheManager.invalidate`.
     *
     * @param path the changed path; only `virtual` is read.
     * @param kind what happened, which decides the reach.
     * @param row a CREATE's new entry, kept in its parent's listing; null drops
     *   that entry.
     * @param options.leaf a DELETE whose remover knows nothing was below the path.
     * @param options.probed the backend's answer the change was found by, which
     *   this command's probe keeps serving for `path` (`probedStat`) past the
     *   retire.
     */
    invalidate(path: string | PathSpec, kind: FileChangeKind, row?: FileStat | null, options?: {
        leaf?: boolean;
        probed?: FileStat | null;
    }): Promise<void>;
    private invalidateOwn;
    private removed;
    private invalidateAncestors;
    private invalidateRemoved;
    private dropBelow;
}
//# sourceMappingURL=manager.d.ts.map