#![cfg_attr(not(doc), no_std)]
#![warn(missing_docs)]

//! Compile-time mapping foundations for `mbus-server`.
//!
//! Provides:
//! - derive-driven coil models with FC01 read / FC05 write
//! - derive-driven FC03 holding-register maps via `HoldingRegistersModel`
//! - derive-driven FC04 input-register maps via `InputRegistersModel`
//! - `modbus_app` address-range routing for FC03/FC04

extern crate self as mbus_server;

pub mod app;
pub mod services;
#[cfg(feature = "diagnostics-stats")]
pub mod statistics;

pub use app::ServerCoilHandler;
pub use app::ServerDiagnosticsHandler;
pub use app::ServerDiscreteInputHandler;
pub use app::ServerFifoHandler;
pub use app::ServerFileRecordHandler;
pub use app::ServerHoldingRegisterHandler;
pub use app::ServerInputRegisterHandler;
#[cfg(feature = "traffic")]
pub use app::TrafficNotifier;
pub use app::{ForwardingApp, ModbusAppAccess, ModbusAppHandler, ServerExceptionHandler};
#[cfg(feature = "coils")]
pub use mbus_macros::CoilsModel;
#[cfg(feature = "discrete-inputs")]
pub use mbus_macros::DiscreteInputsModel;
#[cfg(feature = "holding-registers")]
pub use mbus_macros::HoldingRegistersModel;
#[cfg(feature = "input-registers")]
pub use mbus_macros::InputRegistersModel;
pub use mbus_macros::modbus_app;
pub use services::ServerServices;
pub use services::resilience::{
    ClockFn, OverflowPolicy, RequestPriority, ResilienceConfig, TimeoutConfig,
};
#[cfg(feature = "diagnostics-stats")]
pub use statistics::ServerStatistics;

#[cfg(feature = "coils")]
pub use mbus_core::models::coil::*;
#[cfg(any(feature = "holding-registers", feature = "input-registers"))]
pub use mbus_core::models::register::*;

use mbus_core::errors::MbusError;

/// Trait implemented by user coil maps routed by `#[modbus_app]`.
#[cfg(feature = "coils")]
pub trait CoilMap {
    /// Lowest Modbus coil address declared on this map.
    const ADDR_MIN: u16;
    /// Highest Modbus coil address declared on this map.
    const ADDR_MAX: u16;
    /// Coil slots from `ADDR_MIN` to `ADDR_MAX` inclusive.
    const BIT_COUNT: usize;
    /// `true` when at least one field in this map uses `notify_via_batch = true`.
    ///
    /// Generated by `#[derive(CoilsModel)]` and used by `#[modbus_app]` for
    /// compile-time validation of hook wiring.
    const HAS_BATCH_NOTIFIED_FIELDS: bool = false;

    /// Encodes the coil window `[address, address + quantity)` into packed Modbus bit bytes.
    fn encode(&self, address: u16, quantity: u16, out: &mut [u8]) -> Result<u8, MbusError>;

    /// Writes a single coil value at `address`.
    fn write_single(&mut self, address: u16, value: bool) -> Result<(), MbusError>;

    /// Writes `quantity` packed coil values starting at `address` using `packed_bit_offset` into `values`.
    fn write_many_from_packed(
        &mut self,
        address: u16,
        quantity: u16,
        values: &[u8],
        packed_bit_offset: usize,
    ) -> Result<(), MbusError>;

    /// Returns `true` when a single-write FC05 request for `addr` should be
    /// forwarded to the map-level `on_batch_write` hook (qty = 1) if no
    /// individual field hook is registered for that address.
    #[allow(unused_variables)]
    fn is_batch_notified(addr: u16) -> bool {
        false
    }
}

// ---------------------------------------------------------------------------
// DiscreteInputMap — used by #[derive(DiscreteInputsModel)] + #[modbus_app]
// ---------------------------------------------------------------------------

/// Trait implemented by user discrete input maps routed by `#[modbus_app]`.
///
/// Discrete inputs are read-only bit values from the Modbus protocol perspective (FC02).
/// This trait only provides encode functionality; no write methods are exposed.
#[cfg(feature = "discrete-inputs")]
pub trait DiscreteInputMap {
    /// Lowest Modbus discrete input address declared on this map.
    const ADDR_MIN: u16;
    /// Highest Modbus discrete input address declared on this map.
    const ADDR_MAX: u16;
    /// Discrete input slots from `ADDR_MIN` to `ADDR_MAX` inclusive.
    const BIT_COUNT: usize;

    /// Encodes the discrete input window `[address, address + quantity)` into packed Modbus bit bytes.
    ///
    /// # Parameters
    /// - `address`: Starting discrete input address
    /// - `quantity`: Number of discrete inputs to encode
    /// - `out`: Output buffer for packed bit bytes
    ///
    /// # Returns
    /// Byte count written on success.
    ///
    /// # Errors
    /// - [`MbusError::InvalidQuantity`] if `quantity == 0`
    /// - [`MbusError::InvalidAddress`] if the window falls outside `[ADDR_MIN, ADDR_MAX]`
    /// - [`MbusError::BufferTooSmall`] if `out` is too small
    fn encode(&self, address: u16, quantity: u16, out: &mut [u8]) -> Result<u8, MbusError>;
}

// ---------------------------------------------------------------------------
// HoldingRegisterMap — used by #[derive(HoldingRegistersModel)] + #[modbus_app]
// ---------------------------------------------------------------------------

