// contracts §17 — Projection/UI Contracts
// File: ui.ts — all *Projection types, ProjectionSnapshot, ProjectionStore,
//   ProjectionClient, UiCommandChannel; projection.ts symbols merged per DD §3.

import type {
  SessionID,
  TaskID,
  AgentID,
  ToolRunID,
  CommandRunID,
  ArtifactID,
  ISOTimeString,
} from './ids.js'
import type { RuntimeEvent } from './event.js'
import type { TaskStatus } from './task.js'
import type { AgentType } from './runtime.js'

// Re-export for external consumers of this module
export type {
  SessionID,
  TaskID,
  AgentID,
  ToolRunID,
  CommandRunID,
  ArtifactID,
  ISOTimeString,
}
export type { TaskStatus } from './task.js'
export type { AgentType } from './runtime.js'
export type { RuntimeEvent } from './event.js'

// =============================================================================
// §17 — Projection Types
// =============================================================================

/**
 * Derived status for CommandRunProjection.
 * Matches DD §4.4 derivation:
 *   completed_at == null            -> "running"
 *   cancellation metadata present   -> "cancelled"
 *   exit_code === 0                 -> "ok"
 *   exit_code != 0 (non-null)       -> "error"
 *   otherwise                       -> "unknown"
 */
export type CommandRunStatus = 'running' | 'ok' | 'error' | 'cancelled' | 'unknown'

/**
 * Pure derivation function for command run status.
 * Implements DD §4.4 logic so that CommandRunProjection.status
 * never invents a value outside the defined union.
 */
export function derive_command_status(fields: {
  completed_at: ISOTimeString | null | undefined
  exit_code: number | null | undefined
  cancelled: boolean
}): CommandRunStatus {
  if (fields.completed_at == null) return 'running'
  if (fields.cancelled) return 'cancelled'
  if (fields.exit_code === 0) return 'ok'
  if (fields.exit_code != null) return 'error'
  return 'unknown'
}

export interface SessionProjection {
  session_id: SessionID
  title?: string
  status: string
}

export interface TaskProjection {
  task_id: TaskID
  title: string
  status: TaskStatus
  progress_text?: string
}

export interface AgentProjection {
  agent_id: AgentID
  agent_type: AgentType
  status: string
  task_id?: TaskID
  progress_text?: string
}

export interface ToolRunProjection {
  tool_run_id: ToolRunID
  tool_name: string
  status: string
  task_id?: TaskID
}

export interface CommandRunProjection {
  command_run_id: CommandRunID
  command: string
  status: CommandRunStatus
  exit_code?: number
}

export interface ArtifactProjection {
  artifact_id: ArtifactID
  type: string
  uri: string
}

export interface PermissionPromptProjection {
  prompt_id: string
  subject: string
  risk_level: string
  reason: string
  options: string[]
}

export interface BlockerProjection {
  task_id?: TaskID
  reason: string
  required_decision: string
}

// =============================================================================
// §17 — ProjectionSnapshot
// =============================================================================

export interface ProjectionSnapshot {
  session?: SessionProjection
  tasks: TaskProjection[]
  agents: AgentProjection[]
  tool_runs: ToolRunProjection[]
  command_runs: CommandRunProjection[]
  artifacts: ArtifactProjection[]
  permission_prompts: PermissionPromptProjection[]
  blockers: BlockerProjection[]
  updated_at: ISOTimeString
}

// =============================================================================
// §17 — Subscription (re-declared here for projection consumers)
// =============================================================================

/**
 * Subscription handle returned by ProjectionStore.subscribe and
 * ProjectionClient.subscribe. Mirrors the EventBus Subscription
 * contract (§7) for standalone projection consumers.
 */
export interface Subscription {
  unsubscribe(): void
}

// =============================================================================
// §17 — ProjectionStore
// =============================================================================

/**
 * ProjectionStore.apply handles all durable events and key ephemeral events
 * (agent.heartbeat, task.progress, assistant.message.delta, tool.progress,
 * command.stdout.delta, command.stderr.delta). Unknown event types are ignored.
 *
 * command_runs projection status uses the derivation in DD §4.4
 * (derive_command_status). ProjectionStore is never a scheduling/recovery
 * source of truth (overview §9.3).
 */
export interface ProjectionStore {
  hydrate(session_id: SessionID): Promise<void>
  apply(event: RuntimeEvent): void
  snapshot(): ProjectionSnapshot
  subscribe(handler: (snapshot: ProjectionSnapshot) => void): Subscription
}

// =============================================================================
// §17 — ProjectionClient
// =============================================================================

/**
 * Read-only client for the TUI. V1 transport: TUI runs in-process with runtime;
 * ProjectionClient is a direct interface reference, not IPC.
 * TUI may consume only ProjectionClient or projection contracts, never runtime
 * internals, SQLite, or EventBus directly.
 */
export interface ProjectionClient {
  snapshot(): ProjectionSnapshot
  subscribe(handler: (snapshot: ProjectionSnapshot) => void): Subscription
}

// =============================================================================
// §17 — UiCommandChannel
// =============================================================================

/**
 * Narrow interface through which the TUI emits user decisions back to the
 * runtime. Covers two V1 command types:
 *
 *   - Permission prompt responses (user selects an option for a
 *     permission.prompt.requested event; runtime emits
 *     permission.prompt.resolved).
 *   - Blocker decisions (user resolves a BlockerProjection; runtime
 *     processes the decision through the scheduler/task system).
 *
 * Boundary rule (code-view §7): packages/tui may only import from
 * packages/contracts, never from packages/runtime/src/*.
 */
export interface UiCommandChannel {
  /**
   * Resolve a permission prompt by selecting an option.
   * The runtime will emit a permission.prompt.resolved durable event
   * and resume the suspended tool call.
   */
  resolve_permission_prompt(prompt_id: string, selected_option: string): Promise<void>

  /**
   * Resolve a blocker by providing the user's decision.
   * The runtime processes the decision through the task system
   * to unblock the affected task.
   */
  resolve_blocker(blocker: BlockerProjection, decision: string): Promise<void>
}