import { type ByteSource, IOResult, OpReport } from '../io/types.ts';
import type { OpRecord } from '../observe/record.ts';
import { type Clock } from '../utils/clock.ts';
import type { FileStat, SetAttrFields } from '../types.ts';
import { type MountMode, PathSpec } from '../types.ts';
import { type Caller, type CallerDispatchFn } from '../context/session_context.ts';
import type { FollowOperands, OpKwargs, SessionAccess, SessionBind } from '../view/types.ts';
import { NamespaceReader } from './dispatcher/reader.ts';
/** Receives each record with the id of the session the op ran as. */
export type OpSink = (rec: OpRecord, sessionId: string) => Promise<void>;
/** What a derived facade carries over and a constructor may set. */
export interface FilesOptions {
    bind?: SessionBind | null;
    caller?: Partial<Caller> | null;
    dispatchAs?: ((owner: SessionAccess) => CallerDispatchFn) | null;
    sessionId?: string | null;
    records?: OpRecord[];
    follow?: FollowOperands | null;
    ledger?: boolean;
    served?: Caller['mount'];
    clock?: Clock;
}
/**
 * The typed file API: `ws.vfs`, `session.vfs` and a command's, CLI leaf's or
 * runtime's `ctx.vfs`.
 *
 * Every op delegates to the workspace dispatcher, so `ws.vfs` walks the
 * same pipeline as a shell command: link follow, session grants,
 * admission policies, cache read-through, namespace structure, and
 * post-write invalidation all fire once, at that one dispatcher. The facade
 * keeps only what is its own: the typed surface and the op ledger it
 * records on — the ledger lives here, not on the workspace, which is what
 * lets `MountCore` take one `Files` instead of reaching through a whole
 * `Workspace`. The ledger is read on `ws.vfs`, a `WorkspaceFiles`. Mirrors
 * Python's `Files`.
 *
 * The facade runs as one session, `sessionId`, through `bind`: every
 * op is judged under that session's profile (hides, mount modes,
 * grants) exactly as a shell line in it would be, so an agent whose
 * file tool reads through `ws.vfs` is confined the way its shell is.
 * null names the workspace's default session as it is when the op
 * runs, since a snapshot load can rename it. A session already bound
 * when the op arrives (a command's own runtime, a kernel mount serving
 * one session) is kept, so the facade never widens the caller's view,
 * and the record names the session that judged the op. `Session`
 * derives a facade for another session over the same ledger.
 *
 * Every op also takes a trailing `sessionId` for the one-call case,
 * the way `Workspace.shell` does. One rule decides between them: a
 * shell line *sets* the session, an op *inherits* it. So the argument
 * is the session to run as when no line is already running, and the
 * line's session wins when one is, which is what keeps a handler
 * reaching the dispatcher from widening the view it was given.
 */
