/**
 * Annotations Channel Actions — Mutations of an `ahp-session:/<uuid>/annotations`
 * channel's state.
 *
 * Every annotations action is client-dispatchable: rather than issuing
 * imperative RPC commands, clients drive mutations by dispatching these
 * actions directly — assigning the {@link Annotation.id} /
 * {@link AnnotationEntry.id} themselves, applying the action optimistically
 * through the write-ahead reducer, and letting the server echo it back on the
 * normal `action` envelope stream. The server MAY also originate them (e.g. an
 * agent leaving an annotation of its own). Mirrors the shape of the
 * `changeset/*` action family.
 *
 * @module channels-annotations/actions
 */

import { ActionType } from '../common/actions.js';
import type { URI, TextRange } from '../common/state.js';
import type { AnnotationEntry, Annotation } from './state.js';

// ─── Annotations Actions ─────────────────────────────────────────────────────

/**
 * Upsert an {@link Annotation} in the annotations channel — adds a new
 * annotation, or replaces an existing one identified by
 * {@link Annotation.id}.
 *
 * Dispatched by a client to create an annotation (together with its
 * mandatory first entry) or to re-anchor / resolve an existing one; the
 * dispatching client assigns the {@link Annotation.id} and the id of any
 * new entry. When replacing, the full annotation payload (including its
 * {@link Annotation.entries | entries} list) is substituted; producers
 * SHOULD prefer {@link AnnotationsEntrySetAction} for per-entry edits, and
 * {@link AnnotationsUpdatedAction} to resolve / re-anchor an existing
 * annotation, to keep wire updates small.
 *
 * @category Annotations Actions
 * @version 3
 * @clientDispatchable
 */
export interface AnnotationsSetAction {
  type: ActionType.AnnotationsSet;
  /** The new or replacement annotation. MUST contain at least one entry. */
  annotation: Annotation;
}

/**
 * Partially update an existing {@link Annotation}'s own properties — a narrow
 * alternative to {@link AnnotationsSetAction} for the common case of resolving
 * / re-opening or re-anchoring an annotation without resending its
 * {@link Annotation.entries | entries}.
 *
 * Targets one annotation by its {@link annotationId}. Only the fields present
 * on the action are written; omitted fields leave the corresponding
 * {@link Annotation} property unchanged. The annotation's
 * {@link Annotation.entries | entries}, {@link Annotation.id | id}, and
 * {@link Annotation._meta | _meta} are never touched — dispatch
 * {@link AnnotationsSetAction} to replace those, to clear {@link range}
 * (re-anchor to the whole file), or {@link AnnotationsEntrySetAction} /
 * {@link AnnotationsEntryRemovedAction} to edit individual entries.
 *
 * If {@link annotationId} does not match any current annotation the action is
 * a no-op.
 *
 * @category Annotations Actions
 * @version 4
 * @clientDispatchable
 */
export interface AnnotationsUpdatedAction {
  type: ActionType.AnnotationsUpdated;
  /** The {@link Annotation.id} of the annotation to update. */
  annotationId: string;
  /**
   * Re-anchors the annotation to the file versions this turn produced.
   * Matches a {@link Turn.id} on the owning session. Omit to leave the
   * current {@link Annotation.turnId} unchanged.
   */
  turnId?: string;
  /**
   * Re-anchors the annotation to this file. Omit to leave the current
   * {@link Annotation.resource} unchanged.
   */
  resource?: URI;
  /**
   * Narrows the annotation to this range within {@link resource}. Omit to
   * leave the current {@link Annotation.range} unchanged; this action cannot
   * clear an existing range — dispatch {@link AnnotationsSetAction} to
   * re-anchor to the whole file.
   */
  range?: TextRange;
  /**
   * Marks the annotation resolved (`true`) or re-opens it (`false`). Omit to
   * leave the current {@link Annotation.resolved} state unchanged.
   */
  resolved?: boolean;
}

/**
 * Remove an {@link Annotation} from the channel by its id.
 *
 * Dispatched to delete an entire annotation and every entry it contains.
 * Because the protocol forbids empty annotations, a client that wants to
 * remove the last remaining entry dispatches this action — collapsing the
 * annotation — rather than {@link AnnotationsEntryRemovedAction}.
 *
 * @category Annotations Actions
 * @version 3
 * @clientDispatchable
 */
export interface AnnotationsRemovedAction {
  type: ActionType.AnnotationsRemoved;
  /** The {@link Annotation.id} of the annotation to remove. */
  annotationId: string;
}

/**
 * Upsert an {@link AnnotationEntry} within an existing annotation — adds a
 * new entry, or replaces one identified by {@link AnnotationEntry.id}. The
 * dispatching client assigns the {@link AnnotationEntry.id} of a new entry.
 * If {@link annotationId} does not match any current annotation the action
 * is a no-op.
 *
 * @category Annotations Actions
 * @version 3
 * @clientDispatchable
 */
export interface AnnotationsEntrySetAction {
  type: ActionType.AnnotationsEntrySet;
  /** The {@link Annotation.id} the entry belongs to. */
  annotationId: string;
  /** The new or replacement entry. */
  entry: AnnotationEntry;
}

/**
 * Remove a single {@link AnnotationEntry} from an annotation without
 * collapsing the annotation itself. Used when more than one entry remains —
 * to remove the last entry a client dispatches {@link AnnotationsRemovedAction}
 * instead, since the protocol forbids empty annotations.
 *
 * If either {@link annotationId} or {@link entryId} does not match the
 * current state the action is a no-op.
 *
 * @category Annotations Actions
 * @version 3
 * @clientDispatchable
 */
export interface AnnotationsEntryRemovedAction {
  type: ActionType.AnnotationsEntryRemoved;
  /** The {@link Annotation.id} the entry belongs to. */
  annotationId: string;
  /** The {@link AnnotationEntry.id} to remove. */
  entryId: string;
}
