/**
 * Common Command Types — Connection-level commands (handshake, ping,
 * reconnect, subscribe/unsubscribe, dispatchAction) plus the filesystem
 * `resource*` family and `authenticate` that aren't specific to any one
 * state channel.
 *
 * @module common/commands
 */

import type { URI, Snapshot } from './state.js';
import type { ActionEnvelope, StateAction } from './actions.js';
import type { TelemetryCapabilities } from '../channels-otlp/state.js';

// ─── BaseParams ──────────────────────────────────────────────────────────────

/**
 * Base shape every command's params extends.
 *
 * `channel` identifies the channel the command targets, mirroring the
 * `channel` field on every protocol notification. For commands that operate
 * on a specific channel (a session, terminal, or changeset), `channel` is
 * that channel's URI. For commands that are connection-level rather than
 * channel-scoped (e.g. {@link InitializeParams | `initialize`},
 * {@link PingParams | `ping`}, {@link ListSessionsParams | `listSessions`},
 * the `resource*` filesystem commands, and {@link AuthenticateParams |
 * `authenticate`}), the params type narrows `channel` to the literal
 * root URI `'ahp-root://'`.
 *
 * This invariant lets implementations route every incoming message —
 * request, response, or notification — by inspecting `params.channel`
 * without needing to know the per-method param shape.
 *
 * @category Commands
 */
export interface BaseParams {
  /** Channel URI this command targets. */
  channel: URI;
}

// ─── initialize ──────────────────────────────────────────────────────────────

/**
 * Establishes a new connection and negotiates the protocol version.
 * This MUST be the first message sent by the client.
 *
 * @category Commands
 * @method initialize
 * @direction Client → Server
 * @messageType Request
 * @version 1
 * @see {@link /specification/lifecycle | Lifecycle} for the full handshake flow.
 */
export interface InitializeParams extends BaseParams {
  channel: 'ahp-root://';
  /**
   * Protocol versions the client is willing to speak, ordered from most
   * preferred to least preferred. Each entry is a [SemVer](https://semver.org)
   * `MAJOR.MINOR.PATCH` string (e.g. `"0.1.0"`).
   *
   * The server selects one entry and returns it as `InitializeResult.protocolVersion`.
   * If the server cannot speak any of the offered versions, it MUST return
   * error code `-32005` (`UnsupportedProtocolVersion`).
   */
  protocolVersions: string[];
  /** Unique client identifier */
  clientId: string;
  /** URIs to subscribe to during handshake */
  initialSubscriptions?: URI[];
  /**
   * IETF BCP 47 language tag indicating the client's preferred locale
   * (e.g. `"en-US"`, `"ja"`). The server SHOULD use this to localise
   * user-facing strings such as confirmation option labels.
   */
  locale?: string;
  /**
   * Optional client capability declarations.
   *
   * Servers SHOULD only advertise features whose corresponding client
   * capability is set here. Absent means "not declared" — the server
   * MUST assume the client does not support the feature.
   */
  capabilities?: ClientCapabilities;
}

/**
 * Optional capabilities a client declares during `initialize`.
 *
 * Each field is a presence flag: an empty object `{}` means "supported",
 * absence means "not supported". Sub-fields on individual capabilities
 * are reserved for future per-capability options.
 *
 * @category Commands
 */
export interface ClientCapabilities {
  /**
   * Client can render
   * [MCP Apps](https://github.com/modelcontextprotocol/ext-apps) — i.e.
   * it can host the View sandbox, run the `ui/*` protocol against it,
   * and forward `mcp://`-channel traffic on the App's behalf.
   *
   * Hosts SHOULD only populate
   * {@link McpServerCustomization.mcpApp | `McpServerCustomization.mcpApp`}
   * (and expose the corresponding
   * {@link McpServerCustomization.channel | `mcp://` channel}) when this
   * capability is declared. Clients that omit it MUST treat
   * App-bearing tool calls as ordinary MCP tool calls.
   */
  mcpApps?: Record<string, never>;
}

/**
 * Result of the `initialize` command.
 *
 * `protocolVersion` is the version the server has selected from the client's
 * `protocolVersions` list. The client and server MUST use this version for
 * the rest of the connection. If the server cannot speak any of the offered
 * versions it MUST return error code `-32005` (`UnsupportedProtocolVersion`)
 * instead of a result.
 */
