//! Event types for omne pipe execution.
//!
//! Seven v1 event types per the v2 design doc. Serialized as JSON Lines
//! to per-run `.omne/var/runs/<run_id>/events.jsonl`. Each event is
//! discriminated by a top-level `type` tag.
//!
//! Call sites land in later units (`event_log`, `executor`); the module
//! carries `#[allow(dead_code)]` until then.

#![allow(dead_code)]

use serde::{Deserialize, Serialize};

/// All v1 event types.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Event {
    #[serde(rename = "pipe.started")]
    PipeStarted(PipeStarted),

    #[serde(rename = "pipe.completed")]
    PipeCompleted(PipeCompleted),

    #[serde(rename = "pipe.aborted")]
    PipeAborted(PipeAborted),

    #[serde(rename = "node.started")]
    NodeStarted(NodeStarted),

    #[serde(rename = "node.completed")]
    NodeCompleted(NodeCompleted),

    #[serde(rename = "node.failed")]
    NodeFailed(NodeFailed),

    #[serde(rename = "gate.passed")]
    GatePassed(GatePassed),

    #[serde(rename = "iteration.started")]
    IterationStarted(IterationStarted),
}

impl Event {
    /// Returns the event type tag.
    pub fn event_type(&self) -> &'static str {
        match self {
            Event::PipeStarted(_) => "pipe.started",
            Event::PipeCompleted(_) => "pipe.completed",
            Event::PipeAborted(_) => "pipe.aborted",
            Event::NodeStarted(_) => "node.started",
            Event::NodeCompleted(_) => "node.completed",
            Event::NodeFailed(_) => "node.failed",
            Event::GatePassed(_) => "gate.passed",
            Event::IterationStarted(_) => "iteration.started",
        }
    }

    /// Returns the run_id for this event.
    pub fn run_id(&self) -> &str {
        match self {
            Event::PipeStarted(e) => &e.run_id,
            Event::PipeCompleted(e) => &e.run_id,
            Event::PipeAborted(e) => &e.run_id,
            Event::NodeStarted(e) => &e.run_id,
            Event::NodeCompleted(e) => &e.run_id,
            Event::NodeFailed(e) => &e.run_id,
            Event::GatePassed(e) => &e.run_id,
            Event::IterationStarted(e) => &e.run_id,
        }
    }
}

/// Node execution kind, carried on `node.started`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NodeKind {
    Command,
    Prompt,
    Bash,
    Loop,
}

/// Gate-pass method. In v1 only `Hook` is emitted; `HumanCli` and `McpTool`
/// are reserved for post-v1 and included now to avoid a schema break.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GateMethod {
    Hook,
    HumanCli,
    McpTool,
}

/// Reason a node failed. Serializes as snake_case under `error.kind`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ErrorKind {
    HostMissing,
    Timeout,
    Blocked,
    GateFailed,
    GateTimeout,
    Crash,
    MaxIterationsExceeded,
}

/// Structured error payload nested under `NodeFailed.error`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeError {
    pub kind: ErrorKind,
}

/// One `--input key=value` pair carried on `pipe.started`.
///
/// Wire shape is an object — `{"key":"task","value":"add auth"}` —
/// rather than a 2-element array so jq queries against `events.jsonl`
/// can address fields by name (`.inputs[].value`).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Input {
    pub key: String,
    pub value: String,
}

/// Field conventions shared by every event type:
/// - `id`: monotonic ULID for this event (lowercase, distinct from `run_id`)
/// - `ts`: ISO-8601 / RFC 3339 UTC timestamp, e.g. `2026-04-15T12:00:00Z`
/// - `run_id`: `<pipe-name>-<lowercase-ulid>` from the `ulid` allocator
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipeStarted {
    pub id: String,
    pub ts: String,
    pub run_id: String,
    pub pipe: String,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub inputs: Vec<Input>,
    #[serde(default, skip_serializing_if = "String::is_empty")]
    pub distro_version: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipeCompleted {
    pub id: String,
    pub ts: String,
    pub run_id: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PipeAborted {
    pub id: String,
    pub ts: String,
    pub run_id: String,
    pub reason: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeStarted {
    pub id: String,
    pub ts: String,
    pub run_id: String,
    pub node_id: String,
    pub kind: NodeKind,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub model: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeCompleted {
    pub id: String,
    pub ts: String,
    pub run_id: String,
    pub node_id: String,
    /// Path to the node's captured output, **relative to the volume
    /// root** with forward-slash separators (e.g.
    /// `.omne/var/runs/<run_id>/nodes/<node_id>.out`). Forward slashes
    /// keep the wire format portable across Windows + Unix readers.
    pub output_path: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeFailed {
    pub id: String,
    pub ts: String,
    pub run_id: String,
    pub node_id: String,
    pub error: NodeError,
    /// Optional human-readable failure detail. Unit 11 executor
    /// populates this from the subprocess stderr tail (or gate stderr
    /// tail); absent for failures with no additional context beyond
    /// `error.kind`. Kept optional so agent-native readers get a
    /// human-readable string without forcing every producer to invent
    /// one.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GatePassed {
    pub id: String,
    pub ts: String,
    pub run_id: String,
    pub node_id: String,
    pub gate: String,
    pub method: GateMethod,
    /// Optional tail (last 1024 bytes, UTF-8-boundary-aligned) of the
    /// gate hook's stdout. Populated by the executor when the hook
    /// wrote anything to stdout so agents reading `gate.passed` can see
    /// validation summaries or coverage numbers the hook produced.
    /// Absent when the hook was silent.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub stdout: Option<String>,
}

/// Marks the start of one iteration inside a loop node. Emitted once
/// per iteration from the executor. The `byte_offset` is the zero-based
/// byte position in the node's capture file (`nodes/<id>.out`) at which
/// this iteration's assistant output begins — immediately after the
/// executor's iteration marker line. Agents reconstruct per-iteration
/// text by slicing `[byte_offset_N .. byte_offset_{N+1})` (or to EOF for
/// the final iteration) without having to parse the marker format.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IterationStarted {
    pub id: String,
    pub ts: String,
    pub run_id: String,
    pub node_id: String,
    pub iteration: u32,
    pub byte_offset: u64,
}
