/**
 * Changeset State Types — Catalogue and per-changeset state for file-change
 * views exposed on the `ahp-changeset:` channel.
 *
 * @module channels-changeset/state
 */

import type { StringOrMarkdown, FileEdit, ErrorInfo } from '../common/state.js';

// ─── Changesets ──────────────────────────────────────────────────────────────

/**
 * Catalogue entry describing one changeset the server can produce for a
 * session.
 *
 * Catalogue entries are intentionally lightweight — just enough to render a
 * chip or list row without subscribing. Full per-changeset detail
 * ({@link ChangesetState}) lives on the subscribable URI obtained by
 * expanding {@link uriTemplate}.
 *
 * @category Changesets
 */
export interface Changeset {
  /** Human-readable label, e.g. `"Uncommitted Changes"`. */
  label: string;
  /**
   * RFC 6570 URI template. Clients parse the variables directly out of the
   * template using the standard `{name}` syntax — they are not redeclared
   * here.
   *
   * Only the following template shapes are defined by this protocol; any
   * other variable name MUST be ignored by clients (there is no
   * protocol-defined way to obtain values for unknown variables):
   *
   * | Variables in template                       | Meaning                                                                              |
   * | ------------------------------------------- | ------------------------------------------------------------------------------------ |
   * | _(none)_                                    | A static, session-wide changeset. The template is itself a subscribable URI.         |
   * | `{turnId}`                                  | Per-turn slice. Expand with a `Turn.id` from the session.                            |
   * | `{originalTurnId}` and `{modifiedTurnId}`   | Diff between two turns. Both variables MUST be present.                              |
   *
   * Future protocol versions MAY add new well-known variables.
   */
  uriTemplate: string;
  /** Optional longer description. */
  description?: string;
  /**
   * Advisory hint describing what kind of changeset this is, so clients can
   * group, sort, or render an appropriate icon without parsing
   * {@link uriTemplate}. Recognized values include:
   *
   * - `'session'`: a static, session-wide changeset covering all changes the
   *   agent has produced in this session.
   * - `'branch'`: changes relative to a base branch (e.g. a feature branch
   *   diffed against `main`).
   * - `'uncommitted'`: the workspace's current uncommitted changes.
   * - `'turn'`: changes produced by a single turn. Typically paired with a
   *   `{turnId}` variable in {@link uriTemplate}.
   * - `'compare-turns'`: a diff between two turns. Typically paired with
   *   `{originalTurnId}` and `{modifiedTurnId}` variables in
   *   {@link uriTemplate}.
   *
   * Implementations MAY provide additional values; clients SHOULD fall back
   * to a reasonable default when an unknown value is encountered.
   */
  changeKind: string;
}

/**
 * Computation lifecycle of a {@link ChangesetState}.
 *
 * @category Changesets
 */
export const enum ChangesetStatus {
  /** The server is still computing the contents of this changeset. */
  Computing = 'computing',
  /** The changeset has been fully computed and is up-to-date. */
  Ready = 'ready',
  /**
   * Computation failed. The cause is described by
   * {@link ChangesetState.error}.
   */
  Error = 'error',
}

/**
 * Full state for a single changeset, returned when a client subscribes to
 * an expanded changeset URI.
 *
 * The client already knows the URI it subscribed to, so this state does
 * not redundantly carry it (or the catalogue's `id`, `label`, etc.).
 * Aggregate counts (`additions`, `deletions`, `files`) are likewise
 * omitted: clients trivially compute them from `files[].edit.diff`.
 *
 * @category Changesets
 */
export interface ChangesetState {
  /** Computation lifecycle. */
  status: ChangesetStatus;
  /** Present iff `status === ChangesetStatus.Error`. */
  error?: ErrorInfo;
  /** Files in this changeset, keyed by {@link ChangesetFile.id}. */
  files: ChangesetFile[];
  /**
   * Operations the client may invoke against this changeset. Omit when no
   * operations are available.
   */
  operations?: ChangesetOperation[];
}

/**
 * One file entry within a {@link ChangesetState}.
 *
 * @category Changesets
 */
export interface ChangesetFile {
  /**
   * Stable identifier within the changeset. Typically `after.uri`
   * (or `before.uri` for deletions).
   */
  id: string;
  /**
   * Reuses the existing {@link FileEdit} shape. Clients derive line
   * additions, deletions, and rename/create/delete semantics from this.
   */
  edit: FileEdit;
  /**
   * Server-defined opaque metadata, surfaced to operations and tooling
   * but not interpreted by the protocol.
   */
  _meta?: Record<string, unknown>;
}

/**
 * Execution lifecycle of a {@link ChangesetOperation}.
 *
 * An operation is invoked imperatively via `invokeChangesetOperation`, but
 * its progress and outcome are reflected back into changeset state so that
 * every subscriber observes a consistent view (e.g. a spinner on a "Create
 * Pull Request" button, or an inline error after a failed "revert").
 *
 * @category Changesets
 */
export const enum ChangesetOperationStatus {
  /**
   * The operation is ready to be invoked. This is the default when
   * {@link ChangesetOperation.status} is omitted.
   */
  Idle = 'idle',
  /** An invocation of this operation is currently in flight. */
  Running = 'running',
  /**
   * The most recent invocation failed. The cause is described by
   * {@link ChangesetOperation.error}.
   */
  Error = 'error',
}

/**
 * Where a {@link ChangesetOperation} can be invoked.
 *
 * @category Changesets
 */
export const enum ChangesetOperationScope {
  /** Applies to the whole changeset. */
  Changeset = 'changeset',
  /** Applies to a single file within the changeset. */
  Resource = 'resource',
  /** Applies to a line range within a single file. */
  Range = 'range',
}

/**
 * A server-declared invokable verb the client can run against a
 * changeset, a file, or a range — `"stage"`, `"revert"`, `"create-pr"`,
 * and so on.
 *
 * The term "operation" is used deliberately to avoid colliding with the
 * protocol-level [Actions](/guide/actions) that mutate state.
 *
 * @category Changesets
 */
export interface ChangesetOperation {
  /** Stable identifier, unique within this changeset. */
  id: string;
  /** Human-readable button/menu label. */
  label: string;
  /** Optional longer description shown on hover or in tooltips. */
  description?: string;
  /** Where this operation can be invoked. */
  scopes: ChangesetOperationScope[];
  /**
   * Optional confirmation prompt to show before invoking. When present,
   * the client MUST display this message to the user (typically in a
   * confirmation dialog) and only invoke the operation after the user
   * accepts. The presence of this field also signals that the operation
   * is destructive — clients SHOULD style the affirmative button
   * accordingly (e.g. with a warning colour).
   */
  confirmation?: StringOrMarkdown;
  /** Optional generic icon hint, e.g. `"check"`, `"trash"`. */
  icon?: string;
  /**
   * Current execution status. The server sets
   * {@link ChangesetOperationStatus.Running | Running} while an invocation
   * is in flight, {@link ChangesetOperationStatus.Error | Error} when the
   * most recent invocation failed, and
   * {@link ChangesetOperationStatus.Idle | Idle} otherwise.
   *
   * Clients SHOULD reflect this state in the UI — e.g. disabling the
   * control or showing a spinner while `Running`, and surfacing
   * {@link error} while `Error`.
   */
  status: ChangesetOperationStatus;
  /**
   * Cause of failure. Present iff
   * `status === ChangesetOperationStatus.Error`; otherwise omitted.
   */
  error?: ErrorInfo;
}