export interface InitializeResult {
  /**
   * Protocol version selected by the server. MUST be one of the entries in
   * `InitializeParams.protocolVersions`. Formatted as a [SemVer](https://semver.org)
   * `MAJOR.MINOR.PATCH` string (e.g. `"0.1.0"`).
   */
  protocolVersion: string;
  /** Current server sequence number */
  serverSeq: number;
  /** Snapshots for each `initialSubscriptions` URI */
  snapshots: Snapshot[];
  /** Suggested default directory for remote filesystem browsing */
  defaultDirectory?: URI;
  /**
   * Characters that, when typed in a {@link Message} input, SHOULD cause
   * the client to issue a `completions` request with
   * {@link CompletionItemKind.UserMessage}. Typically includes characters like
   * `'@'` or `'/'`.
   */
  completionTriggerCharacters?: string[];
  /**
   * OTLP telemetry channels the host emits, if any. Each populated field is
   * either a literal `ahp-otlp:` channel URI or an RFC 6570 URI template a
   * client expands before subscribing (currently only the `logs` channel
   * defines a template variable, `{level}`, for subscriber-side severity
   * filtering). Clients MAY ignore signals they cannot process.
   *
   * @see {@link /specification/telemetry-channel | Telemetry Channel}
   */
  telemetry?: TelemetryCapabilities;
}

// ─── ping ────────────────────────────────────────────────────────────────────

/**
 * Verifies that the AHP connection is still alive and keeps it from being
 * closed by idle-timeout intermediaries (proxies, load balancers, etc.).
 *
 * The server MUST respond regardless of whether the client has completed
 * `initialize` or holds any subscriptions. Ping carries no payload in either
 * direction; the response itself is the signal.
 *
 * @category Commands
 * @method ping
 * @direction Client → Server
 * @messageType Request
 * @version 0.1.0
 */
export interface PingParams extends BaseParams {
  channel: 'ahp-root://';
}

// ─── reconnect ───────────────────────────────────────────────────────────────

/**
 * Discriminant for reconnect result types.
 *
 * @category Commands
 */
export const enum ReconnectResultType {
  Replay = 'replay',
  Snapshot = 'snapshot',
}

/**
 * Re-establishes a dropped connection. The server replays missed actions or
 * provides fresh snapshots.
 *
 * @category Commands
 * @method reconnect
 * @direction Client → Server
 * @messageType Request
 * @version 1
 * @see {@link /specification/lifecycle | Lifecycle} for details.
 */
export interface ReconnectParams extends BaseParams {
  channel: 'ahp-root://';
  /** Client identifier from the original connection */
  clientId: string;
  /** Last `serverSeq` the client received */
  lastSeenServerSeq: number;
  /** URIs the client was subscribed to */
  subscriptions: URI[];
}

/**
 * Reconnect result when the server can replay from the requested sequence.
 *
 * The server MUST include all replayed data in the response.
 */
export interface ReconnectReplayResult {
  /** Discriminant */
  type: ReconnectResultType.Replay;
  /** Missed action envelopes since `lastSeenServerSeq` */
  actions: ActionEnvelope[];
  /**
   * URIs from `ReconnectParams.subscriptions` that the server cannot resume.
   * This includes resources that no longer exist (e.g. disposed sessions or
   * terminals) as well as resources the client is no longer permitted to
   * observe. Clients SHOULD drop these from their local subscription set.
   */
  missing: URI[];
}

/**
 * Reconnect result when the gap exceeds the replay buffer.
 */
export interface ReconnectSnapshotResult {
  /** Discriminant */
  type: ReconnectResultType.Snapshot;
  /** Fresh snapshots for each subscription */
  snapshots: Snapshot[];
}

/** Result of the `reconnect` command. */
export type ReconnectResult = ReconnectReplayResult | ReconnectSnapshotResult;

// ─── subscribe ───────────────────────────────────────────────────────────────

/**
 * Subscribe to a URI-identified channel.
 *
 * A channel MAY have state associated with it (e.g. root, sessions,
 * terminals) or be stateless (pure pub/sub for streaming data). For
 * state-bearing channels the result includes a snapshot; for stateless
 * channels `snapshot` is omitted.
 *
 * @category Commands
 * @method subscribe
 * @direction Client → Server
 * @messageType Request
 * @version 1
 * @see {@link /specification/subscriptions | Subscriptions}
 */
