//! Book error types.

use super::common::types::BookMarketState;
use super::protocol::command::MarketState;
use super::protocol::reject::RejectReason;
use crate::types::{CorrelationId, MarketPhase};
use thiserror::Error;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RequestedBookState {
    Market(MarketState),
    Phase(MarketPhase),
    Halted,
    Resume,
    Voided,
    Settled,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BookErrorDetail {
    MarketCommand {
        current_state: BookMarketState,
        current_phase: MarketPhase,
        requested_state: RequestedBookState,
    },
}

/// Error returned when a book command is rejected.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[error("book command rejected: {reason}")]
pub struct BookError {
    pub correlation_id: Option<CorrelationId>,
    pub reason: RejectReason,
    pub detail: Option<BookErrorDetail>,
}

impl BookError {
    pub fn new(correlation_id: Option<CorrelationId>, reason: RejectReason) -> Self {
        Self {
            correlation_id,
            reason,
            detail: None,
        }
    }

    pub fn with_detail(
        correlation_id: Option<CorrelationId>,
        reason: RejectReason,
        detail: BookErrorDetail,
    ) -> Self {
        Self {
            correlation_id,
            reason,
            detail: Some(detail),
        }
    }

    pub fn market_command(
        correlation_id: Option<CorrelationId>,
        reason: RejectReason,
        current_state: BookMarketState,
        current_phase: MarketPhase,
        requested_state: RequestedBookState,
    ) -> Self {
        Self::with_detail(
            correlation_id,
            reason,
            BookErrorDetail::MarketCommand {
                current_state,
                current_phase,
                requested_state,
            },
        )
    }

    /// Build a `BookError` for a state-transition rejection, attaching
    /// [`BookErrorDetail::MarketCommand`] only for the "no real change" family
    /// of reasons.
    pub fn state_error(
        correlation_id: Option<CorrelationId>,
        reason: RejectReason,
        current_state: BookMarketState,
        current_phase: MarketPhase,
        requested_state: RequestedBookState,
    ) -> Self {
        match reason {
            RejectReason::NoChange
            | RejectReason::MarketAlreadyHalted
            | RejectReason::MarketAlreadyVoided => Self::market_command(
                correlation_id,
                reason,
                current_state,
                current_phase,
                requested_state,
            ),
            _ => Self::new(correlation_id, reason),
        }
    }
}