/// Trait implemented by `#[derive(HoldingRegistersModel)]`.
///
/// A register map struct where every field is a raw `u16` wire word.
/// Public getters and setters are generated by the derive macro.
/// Use `#[modbus_app]` on your application struct to wire this map to FC03.
#[cfg(feature = "holding-registers")]
pub trait HoldingRegisterMap {
    /// Lowest Modbus register address declared on this map.
    const ADDR_MIN: u16;
    /// Highest Modbus register address declared on this map.
    const ADDR_MAX: u16;
    /// Register word slots from `ADDR_MIN` to `ADDR_MAX` inclusive.
    const WORD_COUNT: usize;
    /// `true` when at least one field in this map uses `notify_via_batch = true`.
    ///
    /// Generated by `#[derive(HoldingRegistersModel)]` and used by
    /// `#[modbus_app]` for compile-time validation of hook wiring.
    const HAS_BATCH_NOTIFIED_FIELDS: bool = false;

    /// Encodes the register window `[address, address + quantity)` into `out`
    /// as consecutive big-endian 16-bit words.
    ///
    /// # Returns
    /// Byte count written (`quantity * 2`) on success.
    ///
    /// # Errors
    /// - [`MbusError::InvalidAddress`] if `quantity == 0` or the window falls
    ///   outside `[ADDR_MIN, ADDR_MAX]`.
    /// - [`MbusError::BufferTooSmall`] if `out.len() < quantity * 2`.
    fn encode(&self, address: u16, quantity: u16, out: &mut [u8]) -> Result<u8, MbusError>;

    /// Writes a single register word at `address`.
    fn write_single(&mut self, address: u16, value: u16) -> Result<(), MbusError>;

    /// Writes consecutive register words starting at `address`.
    fn write_many(&mut self, address: u16, values: &[u16]) -> Result<(), MbusError>;

    /// Returns `true` when a single-write FC06 request for `addr` should be
    /// forwarded to the map-level `on_batch_write` hook (qty = 1) if no
    /// individual field hook is registered for that address.
    #[allow(unused_variables)]
    fn is_batch_notified(addr: u16) -> bool {
        false
    }
}

// ---------------------------------------------------------------------------
// InputRegisterMap — used by #[derive(InputRegistersModel)] + #[modbus_app]
// ---------------------------------------------------------------------------

/// Trait implemented by `#[derive(InputRegistersModel)]`.
///
/// Input register maps are read-only from the Modbus protocol perspective (FC04).
/// No write methods are exposed on this trait.
#[cfg(feature = "input-registers")]
pub trait InputRegisterMap {
    /// Lowest Modbus register address declared on this map.
    const ADDR_MIN: u16;
    /// Highest Modbus register address declared on this map.
    const ADDR_MAX: u16;
    /// Register word slots from `ADDR_MIN` to `ADDR_MAX` inclusive.
    const WORD_COUNT: usize;

    /// Encodes the register window `[address, address + quantity)` into `out`
    /// as consecutive big-endian 16-bit words.
    fn encode(&self, address: u16, quantity: u16, out: &mut [u8]) -> Result<u8, MbusError>;
}

/// Trait implemented by user FIFO queue fields routed by `#[modbus_app]`.
///
/// Each field declared in `fifo(...)` must implement this trait.  The macro routes
/// FC18 `Read FIFO Queue` requests to the field whose `POINTER_ADDRESS` matches.
pub trait FifoQueueMap {
    /// The Modbus pointer address (FC18) this queue responds to.
    const POINTER_ADDRESS: u16;

    /// Fills `out` with the FIFO data for this queue.
    ///
    /// Must write `out[0..1]` as `fifo_count` (big-endian `u16`) and
    /// `out[2..2 + fifo_count * 2]` as the register values in big-endian order.
    /// Returns `Ok(2 + fifo_count * 2)`.
    fn read_fifo_queue(&mut self, out: &mut [u8]) -> Result<u8, MbusError>;
}

/// Trait implemented by user file record fields routed by `#[modbus_app]`.
///
/// Each field declared in `file_record(...)` must implement this trait.  The macro
/// routes FC14/FC15 sub-requests to the field whose `FILE_NUMBER` matches.
pub trait FileRecordMap {
    /// The Modbus file number (FC14/FC15) this record responds to.
    const FILE_NUMBER: u16;

    /// Reads `record_length` words starting at `record_number` into `out`.
    ///
    /// Returns the number of bytes written (`record_length * 2`).
    fn read_record(
        &mut self,
        record_number: u16,
        record_length: u16,
        out: &mut [u8],
    ) -> Result<u8, MbusError>;

    /// Writes `record_length` words from `data` starting at `record_number`.
    fn write_record(
        &mut self,
        record_number: u16,
        record_length: u16,
        data: &[u16],
    ) -> Result<(), MbusError>;
}

/// Common trait imports for ergonomic `mbus-server` usage.
pub mod prelude {
    #[cfg(feature = "coils")]
    pub use crate::CoilMap;
    #[cfg(feature = "discrete-inputs")]
    pub use crate::DiscreteInputMap;
    pub use crate::FifoQueueMap;
    pub use crate::FileRecordMap;
    #[cfg(feature = "holding-registers")]
    pub use crate::HoldingRegisterMap;
    #[cfg(feature = "input-registers")]
    pub use crate::InputRegisterMap;
}
