/**
 * Shared types for orchestrator and proxy communication.
 */

// ── Agent ──

export type { Address } from './address.ts';

export type AgentState = 'void' | 'spawning' | 'resuming' | 'suspending' | 'active' | 'idle' | 'suspended' | 'failed';

export type EngineType = 'claude' | 'codex' | 'opencode';

/** Launch-time environment variables injected into the agent process. */
export type LaunchEnv = Record<string, string>;

// ── Hook Schema ──

/** A single action in a send sequence. Exactly one of keystroke/text/paste must be set. */
export type SendAction =
  | { keystroke: string; post_wait_ms?: number }
  | { text: string; post_wait_ms?: number }
  | { paste: string; post_wait_ms?: number };

/** Preset hook: use engine adapter default with optional overrides. */
export type PresetHook = {
  preset: string;
  options?: {
    model?: string;
    thinking?: string;
    permissions?: string;
  };
};

/** Shell hook: paste a command, auto-prefixed with env vars. */
export type ShellHook = {
  shell: string;
  env?: LaunchEnv;
};

/** Send hook: ordered sequence of tmux send-keys/paste actions. */
export type SendHook = {
  send: SendAction[];
};

/** Keystrokes hook: ordered sequence of tmux send-keys/paste actions (preferred name for SendHook). */
export type KeystrokesHook = {
  keystrokes: SendAction[];
};

/** Structured hook value — discriminated by which key is present. */
export type StructuredHook = PresetHook | ShellHook | SendHook | KeystrokesHook;

// ── Pipeline Steps ──

/** A single step in a composable hook pipeline. */
export type PipelineStep =
  | { type: 'keystrokes'; actions: SendAction[] }
  | { type: 'keystroke'; key: string }
  | { type: 'shell'; command: string; env?: LaunchEnv }
  | { type: 'capture'; lines: number; regex: string; var: string }
  | { type: 'wait'; ms: number };

/** A hook field value: flat string (legacy), structured object, or pipeline (array of steps). */
export type HookValue = string | StructuredHook | PipelineStep[] | null;

// ── Indicators ──

export type IndicatorAction = PipelineStep[];

export type IndicatorDefinition = {
  id: string;
  regex: string;
  badge: string;
  style: 'warning' | 'danger' | 'info';
  actions?: Record<string, IndicatorAction>;
};

export type ActiveIndicator = {
  id: string;
  badge: string;
  style: string;
  actions?: Record<string, IndicatorAction>;
};

// ── Detection ──

export type DetectionPattern = string | { pattern: string; lines?: number };

export type DetectionConfig = {
  idlePatterns?: DetectionPattern[];
  activePatterns?: DetectionPattern[];
  contextPattern?: string;
  idleThreshold?: number;
  activeGraceMs?: number;
  snapshotLines?: number;
  /** When true, automatically recover failed agents by starting a fresh session
   *  with a reconstruction prompt instead of leaving them in 'failed' state. */
  autoRecover?: boolean;
};

// ── Pages ──

export type PageRecord = {
  slug: string;
  title: string | null;
  agent: string | null;
  fileCount: number;
  totalBytes: number;
  createdAt: string;
  updatedAt: string;
};

// ── Destinations ──

export type DestinationRecord = {
  name: string;
  type: string;
  config: Record<string, unknown>;
  enabled: boolean;
  createdAt: string;
  updatedAt: string;
};

// ── Data Stores ──

export type DataStoreRecord = {
  name: string;
  agent: string | null;
  createdAt: string;
  updatedAt: string;
};

export type EngineConfigRecord = {
  name: string;
  engine: string;
  model: string | null;
  thinking: string | null;
  permissions: string | null;
  hookStart: string | null;
  hookResume: string | null;
  hookCompact: string | null;
  hookExit: string | null;
  hookInterrupt: string | null;
  hookReload: string | null;
  hookSubmit: string | null;
  indicators: string | null;
  detection: string | null;
  customButtons: string | null;
  launchEnv: Record<string, string> | null;
  createdAt: string;
};

