import { atom } from 'nanostores'

export type TabStatus = 'idle' | 'running' | 'done' | 'error'
export type TabType = 'shell' | 'task' | 'agent'

/**
 * Rich agent turn state — driven by hook events from AgentTurnMod.
 * Matches the AgentState type in agent.helpers.ts so deriveAgentState can
 * return it directly: `if (meta.agentState) return meta.agentState`.
 */
export type AgentTurnState = 'idle' | 'in-progress' | 'awaiting' | 'completed'

export type PrState = 'OPEN' | 'MERGED' | 'CLOSED'

/**
 * Compact CI rollup populated by GitMonitorMod from `gh pr view`'s
 * `statusCheckRollup`. Rust does the bucketing so the frontend treats this
 * as a closed 4-bucket counter — unknown check states are dropped on the
 * way out, never producing an "unclassified" bucket here.
 */
export type PrChecks = {
  passing: number
  failing: number
  pending: number
  skipped: number
  total: number
}

export type PrInfo = {
  number: number
  title: string
  state: PrState
  isDraft: boolean
  url: string
  /** Undefined when no checks have reported yet (or repo has no CI). */
  checks?: PrChecks
}

export type GitInfo = {
  branch: string
  aheadBy: number
  behindBy: number
  isDirty: boolean
  worktree?: string
  pr?: PrInfo
}

/**
 * A single process entry as emitted by ProcessInspectorMod.
 * Mirrors the Rust `ProcessEntry` struct serialised over IPC.
 *
 * Note: `cpuPercent` is a sysinfo lifetime average — not a real-time sample.
 * Do not display it in the UI; it is intentionally omitted here.
 */
export type ProcessInfo = {
  pid: number
  name: string
  /** Full command string including args (from `ps -o args=`). */
  command: string
  /** Resident memory in kilobytes (from sysinfo). */
  memoryKb: number
  /** Elapsed wall-clock time formatted as `mm:ss` or `h:mm:ss` or `d-hh:mm`. */
  elapsedTime: string
  /** TCP ports this process is listening on (from lsof). */
  listeningPorts: number[]
}

export type TabMeta = {
  /** Shell or agent process state — driven by ProcessTrackerMod (OSC 133). */
  status: TabStatus
  /** Tab classification — set by ClaudeCodeMod / CodexMod. */
  type: TabType
  /** Current working directory — set by DirTrackerMod (OSC 7). */
  cwd?: string
  /** Git context — set by GitMonitorMod. */
  git?: GitInfo
  /** Non-zero exit code when status is "error". */
  exitCode?: number
  /**
   * Epoch-ms timestamp when status most recently became "done". Drives
   * the transient green dot in TabStatusIcon. Lives in the store (not
   * component-local) so every render of the icon — sidebar, tab bar,
   * Cmd+P palette — agrees on whether the dot is currently lit. Cleared
   * by the mod-listener's per-tab 10s timer or when the user navigates
   * to the tab (acknowledgement).
   */
  doneAt?: number
  /**
   * Machine-readable agent identifier — `"claude-code"`, `"codex"`, etc.
   * Use this for routing / lookups, never for display.
   * Set when type is "agent" by the per-agent mod (ClaudeCodeMod / CodexMod / …).
   */
  agentId?: string
  /**
   * Human-readable agent name for UI — `"Claude Code"`, `"Codex CLI"`, etc.
   * Comes from the `AGENT_HOOK_CONFIGS` registry on the Rust side; flows
   * directly on the `tab_type_changed` event so consumers (badges, OS
   * notifications, status bar widgets) never need their own
   * `agent_id → name` lookup table — they read this field as-is.
   */
  agentDisplayName?: string
  /** Full command used to launch the agent — set by ClaudeCodeMod / CodexMod. */
  agentCmd?: string
  /**
   * Live process list for this tab — set by ProcessInspectorMod every 2s.
   * Only agent processes (claude, codex) that are direct children of the
   * tab's shell PID are included. Empty for shell-only tabs.
   */
  processes?: ProcessInfo[]
  /**
   * Convenience: TCP ports across all tracked processes.
   * Derived from `processes` in the mod-listener; kept for backwards compat.
   */
  listeningPorts?: number[]
  /**
   * Rich agent turn state — set by AgentTurnMod via hook events.
   * `undefined` means no hook data has arrived yet; deriveAgentState falls
   * back to OSC 133-based heuristics in that case.
   */
  agentState?: AgentTurnState
  /**
   * Optional message associated with the current agentState.
   * `awaiting`: the question or permission text the agent needs answered.
   * `completed`: the last assistant message (truncated to ~200 chars).
   */
  agentMessage?: string
}

const defaultMeta: TabMeta = { status: 'idle', type: 'shell' }

/**
 * Ephemeral runtime metadata for each terminal tab, keyed by tabId.
 *
 * This store is never persisted — MODs recompute all values from scratch when
 * a tab is opened. Keeping it separate from `$projects` means persisted user
 * configuration and live runtime state never mix.
 */
export const $tabMeta = atom<Record<string, TabMeta>>({})

export function updateTabMeta(tabId: string, patch: Partial<TabMeta>): void {
  const cur = $tabMeta.get()
  const next = { ...defaultMeta, ...cur[tabId], ...patch }
  if (JSON.stringify(cur[tabId]) === JSON.stringify(next)) return
  $tabMeta.set({ ...cur, [tabId]: next })
}

export function clearTabMeta(tabId: string): void {
  const cur = $tabMeta.get()
  const next = { ...cur }
  delete next[tabId]
  $tabMeta.set(next)
}