export interface SubscribeParams extends BaseParams {}

/**
 * Result of the `subscribe` command.
 *
 * `snapshot` is present when the subscribed channel has associated state, and
 * absent for stateless channels.
 */
export interface SubscribeResult {
  /** Snapshot of the subscribed channel's state (omitted for stateless channels) */
  snapshot?: Snapshot;
}

// ─── unsubscribe ─────────────────────────────────────────────────────────────

/**
 * Stop receiving updates for a channel.
 *
 * @category Commands
 * @method unsubscribe
 * @direction Client → Server
 * @messageType Notification
 * @version 1
 * @see {@link /specification/subscriptions | Subscriptions}
 */
export interface UnsubscribeParams {
  /** Channel URI to unsubscribe from */
  channel: URI;
}

// ─── dispatchAction ──────────────────────────────────────────────────────────

/**
 * Fire-and-forget action dispatch (write-ahead). The client applies actions
 * optimistically to local state and the server echoes them back as an
 * {@link ActionEnvelope} once accepted.
 *
 * The client → server method is named `dispatchAction`; the server's reply
 * arrives on the server → client `action` notification (params:
 * {@link ActionEnvelope}).
 *
 * @category Commands
 * @method dispatchAction
 * @direction Client → Server
 * @messageType Notification
 * @version 1
 * @see {@link /guide/actions | Actions} for the full list of client-dispatchable actions.
 */
export interface DispatchActionParams {
  /** Channel URI this action targets */
  channel: URI;
  /** Client sequence number */
  clientSeq: number;
  /** The action to dispatch */
  action: StateAction;
}

// ─── resourceRead ────────────────────────────────────────────────────────

/**
 * Encoding of fetched content data.
 *
 * @category Commands
 */
export const enum ContentEncoding {
  Base64 = 'base64',
  Utf8 = 'utf-8',
}

/**
 * Reads the content of a resource by URI.
 *
 * Content references keep the state tree small by storing large data (images,
 * long tool outputs) by reference rather than inline.
 *
 * Binary content (images, etc.) MUST use `base64` encoding. Text content MAY
 * use `utf-8` encoding.
 *
 * Like all `resource*` methods, `resourceRead` is symmetrical and MAY be
 * sent in either direction. Hosts use it to fetch content from a
 * client-published URI (e.g. `virtual://my-client/...` plugins); clients
 * use it to read host-side files. The receiver enforces access via the
 * same permission/`resourceRequest` flow regardless of which peer initiated.
 *
 * @category Commands
 * @method resourceRead
 * @direction Client ↔ Server
 * @messageType Request
 * @version 1
 * @throws `NotFound` (`-32008`) if the URI does not exist.
 * @throws `PermissionDenied` (`-32009`) if the client is not permitted to read the URI.
 * @example
 * ```jsonc
 * // Client → Server
 * { "jsonrpc": "2.0", "id": 10, "method": "resourceRead",
 *   "params": { "uri": "ahp-session:/<uuid>/content/img-1" } }
 *
 * // Server → Client
 * { "jsonrpc": "2.0", "id": 10, "result": {
 *   "data": "iVBORw0KGgo...",
 *   "encoding": "base64",
 *   "contentType": "image/png"
 * }}
 * ```
 */
export interface ResourceReadParams extends BaseParams {
  channel: 'ahp-root://';
  /** Content URI from a `ContentRef` */
  uri: string;
  /** Preferred encoding for the returned data (default: server-chosen) */
  encoding?: ContentEncoding;
}

/**
 * Result of the `resourceRead` command.
 *
 * The server SHOULD honor the `encoding` requested in the params. If the
 * server cannot provide the requested encoding, it MUST fall back to either
 * `base64` or `utf-8`.
 */
export interface ResourceReadResult {
  /** Content encoded as a string */
  data: string;
  /** How `data` is encoded */
  encoding: ContentEncoding;
  /** Content type (e.g. `"image/png"`, `"text/plain"`) */
  contentType?: string;
}

// ─── resourceWrite ───────────────────────────────────────────────────────────