export type AgentRecord = {
  name: string;
  engine: EngineType;
  model: string | null;
  thinking: string | null; // 'low' | 'medium' | 'high' | null
  cwd: string;
  persona: string | null;
  permissions: string | null; // 'skip' | null
  agentGroup: string | null; // grouping label from persona frontmatter
  launchEnv: LaunchEnv | null; // launch-time env injected on spawn/resume/reload
  account: string | null; // named credential account for HOME isolation
  sortOrder: number; // manual ordering within group
  /** Hook value for starting the agent (preset/file/inline). */
  hookStart: string | null;
  /** Hook value for resuming the agent. */
  hookResume: string | null;
  /** Hook value for compacting the agent. */
  hookCompact: string | null;
  /** Hook value for exiting the agent. */
  hookExit: string | null;
  /** Hook value for interrupting the agent. */
  hookInterrupt: string | null;
  /** Hook value for reloading the agent (exit + fresh start). */
  hookReload: string | null;
  /** Hook value for submitting messages to the agent. */
  hookSubmit: string | null;
  state: AgentState;
  stateBeforeShutdown: string | null;
  currentSessionId: string | null;
  tmuxSession: string | null;
  proxyId: string | null; // which proxy owns this agent
  lastActivity: string | null;
  lastContextPct: number | null;
  reloadQueued: number;
  reloadTask: string | null;
  failedAt: string | null;
  failureReason: string | null;
  capturedVars: Record<string, string> | null;
  customButtons: string | null;
  indicators: string | null;
  icon: string | null;
  version: number;
  spawnCount: number;
  createdAt: string;
};

// ── Events ──

export type EventRecord = {
  id: number;
  agentName: string;
  event: string;
  messageId: string | null;
  meta: string | null;
  createdAt: string;
};

// ── Dashboard Messages ──

export type MessageDirection = 'to_agent' | 'from_agent';

export type DashboardMessage = {
  id: number;
  agent: string;
  direction: MessageDirection;
  sourceAgent: string | null;
  targetAgent: string | null;
  topic: string | null;
  message: string;
  queueId: number | null;
  deliveryStatus: string | null;
  withdrawn: boolean;
  createdAt: string;
};

// ── Message Queue ──

export type PendingMessageStatus = 'pending' | 'delivered' | 'failed';

export type PendingMessage = {
  id: number;
  sourceAgent: string | null; // who sent it (null = dashboard)
  targetAgent: string;
  envelope: string;
  status: PendingMessageStatus;
  retryCount: number;
  error: string | null;
  lastAttemptAt: string | null;
  nextAttemptAt: string | null;
  createdAt: string;
  deliveredAt: string | null;
};

// ── Reminders ──

export type ReminderStatus = 'pending' | 'completed';

export type Reminder = {
  id: number;
  agentName: string;
  createdBy: string | null;
  prompt: string;
  cadenceMinutes: number;
  skipIfActive: boolean;
  sortOrder: number;
  status: ReminderStatus;
  lastDeliveredAt: string | null;
  completedAt: string | null;
  createdAt: string;
};

// ── Teams (v3 UI grouping) ──

/**
 * A team is a UI-only grouping of agents used as a filter source in the v3
 * dashboard sidebar. Teams have no behavioral effect on the kernel — they
 * only shape what the operator sees in chat, reminders, and search. An
 * agent can belong to multiple teams (many-to-many).
 *
 * Schema lives in two tables:
 *   teams(id, name UNIQUE, created_at)
 *   team_members(team_id, agent_name, added_at, PK(team_id, agent_name))
 * Deleting a team cascades to its membership rows.
 */
export type Team = {
  id: number;
  name: string;
  members: string[]; // agent names (sorted)
  createdAt: string;
};

// ── Proxy Registration ──

export type ProxyRegistration = {
  proxyId: string;
  token: string;
  host: string; // hostname:port of the proxy
  version: string | null;
  versionMatch: boolean; // true if proxy version matches orchestrator
  lastHeartbeat: string;
  registeredAt: string;
};

// ── Agent Templates / Topics (v3 ephemeral-agent surface) ──

/**
 * Row in the `agent_templates` table. Populated by template-sync from
 * persona frontmatter. Persistent personas produce a minimal row (engine,
 * model, hook_start, hook_exit); ephemeral templates additionally carry
 * cwd_base, cwd_template, repo_root and the host-shell prepare/cleanup
 * hooks. These fields never flow through the `agents` table or the scalar
 * field-registry — `agents` schema is untouched.
 */
export type AgentTemplateRow = {
  id: string;
  personaPath: string | null;
  engine: string;
  model: string | null;
  persistent: boolean;
  cwdBase: string | null;
  cwdTemplate: string | null;
  repoRoot: string | null;
  hookStart: string | null;
  hookExit: string | null;
  hookPrepare: string | null;
  hookCleanup: string | null;
  createdAt: string;
  updatedAt: string;
};

/**
 * Row in the `topics` table. Each row is a (template, name) tuple owned by
 * an `agent_templates` row. Ephemeral templates declare topics that publishers
 * address via `topic:<template>/<name>`.
 */
