use std::collections::BTreeMap;

use fabro_model::{ReasoningEffort, Speed};
use serde::{Deserialize, Serialize};
use serde_json::Value;

use super::ExecOutputTail;
use crate::{
    BilledModelUsage, DiffSummary, FailureDetail, Outcome, StageId, StageOutcome, StageTiming,
};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StageStartedProps {
    pub index:                 usize,
    pub handler_type:          String,
    pub attempt:               usize,
    pub max_attempts:          usize,
    /// Graph visit that produced this stage execution. The envelope
    /// `stage_id` ordinal counts executions, which diverges from the graph
    /// visit when post-checkpoint work is replayed after
    /// resume. Absent on events written before stage execution identity.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub graph_visit:           Option<u32>,
    /// Prior execution superseded by this resumed replay.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub resumed_from_stage_id: Option<StageId>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StageCompletedProps {
    pub index: usize,
    /// Per-attempt timing breakdown for this stage visit.
    pub timing: StageTiming,
    pub status: StageOutcome,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub preferred_label: Option<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub suggested_next_ids: Vec<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub billing: Option<BilledModelUsage>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub failure: Option<FailureDetail>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub notes: Option<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub files_touched: Vec<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub context_updates: Option<BTreeMap<String, Value>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub jump_to_node: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub context_values: Option<BTreeMap<String, Value>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub node_visits: Option<BTreeMap<String, usize>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub loop_failure_signatures: Option<BTreeMap<String, usize>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub restart_failure_signatures: Option<BTreeMap<String, usize>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub response: Option<String>,
    pub attempt: usize,
    pub max_attempts: usize,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StageFailedProps {
    pub index:      usize,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub failure:    Option<FailureDetail>,
    pub will_retry: bool,
    /// Per-attempt timing breakdown for this stage visit.
    #[serde(default)]
    pub timing:     StageTiming,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub billing:    Option<BilledModelUsage>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StageRetryingProps {
    pub index:        usize,
    pub attempt:      usize,
    pub max_attempts: usize,
    pub delay_ms:     u64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StagePromptProps {
    pub visit:            u32,
    pub text:             String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mode:             Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub provider:         Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub model:            Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub reasoning_effort: Option<ReasoningEffort>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub speed:            Option<Speed>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PromptCompletedProps {
    pub response: String,
    pub model:    String,
    pub provider: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub billing:  Option<BilledModelUsage>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CheckpointCompletedProps {
    pub status: String,
    pub current_node: String,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub completed_nodes: Vec<String>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub node_retries: BTreeMap<String, u32>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub context_values: BTreeMap<String, Value>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub node_outcomes: BTreeMap<String, Outcome<Option<BilledModelUsage>>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub next_node_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub git_commit_sha: Option<String>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub loop_failure_signatures: BTreeMap<String, usize>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub restart_failure_signatures: BTreeMap<String, usize>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub node_visits: BTreeMap<String, usize>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub diff: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub diff_summary: Option<DiffSummary>,
    /// Graph visit of the checkpointed stage execution; used when this
    /// checkpoint is the event that first materializes a skipped stage.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub graph_visit: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub resumed_from_stage_id: Option<StageId>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CheckpointFailedProps {
    pub error:            String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub exec_output_tail: Option<ExecOutputTail>,
}
