/// @file spi_hw_2_nrf52.h
/// @brief NRF52 implementation of Dual-SPI using SPIM peripherals
///
/// This file provides the SPIDualNRF52 class for Nordic nRF52 series microcontrollers.
/// Uses two SPIM instances (SPIM0 + SPIM1) for dual-lane SPI transmission.
///
/// Hardware approach:
/// - SPIM0 for lane 0 (D0)
/// - SPIM1 for lane 1 (D1)
/// - TIMER + PPI + GPIOTE for synchronized clock generation
/// - EasyDMA for zero-CPU transfers
///
/// Platform support:
/// - nRF52832: SPIM0/1 @ 8 MHz max
/// - nRF52840: SPIM0/1 @ 8 MHz max
///
/// @note All class definition and implementation is contained in this single file

#pragma once

// IWYU pragma: private

#include "platforms/arm/nrf52/is_nrf52.h"

#ifdef FL_IS_NRF52

#include "platforms/shared/spi_hw_2.h"
#include "fl/stl/limits.h"
// IWYU pragma: begin_keep
#include <nrf_spim.h>
#include <nrf_gpio.h>
// IWYU pragma: end_keep

// Include Nordic SDK headers for TIMER, PPI, GPIOTE
#if defined(FL_IS_NRF52840) || defined(FL_IS_NRF52833)
    // IWYU pragma: begin_keep
    #include <nrfx_timer.h>
    #include <nrfx_ppi.h>
    #include <nrfx_gpiote.h>
    // IWYU pragma: end_keep
#else
    // For nRF52832, use legacy API
    // IWYU pragma: begin_keep
    #include <nrf_timer.h>
    #include <nrf_ppi.h>
    #include <nrf_gpiote.h>
    // IWYU pragma: end_keep
#endif

#include "fl/log/log.h"
#include "fl/stl/stddef.h"
#include "fl/stl/string.h"
#include "fl/stl/noexcept.h"

namespace fl {

// ============================================================================
// SPIDualNRF52 Class Definition
// ============================================================================

/// NRF52 hardware driver for Dual-SPI DMA transmission using SPIM peripherals
///
/// Implements SpiHw2 interface for Nordic nRF52 platforms using:
/// - SPIM0 + SPIM1 for dual-lane data transmission
/// - TIMER0 for clock generation (via PPI/GPIOTE)
/// - EasyDMA for non-blocking asynchronous transfers
/// - Configurable clock frequency up to 8 MHz (nRF52832) or 32 MHz (SPIM3 on nRF52840)
///
/// @note Each instance allocates two SPIM peripherals
/// @note Requires EasyDMA buffers in RAM (not flash)
/// @note Uses PPI channels 0-2 for synchronization
/// @note Uses GPIOTE channel 0 for clock output
class SPIDualNRF52 : public SpiHw2 {
public:
    /// @brief Construct a new SPIDualNRF52 controller
    /// @param bus_id Logical bus identifier (0 or 1)
    /// @param name Human-readable name for this controller
    explicit SPIDualNRF52(int bus_id = -1, const char* name = "Unknown") FL_NOEXCEPT;

    /// @brief Destroy the controller and release all resources
    ~SPIDualNRF52();

    /// @brief Initialize the SPI controller with specified configuration
    /// @param config Configuration including pins, clock speed, and bus number
    /// @return true if initialization successful, false on error
    /// @note Validates pin assignments and allocates SPIM/TIMER/PPI resources
    bool begin(const SpiHw2::Config& config) FL_NOEXCEPT override;

    /// @brief Deinitialize the controller and release resources
    void end() FL_NOEXCEPT override;

    /// @brief Acquire a DMA buffer for zero-copy data preparation
    /// @param bytes_per_lane Number of bytes per lane to allocate
    /// @return DMABuffer containing span to buffer or error code
    /// @note Automatically waits if previous transmission still active
    /// @note Reallocates only if requested size exceeds current capacity
    DMABuffer acquireDMABuffer(size_t bytes_per_lane) FL_NOEXCEPT override;