export type TopicRow = {
  agentTemplate: string;
  name: string;
  hookPrepareOverride: string | null;
  hookStartOverride: string | null;
  hookCleanupOverride: string | null;
  monitorTemplate: string | null;
  concurrency: number;
  schemaPath: string | null;
  replySchemaPath: string | null;
};

/** Lifecycle states for `agent_instances` rows. */
export type AgentInstanceState =
  | 'spawning'
  | 'running'
  | 'completing'
  | 'completed'
  | 'failed';

/**
 * Row in the `agent_instances` table — one per ephemeral spawn driven by the
 * TopicDelivery kernel. Separate from `agents` so the persistent-agent state
 * machine, health monitor, and cool-down logic stay untouched.
 */
export type AgentInstanceRow = {
  id: string;
  agentTemplate: string;
  spawnedFromTopic: string | null;
  instanceAddr: string;
  tmuxSession: string;
  worktreePath: string | null;
  proxyId: string;
  state: AgentInstanceState;
  failureReason: string | null;
  replyToAddr: string | null;
  messageId: string;
  messagePath: string;
  replyPath: string;
  statusPath: string;
  queueId: number | null;
  monitorOfInstance: string | null;
  startedAt: string;
  completedAt: string | null;
};

/** Status values used in the `topic_queue.status` column. */
export type TopicQueueStatus = 'queued' | 'claimed' | 'completed' | 'failed';

// ── Approvals (v3 Q5) ──

/**
 * State machine for an approval row:
 *   pending  → approved | rejected | amended | withdrawn (terminal)
 *
 * Terminal states are immutable; `setState` rejects callers that try to
 * mutate a terminal row. `withdrawn` is creator-only while pending.
 */
export type ApprovalState = 'pending' | 'approved' | 'rejected' | 'amended' | 'withdrawn';

/**
 * Row in the `approvals` table. Approvals are first-class CRUD records
 * categorised by `channel` (`approval:<channel>`); they are *not* a
 * message-routing surface — auto-notification on state change routes
 * through the existing message dispatcher to the requester's address.
 */
export type ApprovalRow = {
  id: string;
  requesterAddr: string;
  channel: string;
  payload: string;
  state: ApprovalState;
  amendmentsJson: string | null;
  createdAt: string;
  updatedAt: string;
  decidedBy: string | null;
  decidedAt: string | null;
};

/** Row in the `approval_events` audit table — one per state transition / lifecycle event. */
export type ApprovalEventRow = {
  id: number;
  approvalId: string;
  eventType: string;
  payload: string | null;
  createdAt: string;
};

/**
 * Row in the `topic_queue` table. Each `topic:<tmpl>/<name>` publish lands as
 * one row; the TopicDelivery driver claims rows atomically and pairs each
 * claim with an `agent_instances` row.
 */
export type TopicQueueRow = {
  id: number;
  agentTemplate: string;
  topicName: string;
  payload: string;
  replyToAddr: string | null;
  inReplyTo: string | null;
  status: TopicQueueStatus;
  claimedByInstance: string | null;
  worktreePath: string | null;
  createdAt: string;
  completedAt: string | null;
};

// ── WebSocket Events (Orchestrator → Dashboard) ──

export type WsInitEvent = {
  type: 'init';
  agents: AgentRecord[];
  engineConfigs: EngineConfigRecord[];
  threads: Record<string, DashboardMessage[]>;
  proxies: ProxyRegistration[];
  unreadCounts: Record<string, number>;
  indicators?: Record<string, ActiveIndicator[]>;
  stores?: DataStoreRecord[];
};

export type WsAgentUpdateEvent = {
  type: 'agent_update';
  agent: AgentRecord;
};

export type WsMessageEvent = {
  type: 'message';
  msg: DashboardMessage;
};

export type WsProxyEvent = {
  type: 'proxy_update';
  proxies: ProxyRegistration[];
};

export type WsQueueUpdateEvent = {
  type: 'queue_update';
  message: PendingMessage;
};

export type WsIndicatorUpdateEvent = {
  type: 'indicator_update';
  agentName: string;
  indicators: ActiveIndicator[];
};

export type WsStoresUpdateEvent = {
  type: 'stores_update';
  stores: DataStoreRecord[];
};

export type WsDestinationsUpdateEvent = {
  type: 'destinations_update';
  destinations: DestinationRecord[];
};

export type WsNotificationEvent = {
  type: 'notification';
  agent: string | null;
  message: string;
  priority: string;
};