export declare class Files {
    private readonly dispatchFn;
    private readonly namespace;
    private readonly sink;
    private readonly bind;
    private readonly dispatchAs;
    private readonly caller;
    /** The session this facade runs as; null for the workspace's default. */
    readonly sessionId: string | null;
    /** The op ledger this facade records on; `WorkspaceFiles.records` reads it. */
    protected readonly recorded: OpRecord[];
    private readonly follow;
    private readonly ledger;
    private readonly clock;
    /**
     * The mount whose command scope these files run in, null for a run across
     * mounts; undefined where nothing said one, which the async context
     * answers instead (`servedBy`).
     *
     * @internal
     */
    readonly served: Caller['mount'] | undefined;
    /**
     * @param dispatch the workspace dispatcher every op goes to.
     * @param namespace the reader of the namespace for the session an op runs
     *   as (`Namespace.readers`); null for files outside a workspace, which see
     *   no link and no mount under the bound session's hides.
     * @param sink receives each record with the session it ran as.
     * @param options what a derived facade carries over.
     */
    constructor(dispatch: CallerDispatchFn, namespace?: ((sessionId: string | null) => NamespaceReader) | null, sink?: OpSink | null, options?: FilesOptions);
    private options;
    /**
     * The same facade run as another session, over the same ledger, so
     * the workspace-wide account stays one list.
     *
     * @internal The mechanism behind `Session.vfs`, not an entry point of
     * its own: a host binds a session with `ws.session(id)` (creating it
     * when the id is new) or `new Session(ws, id)` (adopting one
     * that exists), so there is one way to say it rather than two.
     * TypeScript has no package-private, so this stays reachable; it is
     * not part of the supported surface. Python spells it
     * `Files._for_session`.
     */
    forSession(sessionId: string): Files;
    /**
     * The same facade with `changes` said of every op's caller: a file
     * tool's call, the unit an op-level answer covers, so an approval one op
     * is answered by is claimed for the call's other ops and a call running
     * at the same time is never taken for this one; a dry run's trace; or a
     * policy method's deciding mark. Said on each op, not bound in the async
     * context, which without task isolation another task would read. Reached
     * through `withCaller`.
     */
    private forCaller;
    /**
     * The same files said to run in `mount`'s command scope, null for a run
     * across mounts: what `mountRoot` and `crossesMounts` read, tied to the
     * command's run, where an async context without task isolation answers
     * whichever task bound last. The ops are unchanged.
     *
     * @internal Said by a mount for each command it runs, and by a run across
     * mounts; Python's task context carries the caller instead.
     */
    servedBy(mount: Caller['mount']): Files;
    /**
     * The same files over another dispatcher, keeping no ledger.
     *
     * @internal Behind `captureBinding`; Python spells it `Files._over`.
     * @param dispatch the dispatcher the calls go to.
     */
    over(dispatch: CallerDispatchFn): Files;
    /**
     * The namespace as the session an op would run as sees it: the workspace's,
     * else, for files outside one, no link and no mount under the bound
     * session's hides. Mirrors Python's `Files._reader`.
     */
    private reader;
    /**
     * The mount prefix serving `path`, without a trailing slash: the one
     * `RuntimeFiles.mountOf` gives a guest, where a walk up a tree stops, as git
     * stops looking for `.git` at a filesystem boundary. null for a path the
     * session cannot see. A path a show carve-out opens inside a hidden mount
     * names that mount, whose root the path itself runs through. Mirrors
     * Python's `Files.mount_of`.
     */
    mountOf(path: string | PathSpec): string | null;
    /** Whether `path` is the root of a mount the session sees. Mirrors Python's
     * `Files.is_mount`. */
    isMount(path: string | PathSpec): boolean;
    /**
     * The mount roots strictly under `path`, sorted, without a trailing slash,
     * as `findmnt --submounts` lists them.
     *
     * A mount the session hides still stands where it is: whatever removes or
     * renames the folder holding it would take it along. It is reported by the
     * nearest folder above it the session sees, `path` itself when nothing
     * between is visible, so a caller refuses that folder without learning the
     * mount's name. Mirrors Python's `Files.submounts`.
     */
    submounts(path: string | PathSpec): string[];
    /**
     * `Workspace.dispatch` as this facade runs it: as its session, and
     * recorded on the ledger. The dispatcher `RuntimeFiles` drives for node's
     * patched `fs` (`patchNodeFs`), a facade caller like FUSE, and a
     * handler's for any op its typed methods do not spell; bound, so a handler
     * can hand it on. The IOResult is empty unless this facade keeps no
     * ledger. Mirrors Python's `Files.dispatch`.
     */
    readonly dispatch: (name: string, path: string | PathSpec, args?: readonly unknown[], kwargs?: OpKwargs, report?: OpReport) => Promise<[unknown, IOResult]>;
    /**
     * Run one op through the workspace dispatcher and record it.
     *
     * The dispatcher owns the whole pipeline (follow, grants, gates, cache,
     * structure, invalidation). On a line this workspace runs, the op records
     * where the line records, so its records are the line's; anywhere else
     * (outside a line, or on another workspace's) it records into its own
     * recording, handed to this ledger and the sink once the op ends, a
     * stream's once it closes. What records it is what records a line's
     * calls: the backend and the dispatcher, with the tokens they read. An op
     * nothing below recorded (a stat, a listing, a warm cache hit, a
     * synthetic namespace answer) is recorded here, on the path the
     * dispatcher's report says it ran on, its links followed. Mirrors
     * Python's Files._call.
     */
    private through;
    /** The session id a record carries: the one the op ran as, else this
     * facade's own, else the unbound entry point's empty id. */
    private sessionFor;
    /**
     * Read file content, whole or as the caller pulls it. `raw` skips the
     * filetype cascade: an explicit null filetype stops the dispatcher from
     * stamping the path's extension, so a rendered read op
     * (gdoc/gsheet/gslide) is bypassed and the stored bytes come back.
     * `offset`/`size` ride the same kwargs the generic read op already reads,
     * so a backend with a native range fetches one window instead of the
     * whole object. `stream` answers with an async iterable: its first chunk
     * is read before this returns, so a missing file fails here rather than at
     * the first pull, a cold read fills the cache as it is pulled, and the read
     * is recorded once the stream ends. A read the dispatcher answers whole (a
     * window, a warm copy, a rendering, a backend answering bytes) arrives as
     * one chunk. Python spells this `read(path, offset, size, raw, stream=)`.
     */
    read(path: string | PathSpec, options?: {
        raw?: boolean;
        offset?: number;
        size?: number | null;
        stream?: false;
    }, sessionId?: string): Promise<Uint8Array>;
    read(path: string | PathSpec, options: {
        raw?: boolean;
        offset?: number;
        size?: number | null;
        stream: true;
    }, sessionId?: string): Promise<AsyncIterable<Uint8Array>>;
    read(path: string | PathSpec, options: {
        raw?: boolean;
        offset?: number;
        size?: number | null;
        stream: boolean;
    }, sessionId?: string): Promise<Uint8Array | AsyncIterable<Uint8Array>>;
    write(path: string | PathSpec, data: ByteSource | string, sessionId?: string): Promise<void>;
    /**
     * Append bytes or an async byte source through the dispatcher: a VFS's
     * `append` takes either, and one with no `append` is read and rewritten.
     */
    append(path: string | PathSpec, data: ByteSource, sessionId?: string): Promise<void>;
    /**
     * Write bytes at an offset, keeping the rest of the file (the python
     * facade's `pwrite`). pwrite(2): the bytes outside the window stay, a gap
     * past the end reads back as zeros, and a missing file is created. It is
     * one write at the dispatcher, so a session that may write the file and not
     * read it can still do it. A stream lets the path's hold go while its next
     * bytes are pulled, as a streamed write does.
     */
    pwrite(path: string | PathSpec, data: ByteSource, offset: number, sessionId?: string): Promise<void>;
    readdir(path: string | PathSpec, sessionId?: string): Promise<string[]>;
    /**
     * One path's row. `nofollow` reports a trailing symlink itself rather
     * than its target (lstat), so a dangling link is a name that is there.
     * `fresh` asks the backend past the mount's listing index and reconciles
     * the index and file cache with its answer.
     */
    stat(path: string | PathSpec, sessionId?: string, opts?: {
        nofollow?: boolean;
        fresh?: boolean;
    }): Promise<FileStat>;
    exists(path: string | PathSpec, sessionId?: string): Promise<boolean>;
    isDir(path: string | PathSpec, sessionId?: string): Promise<boolean>;
    isFile(path: string | PathSpec, sessionId?: string): Promise<boolean>;
    mkdir(path: string | PathSpec, sessionId?: string): Promise<void>;
    create(path: string | PathSpec, sessionId?: string): Promise<void>;
    /**
     * Create a namespace symlink at `path`.
     *
     * Routed through the dispatcher like every write: session grants and
     * admission policies fire on the link's turf, and the write lands on
     * the ledger. The target is stored verbatim as typed. Throws EEXIST
     * when something is already at `path` (a file, a directory, another
     * link, a mount root): symlink(2) never overwrites, and the dispatcher is
     * the layer that can see both planes to tell. Throws ENOENT when the
     * directory `path` would sit in is absent, and ENOTDIR when a
     * non-directory stands there or above it. Mirrors Python's
     * Files.symlink.
     */
    symlink(path: string | PathSpec, target: string, sessionId?: string): Promise<void>;
    /** The stored target of the link at `path`; EINVAL when not a link. */
    readlink(path: string | PathSpec, sessionId?: string): Promise<string>;
    /**
     * Write metadata fields, natively where the backend can hold them.
     *
     * Every field is passed, unset ones as undefined, because the dispatcher
     * reads the whole set and stores in the namespace overlay whatever
     * the backend cannot keep. A mount with no setattr op therefore still
     * answers: a chmod on an s3 or dropbox mount lands in the name plane
     * and stat reports it back. Stored, not enforced; the mount mode is
     * the access control. Returns what the backend could not keep.
     * Mirrors Python's Files.setattr.
     */
    setattr(path: string | PathSpec, attrs?: SetAttrFields, sessionId?: string): Promise<Record<string, number | string>>;
    /**
     * One extended attribute's value. The node table answers with what a
     * caller set. `nofollow` reads a link entry's own
     * attributes. Throws ENODATA when the path has no such attribute.
     * Mirrors Python's Files.getxattr.
     */
    getxattr(path: string | PathSpec, name: string, opts?: {
        nofollow?: boolean;
    }, sessionId?: string): Promise<Uint8Array>;
    /** Every extended attribute name a path carries, sorted. */
    listxattr(path: string | PathSpec, opts?: {
        nofollow?: boolean;
    }, sessionId?: string): Promise<string[]>;
    /**
     * Store an extended attribute on a path's namespace node, so it works
     * on every backend and moves with a rename. `create` refuses with
     * EEXIST when it is set (XATTR_CREATE) and `replace` with ENODATA when
     * it is not (XATTR_REPLACE).
     * Mirrors Python's Files.setxattr.
     */
    setxattr(path: string | PathSpec, name: string, value: Uint8Array, opts?: {
        create?: boolean;
        replace?: boolean;
        nofollow?: boolean;
    }, sessionId?: string): Promise<void>;
    /** Drop an extended attribute; ENODATA when it is not set. */
    removexattr(path: string | PathSpec, name: string, opts?: {
        nofollow?: boolean;
    }, sessionId?: string): Promise<void>;
    truncate(path: string | PathSpec, length: number, sessionId?: string): Promise<void>;
    unlink(path: string | PathSpec, sessionId?: string): Promise<void>;
    rmdir(path: string | PathSpec, sessionId?: string): Promise<void>;
    /**
     * Rename a file or directory within one mount.
     *
     * Both ends must resolve to the same mount: a mount is a filesystem
     * boundary, and the dispatcher answers EXDEV across two (ENOENT when a
     * parent directory is missing), which a kernel-facing whole-workspace FUSE
     * mount needs so `mv` between two backends falls back to its copy+unlink
     * path. Mirrors Python's Files.rename.
     */
    rename(src: string | PathSpec, dst: string | PathSpec, sessionId?: string): Promise<void>;
    listFiles(path: string | PathSpec, sessionId?: string): Promise<string[]>;
}
/**
 * `ws.vfs`: the files the host holds, with what only the host asks of them.
 *
 * The op ledger every ledgered facade appends to (`records`, with the
 * network/cache split derived from it). The `Files` a handler, a CLI leaf, a
 * runtime or `session.vfs` gets is a facade of its own, on which the ledger
 * would report only that facade, so it is a plain `Files`. Mirrors Python's
 * `WorkspaceFiles`, which also answers the mount questions FUSE and the
 * runtime's `open()` interception ask; here the workspace's registry answers
 * those.
 */