/**
 * How {@link ResourceWriteParams.data} is placed within the target file.
 *
 * Each mode interprets {@link ResourceWriteParams.position} differently:
 *
 * - `truncate` (default): rooted at the **start** of the file. The file is
 *   truncated at `position` (0 by default) and `data` is written from that
 *   offset, so the resulting file is `existing[0..position] + data`. With
 *   `position` omitted this is a full overwrite.
 * - `append`: rooted at the **end** of the file. `position` counts bytes
 *   backwards from EOF, so `position: 0` (the default) writes at EOF —
 *   POSIX append — and `position: 5` inserts `data` 5 bytes before the
 *   current EOF, shifting those trailing 5 bytes after the inserted region.
 *   The server MUST evaluate the effective EOF and write atomically with
 *   respect to other appenders so concurrent `append` writes do not
 *   clobber each other.
 * - `insert`: rooted at the **start** of the file. `position` (0 by default)
 *   is the byte offset at which `data` is spliced in; bytes at or after
 *   `position` are shifted right by `data.length`. `insert` always grows
 *   the file — use `truncate` to overwrite bytes in place.
 *
 * @category Commands
 */
export const enum ResourceWriteMode {
  Truncate = 'truncate',
  Append = 'append',
  Insert = 'insert',
}

/**
 * Writes content to a file on the server's filesystem.
 *
 * Binary content (images, etc.) MUST use `base64` encoding. Text content MAY
 * use `utf-8` encoding.
 *
 * If the file does not exist, it is created. If the file already exists, the
 * effect on existing bytes depends on {@link ResourceWriteParams.mode}:
 * `truncate` (default) overwrites from the chosen offset onward, `append`
 * preserves all existing bytes and adds `data` at a position rooted at EOF,
 * and `insert` preserves all existing bytes and splices `data` in at an
 * offset rooted at the start of the file.
 *
 * Like all `resource*` methods, `resourceWrite` is symmetrical and MAY be
 * sent in either direction.
 *
 * @category Commands
 * @method resourceWrite
 * @direction Client ↔ Server
 * @messageType Request
 * @version 1
 * @throws `NotFound` (`-32008`) if the parent directory does not exist.
 * @throws `PermissionDenied` (`-32009`) if the client is not permitted to write to the path.
 * @throws `AlreadyExists` (`-32010`) if `createOnly` is set and the file already exists.
 * @throws `Conflict` (`-32011`) if `ifMatch` is set and the current `etag` does not match.
 * @example
 * ```jsonc
 * // Client → Server
 * { "jsonrpc": "2.0", "id": 11, "method": "resourceWrite",
 *   "params": { "uri": "file:///workspace/hello.txt", "data": "SGVsbG8=",
 *              "encoding": "base64", "contentType": "text/plain" } }
 *
 * // Server → Client
 * { "jsonrpc": "2.0", "id": 11, "result": {} }
 * ```
 */
export interface ResourceWriteParams extends BaseParams {
  channel: 'ahp-root://';
  /** Target file URI on the server filesystem */
  uri: URI;
  /** Content encoded as a string */
  data: string;
  /** How `data` is encoded */
  encoding: ContentEncoding;
  /** Content type (e.g. `"text/plain"`, `"image/png"`) */
  contentType?: string;
  /**
   * If `true`, the server MUST fail if the file already exists instead of
   * overwriting it. Useful for safe creation of new files.
   */
  createOnly?: boolean;
  /**
   * How `data` is placed within the target file. Defaults to `'truncate'`
   * (full overwrite) when omitted. See {@link ResourceWriteMode} for the
   * meaning of each mode and how it interprets {@link position}.
   */
  mode?: ResourceWriteMode;
  /**
   * Byte offset interpreted according to {@link mode}. Defaults to `0`.
   * - `truncate`: offset from the start of the file at which to truncate
   *   before writing.
   * - `append`: bytes back from EOF at which to insert `data`.
   * - `insert`: offset from the start of the file at which to splice in
   *   `data`.
   */
  position?: number;
  /**
   * Optimistic-concurrency token previously returned by
   * {@link ResourceResolveResult.etag}. When set, the server MUST fail with
   * `Conflict` if the current `etag` does not match — preventing lost
   * updates between a `resourceResolve` and a subsequent `resourceWrite`.
   */
  ifMatch?: string;
}

/**
 * Result of the `resourceWrite` command.
 *
 * An empty object on success.
 */