// ── v3 events: templates / topics / instances / approvals ──
//
// Snake-case `type` values follow the existing WS convention. Dashboard
// consumers currently ignore unknown event types (see
// `src/dashboard/connection.ts`'s default-less switch) so adding these
// is non-breaking — Q9 will wire the consumer side.

/**
 * Emitted by `template-sync.ts` when a persona reload writes or rewrites
 * an `agent_templates` row. `removed` is reserved for Q8's orphan sweep
 * — Q4 only emits `added` and `modified`.
 */
export type WsTemplateUpdatedEvent = {
  type: 'template_updated';
  templateId: string;
  action: 'added' | 'modified' | 'removed';
};

/**
 * Emitted by `topic-delivery.ts` whenever the queued depth for a topic
 * changes (publish, claim, or completion). `depth` is the count of rows
 * in `topic_queue` with status='queued' for the (template, topic) pair.
 */
export type WsTopicQueueChangedEvent = {
  type: 'topic_queue_changed';
  agentTemplate: string;
  topic: string;
  depth: number;
};

/**
 * Emitted by `topic-delivery.ts` once an ephemeral instance has reached
 * `running` state (start hook pasted, env set, IPC files allocated).
 */
export type WsInstanceSpawnedEvent = {
  type: 'instance_spawned';
  instance: AgentInstanceRow;
};

/**
 * Emitted by `instance-reaper.ts` once an instance reaches terminal
 * `completed` state (status file said `ok`, reply routed, cleanup done).
 */
export type WsInstanceCompletedEvent = {
  type: 'instance_completed';
  instance: AgentInstanceRow;
};

/**
 * Emitted by `topic-delivery.ts` (spawn-side failure) or
 * `instance-reaper.ts` (status file said error) when an instance reaches
 * terminal `failed` state. `reason` mirrors `instance.failureReason` but
 * is exposed separately so subscribers don't have to dig into the row.
 */
export type WsInstanceFailedEvent = {
  type: 'instance_failed';
  instance: AgentInstanceRow;
  reason: string | null;
};

/**
 * Emitted by approvals (Q5) when an approval row's `state` column
 * changes. Q4 only ships the type so subscribers can subscribe ahead of
 * the producer landing.
 */
export type WsApprovalChangedEvent = {
  type: 'approval_changed';
  approvalId: string;
  state: string;
  channel: string;
};

export type WsEvent =
  | WsInitEvent
  | WsAgentUpdateEvent
  | WsMessageEvent
  | WsProxyEvent
  | WsQueueUpdateEvent
  | WsIndicatorUpdateEvent
  | WsStoresUpdateEvent
  | WsDestinationsUpdateEvent
  | WsNotificationEvent
  | WsTemplateUpdatedEvent
  | WsTopicQueueChangedEvent
  | WsInstanceSpawnedEvent
  | WsInstanceCompletedEvent
  | WsInstanceFailedEvent
  | WsApprovalChangedEvent;

// ── Proxy API ──

export type ProxyCommand =
  | { action: 'create_session'; sessionName: string; cwd: string }
  | { action: 'paste'; sessionName: string; text: string; pressEnter: boolean }
  | { action: 'capture'; sessionName: string; lines: number }
  | { action: 'kill_session'; sessionName: string }
  | { action: 'list_sessions' }
  | { action: 'has_session'; sessionName: string }
  | { action: 'pane_activity'; sessionName: string }
  | { action: 'send_keys'; sessionName: string; keys: string }
  | { action: 'send_keys_raw'; sessionName: string; keys: string[] }
  | { action: 'display_message'; sessionName: string; format: string }
  | { action: 'write_codex_profile'; profileName: string; developerInstructions: string }
  | { action: 'remove_codex_profile'; profileName: string }
  | { action: 'exec'; command: string; cwd?: string; timeoutMs?: number }
  | { action: 'resize_pane'; sessionName: string; width: number; height: number }
  | { action: 'clear_history'; sessionName: string }
  /**
   * List a directory on the proxy host. `path` is the absolute path to read;
   * if empty, the proxy interprets it as $HOME. `showHidden` includes entries
   * starting with `.` (default false). Returns:
   *   { path: <resolved absolute>, entries: [{ name, kind: 'dir'|'file'|'link' }] }
   * Used by the v3 dashboard's CWD picker so agents can be created against
   * paths that exist on the host (orchestrator is in Docker, can't read host fs).
   */
  | { action: 'list_dir'; path: string; showHidden?: boolean };

export type ProxyResponse = {
  ok: boolean;
  data?: unknown;
  error?: string;
};