export declare class WorkspaceFiles extends Files {
    /**
     * The op ledger: every facade op lands here, and the executor appends each
     * shell line's ops too, so this is the one workspace-wide account (Python's
     * `WorkspaceFiles.records`).
     */
    get records(): OpRecord[];
    /** Total bytes transferred over the network. */
    get networkBytes(): number;
    /** Total bytes served from cache. */
    get cacheBytes(): number;
}
/**
 * The namespace as the session a command's files run as sees it, for the
 * helpers that walk it (`NamespaceReader`); outside a workspace, no link and
 * no mount.
 *
 * The dispatcher's reader, never one of the facade's own: the link and mount
 * tables are the namespace's, and the reader filters every answer by the rule
 * the dispatcher refuses a call by. Mirrors Python's `reader_of`.
 */
export declare function readerOf(files: Files): NamespaceReader;
/**
 * The mode a write at `path` meets, for the session `files` runs as
 * (`effectivePathMode`): asked under the binding its ops run under, so a
 * default session's profile counts, at the path a link leads to, where the
 * write lands, unless `follow` is off. Null where no mount owns it. Mirrors
 * Python's `path_mode`.
 */
export declare function pathMode(files: Files, path: string, follow?: boolean): Promise<MountMode | null>;
/**
 * The line's operand resolution a command's globs give their matches, which a
 * command's files carry; null outside a line. Mirrors Python's `operands_of`.
 */
export declare function operandsOf(files: Files): FollowOperands | null;
/**
 * The same files with `changes` said of every op's caller: a file tool's call,
 * a dry run's trace or a policy method's deciding mark. Never a handler's: the
 * dispatcher trusts the caller, so one that dropped its rule gate would read
 * past a path rule scoped to its command. Python's task context carries the
 * caller instead.
 */
export declare function withCaller(files: Files, changes: Partial<Caller>): Files;
//# sourceMappingURL=files.d.ts.map