//! Error type for the BLE layer.
//!
//! NFR-4: no BLE dropout, malformed packet or missing characteristic may crash
//! the app. Everything that can go wrong on the wire is a value here, not a
//! panic.

use crate::capabilities::{FieldTooShort, UnsupportedTarget};
use crate::control_point::{OpCode, ResponseError, ResultCode};
use crate::indoor_bike_data::DecodeError;

#[derive(Debug, thiserror::Error)]
pub enum FtmsError {
    #[error("bluetooth error: {0}")]
    Bluetooth(#[from] btleplug::Error),

    #[error("no bluetooth adapter available")]
    NoAdapter,

    #[error("no device found matching {0} (the trainer may be asleep — pedal it and retry)")]
    NotFound(String),

    #[error("peripheral does not expose the Fitness Machine Service (0x1826)")]
    NotAFitnessMachine,

    #[error("the trainer is missing a required characteristic: {0}")]
    MissingCharacteristic(&'static str),

    #[error("{0} is not writable on this trainer")]
    NotWritable(&'static str),

    #[error("could not decode Indoor Bike Data: {0}")]
    IndoorBikeData(#[from] DecodeError),

    #[error("could not decode a control point indication: {0}")]
    ControlPointResponse(#[from] ResponseError),

    #[error("could not decode a capability characteristic: {0}")]
    Capability(#[from] FieldTooShort),

    #[error("{0}")]
    Unsupported(#[from] UnsupportedTarget),

    /// The trainer answered, but with an error. FR-2.7 — this is the whole
    /// reason control writes are not fire-and-forget.
    #[error("trainer rejected {op}: {result}")]
    Rejected { op: OpCode, result: ResultCode },

    /// The trainer answered a *different* op code than the one in flight.
    #[error("trainer answered op code 0x{got:02x} while 0x{expected:02x} was in flight")]
    MismatchedResponse { expected: u8, got: u8 },

    #[error("trainer did not acknowledge {op} within {timeout_ms} ms")]
    Unacknowledged { op: OpCode, timeout_ms: u64 },

    /// SAF-4 — repeated unacknowledged writes stop the control path and the
    /// rider must be alerted.
    #[error(
        "control halted after {failures} consecutive unacknowledged control point writes (SAF-4); \
         reconnect the trainer to resume"
    )]
    ControlHalted { failures: u32 },

    #[error("not connected to the trainer")]
    NotConnected,

    #[error("the FTMS client has shut down")]
    ClientShutDown,

    #[error("gave up reconnecting after {attempts} attempts: {reason}")]
    ReconnectFailed { attempts: u32, reason: String },
}
