use std::fmt;
use thiserror::Error;

/// A single failed RPC attempt, capturing the endpoint index and error.
#[derive(Debug, Clone)]
pub struct RpcAttemptError {
    pub endpoint_index: usize,
    pub error: String,
}

impl fmt::Display for RpcAttemptError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "endpoint {}: {}", self.endpoint_index, self.error)
    }
}

#[derive(Debug, Error)]
pub enum PyraSolanaError {
    #[error("RPC error: {0}")]
    Rpc(String),

    #[error("All RPC endpoints failed ({} attempt(s)): {}", attempts.len(), format_attempts(attempts))]
    AllEndpointsFailed { attempts: Vec<RpcAttemptError> },

    #[error("Transaction error: {0}")]
    Transaction(String),

    #[error("Simulation failed: {0}")]
    Simulation(String),

    #[error("Gas fee too high: estimated {estimated} lamports exceeds max {max} lamports")]
    GasFeeTooHigh { estimated: u64, max: u64 },

    #[error("Math overflow")]
    MathOverflow,
}

fn format_attempts(attempts: &[RpcAttemptError]) -> String {
    attempts
        .iter()
        .map(|a| a.to_string())
        .collect::<Vec<_>>()
        .join("; ")
}

pub type Result<T> = std::result::Result<T, PyraSolanaError>;
