// AutoResearchTest.h - Generic LED autoresearch testing infrastructure
// Driver-agnostic test functions that work with any FastLED driver (RMT, SPI, PARLIO)

#pragma once

#include <FastLED.h>
#include "fl/channels/validation.h"

namespace fl {

/// @brief Configuration for driver-agnostic autoresearch testing
/// Contains all input parameters needed for autoresearch (excludes output parameters)
struct AutoResearchConfig {
    const fl::ChipsetTimingConfig& timing;       ///< Chipset timing configuration to test
    fl::ClocklessEncoder encoder;                ///< Encoder selector (peer of timing; see issue #2467)
    const char* timing_name;                     ///< Timing name for logging (e.g., "WS2812B-V5")
    fl::span<fl::ChannelConfig> tx_configs;      ///< TX channel configurations to test (mutable for LED manipulation)
    const char* driver_name;                     ///< Driver name for logging (e.g., "RMT", "SPI", "PARLIO")
    fl::shared_ptr<fl::RxChannel> rx_channel;    ///< RX channel for loopback capture (created in .ino, passed in)
    fl::span<uint8_t> rx_buffer;                 ///< Buffer to store received bytes
    int base_strip_size;                         ///< Base strip size (10 or 300 LEDs)
    fl::RxDeviceType rx_type;                    ///< RX device type (RMT or ISR)

    AutoResearchConfig(const fl::ChipsetTimingConfig& t,
                     const char* tn,
                     fl::span<fl::ChannelConfig> tc,
                     const char* dn,
                     fl::shared_ptr<fl::RxChannel> rc,
                     fl::span<uint8_t> rb,
                     int bss,
                     fl::RxDeviceType rt,
                     fl::ClocklessEncoder enc = fl::ClocklessEncoder::CLOCKLESS_ENCODER_WS2812)
        : timing(t)
        , encoder(enc)
        , timing_name(tn)
        , tx_configs(tc)
        , driver_name(dn)
        , rx_channel(rc)
        , rx_buffer(rb)
        , base_strip_size(bss)
        , rx_type(rt) {}
};

/// @brief Test context for detailed error reporting
/// Aggregates all test configuration parameters for error messages
struct TestContext {
    const char* driver_name;      ///< Driver name (e.g., "RMT", "SPI", "PARLIO")
    const char* timing_name;      ///< Timing name (e.g., "WS2812B-V5")
    const char* rx_type_name;     ///< RX device type name (e.g., "RMT", "ISR")
    const char* pattern_name;     ///< Pattern name (e.g., "Pattern A (R=0xF0...)")
    int lane_count;               ///< Total number of lanes (1-16)
    int lane_index;               ///< Current lane index (0-15)
    int base_strip_size;          ///< Base strip size (10 or 300 LEDs)
    int num_leds;                 ///< Number of LEDs in this lane
    int pin_number;               ///< TX pin number for this lane
};

/// @brief LED error information for a single run
struct LEDError {
    int led_index;          ///< LED index where error occurred
    uint8_t expected_r;     ///< Expected R value
    uint8_t expected_g;     ///< Expected G value
    uint8_t expected_b;     ///< Expected B value
    uint8_t actual_r;       ///< Actual R value
    uint8_t actual_g;       ///< Actual G value
    uint8_t actual_b;       ///< Actual B value

    LEDError(int idx, uint8_t exp_r, uint8_t exp_g, uint8_t exp_b,
             uint8_t act_r, uint8_t act_g, uint8_t act_b)
        : led_index(idx)
        , expected_r(exp_r), expected_g(exp_g), expected_b(exp_b)
        , actual_r(act_r), actual_g(act_g), actual_b(act_b) {}
};

/// @brief Single run result with error tracking
struct RunResult {
    int run_number;                 ///< Run iteration number (1-based)
    int total_leds;                 ///< Total LEDs tested
    int mismatches;                 ///< Number of LED mismatches
    int totalBytes;                 ///< Total bytes compared (num_leds * 3)
    int mismatchedBytes;            ///< Number of individual bytes that differ
    int lsbOnlyErrors;              ///< Bytes where (expected ^ actual) == 0x01
    fl::vector<LEDError> errors;    ///< First few errors (up to 10)
    bool passed;                    ///< True if no errors

    RunResult() : run_number(0), total_leds(0), mismatches(0),
                  totalBytes(0), mismatchedBytes(0), lsbOnlyErrors(0),
                  passed(false) {}
};

/// @brief Multi-run test configuration
struct MultiRunConfig {
    int num_runs;               ///< Number of runs to execute
    bool print_all_runs;        ///< Print all run results (default: only errors)
    bool print_per_led_errors;  ///< Print every LED error (default: false)
    int max_errors_per_run;     ///< Max errors to store per run (default: 5)