export interface ResourceWriteResult {
}

// ─── resourceList ────────────────────────────────────────────────────────

/**
 * Lists directory entries at a file URI on the server's filesystem.
 *
 * This is intended for remote folder pickers and similar UI that needs to let
 * users navigate the server's local filesystem.
 *
 * The server MUST return success only if the target exists and is a directory.
 * If the target does not exist, is not a directory, or cannot be accessed, the
 * server MUST return a JSON-RPC error.
 *
 * Like all `resource*` methods, `resourceList` is symmetrical and MAY be
 * sent in either direction.
 *
 * @category Commands
 * @method resourceList
 * @direction Client ↔ Server
 * @messageType Request
 * @version 1
 * @throws `NotFound` (`-32008`) if the directory does not exist.
 * @throws `PermissionDenied` (`-32009`) if the client is not permitted to browse the directory.
 */
export interface ResourceListParams extends BaseParams {
  channel: 'ahp-root://';
  /** Directory URI on the server filesystem */
  uri: URI;
}

/**
 * Directory entry returned by `resourceList`.
 */
export interface DirectoryEntry {
  /** Base name of the entry */
  name: string;
  /** Whether the entry is a file or directory */
  type: 'file' | 'directory';
}

/**
 * Result of the `resourceList` command.
 */
export interface ResourceListResult {
  /** Entries directly contained in the requested directory */
  entries: DirectoryEntry[];
}

// ─── resourceCopy ────────────────────────────────────────────────────────────

/**
 * Copies a resource from one URI to another on the server's filesystem.
 *
 * If the destination already exists, it is overwritten unless `failIfExists`
 * is set.
 *
 * Like all `resource*` methods, `resourceCopy` is symmetrical and MAY be
 * sent in either direction.
 *
 * @category Commands
 * @method resourceCopy
 * @direction Client ↔ Server
 * @messageType Request
 * @version 1
 * @throws `NotFound` (`-32008`) if the source does not exist.
 * @throws `PermissionDenied` (`-32009`) if the client is not permitted to read the source or write to the destination.
 * @throws `AlreadyExists` (`-32010`) if `failIfExists` is set and the destination already exists.
 */
export interface ResourceCopyParams extends BaseParams {
  channel: 'ahp-root://';
  /** Source URI to copy from */
  source: URI;
  /** Destination URI to copy to */
  destination: URI;
  /**
   * If `true`, the server MUST fail if the destination already exists instead
   * of overwriting it.
   */
  failIfExists?: boolean;
}

/**
 * Result of the `resourceCopy` command.
 *
 * An empty object on success.
 */
export interface ResourceCopyResult {
}

// ─── resourceDelete ──────────────────────────────────────────────────────────

/**
 * Deletes a resource at a URI on the server's filesystem.
 *
 * Like all `resource*` methods, `resourceDelete` is symmetrical and MAY be
 * sent in either direction.
 *
 * @category Commands
 * @method resourceDelete
 * @direction Client ↔ Server
 * @messageType Request
 * @version 1
 * @throws `NotFound` (`-32008`) if the resource does not exist.
 * @throws `PermissionDenied` (`-32009`) if the client is not permitted to delete the resource.
 */
export interface ResourceDeleteParams extends BaseParams {
  channel: 'ahp-root://';
  /** URI of the resource to delete */
  uri: URI;
  /**
   * If `true` and the target is a directory, delete it and all its contents
   * recursively. If `false` (default), deleting a non-empty directory MUST fail.
   */
  recursive?: boolean;
}

/**
 * Result of the `resourceDelete` command.
 *
 * An empty object on success.
 */
export interface ResourceDeleteResult {
}

// ─── resourceRequest ─────────────────────────────────────────────────────────