    /// @brief Start non-blocking transmission using internal DMA buffer
    /// @return true if transfer started successfully, false on error
    /// @note Must call acquireDMABuffer() first
    /// @note Returns immediately - use waitComplete() to block until done
    bool transmit(TransmitMode mode = TransmitMode::ASYNC) FL_NOEXCEPT override;

    /// @brief Wait for current transmission to complete
    /// @param timeout_ms Maximum time to wait in milliseconds (fl::numeric_limits<uint32_t>::max() = infinite)
    /// @return true if transmission completed, false on timeout
    bool waitComplete(u32 timeout_ms = fl::numeric_limits<u32>::max()) FL_NOEXCEPT override;

    /// @brief Check if transmission is currently in progress
    /// @return true if busy, false if idle
    bool isBusy() const FL_NOEXCEPT override;

    /// @brief Check if controller has been initialized
    /// @return true if initialized, false otherwise
    bool isInitialized() const FL_NOEXCEPT override;

    /// @brief Get the bus identifier for this controller
    /// @return Bus ID (0 or 1)
    int getBusId() const FL_NOEXCEPT override;

    /// @brief Get the human-readable name for this controller
    /// @return Controller name string
    const char* getName() const FL_NOEXCEPT override;

private:
    /// @brief Release all allocated resources (SPIM, TIMER, PPI, GPIOTE, buffers)
    void cleanup() FL_NOEXCEPT;

    /// @brief Allocate or resize internal DMA buffers for dual-lane operation
    /// @param required_size Size needed in bytes (per lane)
    /// @return true if buffers allocated successfully
    bool allocateDMABuffers(size_t required_size) FL_NOEXCEPT;

    /// @brief Configure TIMER0 for clock generation
    /// @param clock_speed_hz Desired clock frequency
    void configureTimer(u32 clock_speed_hz) FL_NOEXCEPT;

    /// @brief Configure PPI channels for synchronization
    void configurePPI() FL_NOEXCEPT;

    /// @brief Configure GPIOTE for clock output
    void configureGPIOTE() FL_NOEXCEPT;

    /// @brief Start synchronized transmission on both SPIM peripherals
    void startTransmission() FL_NOEXCEPT;

    int mBusId;  ///< Logical bus identifier
    const char* mName;  ///< Human-readable controller name

    // SPIM resources
    NRF_SPIM_Type* mSPIM0;  ///< SPIM0 peripheral (lane 0)
    NRF_SPIM_Type* mSPIM1;  ///< SPIM1 peripheral (lane 1)

    // TIMER resource
    NRF_TIMER_Type* mTimer;  ///< TIMER0 for clock generation

    // DMA buffer management (must be in RAM for EasyDMA)
    DMABuffer mDMABuffer;            ///< Allocated DMA buffer (interleaved format for dual-lane)
    size_t mMaxBytesPerLane;         ///< Max bytes per lane we've allocated for
    size_t mCurrentTotalSize;        ///< Current transmission size (bytes_per_lane * num_lanes)
    bool mBufferAcquired;            ///< True if buffer has been acquired for transmission

    // Legacy lane buffers (kept for internal de-interleaving)
    u8* mLane0Buffer;  ///< Buffer for lane 0 data
    u8* mLane1Buffer;  ///< Buffer for lane 1 data
    size_t mBufferSize;     ///< Size of each lane buffer in bytes

    // State
    bool mTransactionActive;  ///< True if transmission in progress
    bool mInitialized;        ///< True if controller initialized

    // Configuration
    u8 mClockPin;  ///< Clock pin (SCK)
    u8 mData0Pin;  ///< Data pin for lane 0
    u8 mData1Pin;  ///< Data pin for lane 1
    u32 mClockSpeed;  ///< Clock frequency in Hz

    // PPI channels used (0-2)
    u8 mPPIChannel0;  ///< PPI channel for TIMER -> GPIOTE (clock)
    u8 mPPIChannel1;  ///< PPI channel for TIMER -> SPIM0.START
    u8 mPPIChannel2;  ///< PPI channel for TIMER -> SPIM1.START

    SPIDualNRF52(const SPIDualNRF52&) = delete;
    SPIDualNRF52& operator=(const SPIDualNRF52&) = delete;
};

}  // namespace fl

#endif  // FL_IS_NRF52