    MultiRunConfig()
        : num_runs(10)
        , print_all_runs(false)
        , print_per_led_errors(false)
        , max_errors_per_run(5) {}
};

// DriverTestResult is defined in fl/channels/validation.h (canonical definition)

} // namespace fl

// Capture transmitted LED data via RX loopback
// - rx_channel: Shared pointer to RX device (persistent across calls)
// - rx_buffer: Buffer to store received bytes
// - timing: Chipset timing configuration for RX decoder
// - driver_name: Name of the TX driver being tested (e.g., "RMT", "PARLIO") - enables io_loop_back only for RMT
// Returns number of bytes captured, or 0 on error
size_t capture(fl::shared_ptr<fl::RxChannel> rx_channel, fl::span<uint8_t> rx_buffer, const fl::ChipsetTimingConfig& timing, const char* driver_name);

// Generic driver-agnostic autoresearch test runner (single run)
// Tests all channels using the specified configuration
// @param test_name Test name for logging (e.g., "Solid Red", "Solid Green")
// @param config All autoresearch configuration (timing, channels, driver, RX, buffer) - non-const for LED manipulation
// @param total Output parameter - total tests run (incremented)
// @param passed Output parameter - tests passed (incremented)
void runTest(const char* test_name,
             fl::AutoResearchConfig& config,
             int& total, int& passed);

// Multi-run autoresearch test runner
// Runs the same test multiple times and tracks errors across runs
// @param test_name Test name for logging (e.g., "Pattern A", "Pattern B")
// @param config All autoresearch configuration (timing, channels, driver, RX, buffer) - non-const for LED manipulation
// @param multi_config Multi-run configuration (num runs, print settings)
// @param total Output parameter - total tests run (incremented)
// @param passed Output parameter - tests passed (incremented)
// @param out_results Optional output for per-run results with LED error details
void runMultiTest(const char* test_name,
                  fl::AutoResearchConfig& config,
                  const fl::MultiRunConfig& multi_config,
                  int& total, int& passed,
                  fl::vector<fl::RunResult>* out_results = nullptr);

// AutoResearch a specific chipset timing configuration
// Creates channels, runs tests, destroys channels
// @param config All autoresearch configuration (timing, channels, driver, RX, buffer) - non-const for LED manipulation
// @param total Output parameter - total tests run (incremented)
// @param passed Output parameter - tests passed (incremented)
// @param out_results Optional output for per-pattern results with LED error details
// @param num_runs_per_pattern Consecutive captures per pattern (default 1). >=2 exposes
//        second-frame degradation bugs like SPI driver zeroing DMA buffer after first show().
void autoResearchChipsetTiming(fl::AutoResearchConfig& config,
                           int& total, int& passed,
                           uint32_t& out_show_duration_ms,
                           fl::vector<fl::RunResult>* out_results = nullptr,
                           int num_runs_per_pattern = 1);

// AutoResearch using the legacy template addLeds API (supports multi-lane)
// Uses LegacyClocklessProxy to map runtime pin to WS2812B<PIN> template instantiation
// @param config All autoresearch configuration (one or more tx_configs, pins must be 0-8)
// @param total Output parameter - total tests run (incremented)
// @param passed Output parameter - tests passed (incremented)
// @param out_results Optional output for per-pattern results with LED error details
// @param num_runs_per_pattern Consecutive captures per pattern (default 1).
void autoResearchChipsetTimingLegacy(fl::AutoResearchConfig& config,
                                 int& total, int& passed,
                                 uint32_t& out_show_duration_ms,
                                 fl::vector<fl::RunResult>* out_results = nullptr,
                                 int num_runs_per_pattern = 1);

// Set mixed RGB bit patterns to test MSB vs LSB handling
// @param leds LED array to fill with pattern
// @param count Number of LEDs in array
// @param pattern_id Pattern identifier (0-3):
//   0 = Pattern A: R=0xF0, G=0x0F, B=0xAA (high nibble, low nibble, alternating)
//   1 = Pattern B: R=0x55, G=0xFF, B=0x00 (alternating, all-high, all-low)
//   2 = Pattern C: R=0x0F, G=0xAA, B=0xF0 (rotated nibbles)
//   3 = Pattern D: Solid colors (Red/Green/Blue alternating every 3 LEDs)
void setMixedBitPattern(CRGB* leds, size_t count, int pattern_id);

// Get name of bit pattern for logging
// @param pattern_id Pattern identifier (0-3)
// @return Pattern name string
const char* getBitPatternName(int pattern_id);