/**
 * Requests permission to access a resource on the receiver's filesystem.
 *
 * `resourceRequest` is symmetrical and MAY be sent in either direction: a
 * client asks the server to grant access to a server-side resource, or a
 * server asks the client to grant access to a client-side resource. The
 * receiver decides whether to allow, deny, or prompt the user for the
 * requested access.
 *
 * If the receiver denies access, it MUST respond with `PermissionDenied`
 * (-32009). The error data MAY include a `ResourceRequestParams` value
 * describing the access the caller would need to be granted for the
 * operation to succeed; see `PermissionDeniedErrorData` in
 * `types/errors.ts`.
 *
 * After a successful `resourceRequest`, the caller MAY use the corresponding
 * `resource*` commands (e.g. `resourceRead`, `resourceWrite`) to perform the
 * operation. Receivers MAY rescind access at any time by returning
 * `PermissionDenied` on subsequent operations.
 *
 * Either `read`, `write`, or both SHOULD be set to `true`. A request with
 * neither flag set is treated as `read: true` by receivers.
 *
 * @category Commands
 * @method resourceRequest
 * @direction Client ↔ Server
 * @messageType Request
 * @version 1
 * @throws `PermissionDenied` (`-32009`) if access is denied.
 */
export interface ResourceRequestParams extends BaseParams {
  channel: 'ahp-root://';
  /**
   * Resource URI being requested. Typically a `file:` URI on the receiver's
   * filesystem, but any URI scheme that the receiver mediates access to is
   * allowed.
   */
  uri: URI;
  /** Whether the caller needs read access to the resource. */
  read?: boolean;
  /** Whether the caller needs write access to the resource. */
  write?: boolean;
}

/**
 * Result of the `resourceRequest` command.
 *
 * An empty object on success.
 */
export interface ResourceRequestResult {
}

// ─── resourceMove ────────────────────────────────────────────────────────────

/**
 * Moves (renames) a resource from one URI to another on the server's filesystem.
 *
 * If the destination already exists, it is overwritten unless `failIfExists`
 * is set.
 *
 * Like all `resource*` methods, `resourceMove` is symmetrical and MAY be
 * sent in either direction.
 *
 * @category Commands
 * @method resourceMove
 * @direction Client ↔ Server
 * @messageType Request
 * @version 1
 * @throws `NotFound` (`-32008`) if the source does not exist.
 * @throws `PermissionDenied` (`-32009`) if the client is not permitted to move the resource.
 * @throws `AlreadyExists` (`-32010`) if `failIfExists` is set and the destination already exists.
 */
export interface ResourceMoveParams extends BaseParams {
  channel: 'ahp-root://';
  /** Source URI to move from */
  source: URI;
  /** Destination URI to move to */
  destination: URI;
  /**
   * If `true`, the server MUST fail if the destination already exists instead
   * of overwriting it.
   */
  failIfExists?: boolean;
}

/**
 * Result of the `resourceMove` command.
 *
 * An empty object on success.
 */
export interface ResourceMoveResult {
}

// ─── resourceResolve ─────────────────────────────────────────────────────────

/**
 * Discriminant for {@link ResourceResolveResult.type}.
 *
 * @category Commands
 */
export const enum ResourceType {
  File = 'file',
  Directory = 'directory',
  Symlink = 'symlink',
}

/**
 * Resolves a resource — the combination of POSIX `stat` and `realpath`.
 *
 * `resourceResolve` returns metadata about the resource together with its
 * canonical URI after symlink resolution. Use this in place of any
 * `resourceExists` shim: a missing resource MUST surface as a `NotFound`
 * JSON-RPC error rather than a success with a sentinel value. Callers that
 * truly need a boolean check should attempt `resourceResolve` and treat
 * `NotFound` as "does not exist".
 *
 * Like all `resource*` methods, `resourceResolve` is symmetrical and MAY be
 * sent in either direction.
 *
 * @category Commands
 * @method resourceResolve
 * @direction Client ↔ Server
 * @messageType Request
 * @version 1
 * @throws `NotFound` (`-32008`) if the resource does not exist.
 * @throws `PermissionDenied` (`-32009`) if the caller is not permitted to stat the URI.
 * @example
 * ```jsonc
 * // Client → Server
 * { "jsonrpc": "2.0", "id": 20, "method": "resourceResolve",
 *   "params": { "channel": "ahp-root://", "uri": "file:///workspace/hello.txt" } }
 *
 * // Server → Client
 * { "jsonrpc": "2.0", "id": 20, "result": {
 *   "uri": "file:///workspace/hello.txt",
 *   "type": "file",
 *   "size": 5,
 *   "mtime": "2026-01-15T12:34:56.789Z",
 *   "etag": "W/\"5-abc123\""
 * }}
 * ```
 */
