//! Inspection types for [`crate::PgSagaRunner`] — one-call snapshots of a
//! saga instance for tests, debugging, and operator tooling.
//!
//! The data derives entirely from durable state: the envelope stream, the
//! topology, and the `es_saga_awaits` routing rows.

use crate::envelope::{SagaInstanceStatus, SagaStepStatus};
use crate::result::StepError;
use crate::step::StepName;

/// A one-shot snapshot of one saga instance, suitable for printing.
/// Produced by [`crate::PgSagaRunner::describe`]. See the `Display` impl for
/// the rendered shape; the struct fields are stable and machine-readable.
#[derive(Debug, Clone)]
pub struct InstanceDiagnostic {
    pub saga: &'static str,
    pub instance_id: String,
    pub status: SagaInstanceStatus,
    pub steps: Vec<(StepName, SagaStepStatus)>,
    pub awaiting: Vec<AwaitDescription>,
    pub emit_count: usize,
    /// Steps whose compensation is still in flight (`Compensating`), in
    /// cascade order (reverse declaration order).
    pub compensation_queue: Vec<StepName>,
    pub originating_error: Option<StepError>,
    pub last_error: Option<StepError>,
}

/// What a step is currently waiting for — one row in `es_saga_awaits`.
#[derive(Debug, Clone)]
pub struct AwaitDescription {
    pub step: String,
    pub event_type: String,
    pub deadline_passed: bool,
    pub is_compensation: bool,
}

impl std::fmt::Display for InstanceDiagnostic {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(
            f,
            "saga `{}` instance `{}` — {:?}",
            self.saga, self.instance_id, self.status
        )?;
        writeln!(f, "  steps:")?;
        for (name, status) in &self.steps {
            writeln!(f, "    {} = {:?}", name, status)?;
        }
        if !self.awaiting.is_empty() {
            writeln!(f, "  awaiting:")?;
            for a in &self.awaiting {
                let suffix = if a.deadline_passed {
                    " (DEADLINE PASSED)"
                } else {
                    ""
                };
                writeln!(f, "    {} ← `{}`{}", a.step, a.event_type, suffix)?;
            }
        }
        if !self.compensation_queue.is_empty() {
            writeln!(f, "  compensation queue:")?;
            for s in &self.compensation_queue {
                writeln!(f, "    {}", s)?;
            }
        }
        writeln!(f, "  emits recorded: {}", self.emit_count)?;
        if let Some(orig) = &self.originating_error {
            writeln!(f, "  originating error: {}", orig)?;
        }
        if let (Some(orig), Some(last)) = (&self.originating_error, &self.last_error) {
            // StepError doesn't impl PartialEq; compare via Debug repr to
            // suppress the duplicate line in the common single-failure case.
            if format!("{:?}", orig) != format!("{:?}", last) {
                writeln!(f, "  last error:        {}", last)?;
            }
        } else if let Some(last) = &self.last_error {
            writeln!(f, "  last error:        {}", last)?;
        }
        Ok(())
    }
}

/// One emit recorded on a saga instance's stream (a `StepEmitted` envelope
/// event), in stream order. The durable audit trail of "what the saga asked
/// for" — both in-process-handled (Regime A) and external (Regime B) emits
/// are recorded (review P1.2a).
#[derive(Debug, Clone)]
pub struct EmittedEvent {
    pub instance_id: String,
    pub source_step: String,
    /// 1-based position among the instance's emits, in stream order.
    pub sequence: i64,
    pub event_type: String,
    pub payload: serde_json::Value,
}
