// ============================================================================
// subghz_scanner.cpp — see subghz_scanner.h for design notes and honest
// validation caveat (no real ELRS/Crossfire transmitter available to test
// against).
// ============================================================================

#include "subghz_scanner.h"

// ---------------------------------------------------------------------------
// Pure plausibility check — no radio access, always compiled (native and
// ARDUINO alike) so it's directly unit-testable without a real SX1262.
//
// FIXED (2026-07-30): this band was 20-600 Hz, sourced from only the
// 900MHz LoRa rates (50/150/250/500 Hz). Cross-checked against
// ExpressLRS's own docs (expresslrs.org/info/signal-health/) and TBS
// Crossfire's real-world reverse-engineering (g3gg0.de measured 150 Hz /
// 6.666ms on real captured Crossfire traffic, matching the documented
// rate) while testing this module against real, sourced reference data
// instead of just the values that happened to be in the original comment:
// the 900MHz band this sensor's SX1262 actually covers goes up to
// **1000 Hz FSK** (25/50/100/200/250/1000 Hz), not 500 Hz -- the old
// 600 Hz ceiling would have silently rejected any real ELRS control link
// running at its fastest 900MHz rate as "implausible," exactly the kind
// of link an operator most needs flagged (fastest rate = closest/most
// active flying). Widened with the same ~20% margin logic the original MIN
// already used (20 Hz sits modestly below the real 25 Hz floor).
// rf_cataloger.cpp's RF_CATALOG_CONTROL_LINK_MIN_HZ/MAX_HZ deliberately
// mirror these same bounds -- keep both in sync if either changes.
// ---------------------------------------------------------------------------
#define SUBGHZ_MIN_PLAUSIBLE_HZ      20.0f
#define SUBGHZ_MAX_PLAUSIBLE_HZ    1200.0f

bool subghz_is_plausible_rate(float rate_hz) {
    return (rate_hz >= SUBGHZ_MIN_PLAUSIBLE_HZ && rate_hz <= SUBGHZ_MAX_PLAUSIBLE_HZ);
}

#ifdef ARDUINO

#include <Arduino.h>
#include <cstring>
#include "mesh_publish.h"

#define SUBGHZ_SCAN_INTERVAL_MS   60000u
#define SUBGHZ_DWELL_MS             300u
#define SUBGHZ_MAX_FINDINGS           8
#define SUBGHZ_MESH_FREQ_MHZ        915.0f

// Real channel ranges pulled directly from ExpressLRS's own FHSS.cpp
// (github.com/ExpressLRS/ExpressLRS/blob/master/src/lib/FHSS/FHSS.cpp),
// not guessed. FCC915 (US regulatory domain, most relevant for a US
// deployment) spans 903.5-926.9 MHz across 40 channels; US433/US433W
// spans 423.5-438 MHz. Sampling representative points spread across each
// real band rather than all 40 channels individually (bounds total scan
// time -- see SUBGHZ_DWELL_MS). EU868 (863.275-869.575 MHz) included too
// in case a EU-configured unit is nearby.
struct CandidateFreq { float mhz; };
static const CandidateFreq kCandidates[] = {
    {905.0f}, {911.0f}, {917.0f}, {923.0f}, {926.5f},   // FCC915 span
    {426.0f}, {430.0f}, {435.0f},                        // US433/US433W span
    {864.0f}, {867.0f},                                  // EU868 span
};
#define N_CANDIDATES (sizeof(kCandidates) / sizeof(kCandidates[0]))

static uint32_t s_last_scan_ms = 0;
static SubGhzScannerStats s_stats{};
static SubGhzFinding s_findings[SUBGHZ_MAX_FINDINGS];
static size_t s_finding_count = 0;

void subghz_scanner_init() {
    s_stats = SubGhzScannerStats{};
    s_finding_count = 0;
    s_last_scan_ms = millis();
}

// Scans one candidate frequency for SUBGHZ_DWELL_MS, polling live RSSI as
// fast as the SPI bus allows. Returns true and fills *out_rate/*out_peak
// if a plausible burst-rate pattern was observed.
static bool scan_one_frequency(SX1262& radio, float freq_mhz,
                                float* out_rate, int16_t* out_peak) {
    radio.setFrequency(freq_mhz);
    radio.startReceive();
    delay(5);   // let the front end settle after retuning

    // Baseline from the first ~20% of the dwell (assume mostly noise floor).
    uint32_t t_start = millis();
    uint32_t baseline_end = t_start + (SUBGHZ_DWELL_MS / 5);
    float baseline_sum = 0.0f;
    uint32_t baseline_n = 0;
    while (millis() < baseline_end) {
        baseline_sum += radio.getRSSI(false);
        baseline_n++;
    }
    float baseline = (baseline_n > 0) ? (baseline_sum / baseline_n) : -110.0f;
    float threshold = baseline + 6.0f;   // 6dB above local noise floor

    uint32_t dwell_end = t_start + SUBGHZ_DWELL_MS;
    bool above = false;
    uint32_t crossings = 0;
    int16_t peak = -128;
    while (millis() < dwell_end) {
        float rssi = radio.getRSSI(false);
        if (rssi > peak) peak = (int16_t)rssi;
        bool now_above = rssi > threshold;
        if (now_above && !above) crossings++;   // rising edge
        above = now_above;
    }

    float dwell_s = (float)SUBGHZ_DWELL_MS / 1000.0f;
    float rate_hz = (float)crossings / dwell_s;

    *out_rate = rate_hz;
    *out_peak = peak;
    return subghz_is_plausible_rate(rate_hz);
}

void subghz_scanner_tick() {
    uint32_t now = millis();
    if (now - s_last_scan_ms < SUBGHZ_SCAN_INTERVAL_MS) return;
    s_last_scan_ms = now;

    SX1262& radio = mesh_get_radio();
    s_finding_count = 0;

    for (size_t i = 0; i < N_CANDIDATES; i++) {
        float rate = 0.0f;
        int16_t peak = -128;
        bool plausible = scan_one_frequency(radio, kCandidates[i].mhz, &rate, &peak);
        if (plausible && s_finding_count < SUBGHZ_MAX_FINDINGS) {
            s_findings[s_finding_count].freq_mhz = kCandidates[i].mhz;
            s_findings[s_finding_count].estimated_rate_hz = rate;
            s_findings[s_finding_count].peak_rssi_dbm = peak;
            s_finding_count++;
        }
    }

    // Always restore the mesh's own frequency and resume RX -- never
    // leave the shared radio parked off-mesh-frequency.
    radio.setFrequency(SUBGHZ_MESH_FREQ_MHZ);
    mesh_start_receive();

    s_stats.scan_cycles_completed++;
    s_stats.findings_last_cycle = (uint16_t)s_finding_count;
}

void subghz_scanner_get_stats(SubGhzScannerStats* out) {
    if (out) *out = s_stats;
}

size_t subghz_scanner_list_findings(SubGhzFinding* out, size_t max) {
    if (!out) return 0;
    size_t n = s_finding_count < max ? s_finding_count : max;
    memcpy(out, s_findings, n * sizeof(SubGhzFinding));
    return n;
}

#else  // !ARDUINO — native/test stub

void subghz_scanner_init() {}
void subghz_scanner_tick() {}
void subghz_scanner_get_stats(SubGhzScannerStats* out) {
    if (out) { SubGhzScannerStats z{}; *out = z; }
}
size_t subghz_scanner_list_findings(SubGhzFinding*, size_t) { return 0; }

#endif