export interface ResourceResolveParams extends BaseParams {
  channel: 'ahp-root://';
  /** URI to resolve */
  uri: URI;
  /**
   * When `true` (default), follow symlinks and report the metadata of the
   * link target — and set `uri` in the result to the canonical (realpath)
   * URI. When `false`, stat the link itself (lstat semantics) and report
   * `type: 'symlink'`.
   */
  followSymlinks?: boolean;
}

/**
 * Result of the `resourceResolve` command.
 */
export interface ResourceResolveResult {
  /**
   * Canonical URI after symlink resolution. Equal to the requested URI when
   * `followSymlinks` is `false` or the URI does not traverse a symlink.
   */
  uri: URI;
  /** Resource kind. */
  type: ResourceType;
  /**
   * Size in bytes. Omitted for directories when the provider cannot
   * cheaply compute it.
   */
  size?: number;
  /** Last-modified time in ISO 8601 format, when known. */
  mtime?: string;
  /** Creation time in ISO 8601 format, when known. */
  ctime?: string;
  /** Sniffed MIME type, when known (e.g. `"text/plain"`, `"image/png"`). */
  contentType?: string;
  /**
   * Opaque per-provider version token. When present, pass it as
   * {@link ResourceWriteParams.ifMatch} on a subsequent `resourceWrite` to
   * detect concurrent modifications.
   */
  etag?: string;
}

// ─── resourceMkdir ───────────────────────────────────────────────────────────

/**
 * Creates a directory on the server's filesystem with `mkdir -p` semantics.
 *
 * The server MUST create any missing parent directories. Creating a
 * directory that already exists is a no-op success. If `uri` already
 * exists but is **not** a directory, the server MUST fail with
 * `AlreadyExists`.
 *
 * Like all `resource*` methods, `resourceMkdir` is symmetrical and MAY be
 * sent in either direction.
 *
 * @category Commands
 * @method resourceMkdir
 * @direction Client ↔ Server
 * @messageType Request
 * @version 1
 * @throws `PermissionDenied` (`-32009`) if the caller is not permitted to create the directory.
 * @throws `AlreadyExists` (`-32010`) if `uri` already exists as a non-directory.
 */
export interface ResourceMkdirParams extends BaseParams {
  channel: 'ahp-root://';
  /** Directory URI to create (parents created as needed). */
  uri: URI;
}

/**
 * Result of the `resourceMkdir` command.
 *
 * An empty object on success.
 */
export interface ResourceMkdirResult {
}

// ─── authenticate ────────────────────────────────────────────────────────────

/**
 * Pushes a Bearer token for a protected resource. The `resource` field MUST
 * match a `ProtectedResourceMetadata.resource` value declared by an agent
 * in `AgentInfo.protectedResources`.
 *
 * Tokens are delivered using [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750)
 * (Bearer Token Usage) semantics. The client obtains the token from the
 * authorization server(s) listed in the resource's metadata and pushes it
 * to the server via this command.
 *
 * @category Commands
 * @method authenticate
 * @direction Client → Server
 * @messageType Request
 * @version 1
 * @see {@link /specification/authentication | Authentication}
 * @example
 * ```jsonc
 * // Client → Server
 * { "jsonrpc": "2.0", "id": 3, "method": "authenticate",
 *   "params": { "channel": "ahp-root://", "resource": "https://api.github.com", "token": "gho_xxxx" } }
 *
 * // Server → Client (success)
 * { "jsonrpc": "2.0", "id": 3, "result": {} }
 *
 * // Server → Client (failure — invalid token)
 * { "jsonrpc": "2.0", "id": 3, "error": { "code": -32007, "message": "Invalid token" } }
 * ```
 */
export interface AuthenticateParams extends BaseParams {
  channel: 'ahp-root://';
  /**
   * The protected resource identifier. MUST match a `resource` value from
   * `ProtectedResourceMetadata` declared in `AgentInfo.protectedResources`.
   */
  resource: string;
  /** Bearer token obtained from the resource's authorization server */
  token: string;
}

/**
 * Result of the `authenticate` command.
 *
 * An empty object on success. If the token is invalid or the resource is
 * unrecognized, the server MUST return a JSON-RPC error (e.g. `AuthRequired`
 * `-32007` or `InvalidParams` `-32602`).
 */
export interface AuthenticateResult {
}
