use std::path::PathBuf;
use std::time::Duration;

use thiserror::Error;

/// Errors produced by the Pine client.
///
/// # Examples
///
/// ```rust
/// use pine_client::PineError;
///
/// fn describe(error: &PineError) -> String {
///     error.to_string()
/// }
/// ```
#[derive(Debug, Error)]
pub enum PineError {
    /// The configured slot is outside the valid TCP/IPC range.
    #[error("invalid Pine slot {slot}")]
    InvalidSlot { slot: u32 },

    /// The configured endpoint is not supported by the current platform.
    #[error("unsupported Pine endpoint on this platform: {endpoint}")]
    UnsupportedEndpoint { endpoint: String },

    /// Connecting to the Pine host failed.
    #[error("failed to connect to {endpoint}: {source}")]
    Connect {
        endpoint: String,
        #[source]
        source: std::io::Error,
    },

    /// Creating a Unix socket endpoint failed because the runtime path was invalid.
    #[error("invalid Pine socket path: {path:?}")]
    InvalidSocketPath { path: PathBuf },

    /// A reconnect loop ran out of attempts.
    #[error("failed to reconnect to {endpoint} after {attempts} attempts: {last_error}")]
    ReconnectExhausted {
        endpoint: String,
        attempts: u32,
        last_error: String,
    },

    /// A read/write/connect operation exceeded its timeout budget.
    #[error("pine {phase} timed out after {timeout:?}")]
    Timeout { phase: &'static str, timeout: Duration },

    /// The server closed the socket while a request was in flight.
    #[error("pine disconnected during {phase}")]
    Disconnected { phase: &'static str },

    /// The response packet was malformed.
    #[error("invalid Pine packet: {message}")]
    InvalidPacket { message: String },

    /// The emulator returned `IPC_FAIL` for a command.
    #[error("pine command failed: opcode {opcode:#04x}")]
    CommandFailed { opcode: u8 },
}

impl PineError {
    pub(crate) fn invalidates_connection(&self) -> bool {
        matches!(
            self,
            Self::Connect { .. }
                | Self::ReconnectExhausted { .. }
                | Self::Timeout { .. }
                | Self::Disconnected { .. }
                | Self::InvalidPacket { .. }
        )
    }
}
