// ============================================================================
// mesh_timesync.cpp — Wall-clock sync via T2 leader broadcast
// ============================================================================

#include "mesh_timesync.h"
#include "config.h"   // MSG_TYPE_TIME_SYNC defined in integration notes / config

// ---------------------------------------------------------------------------
// Platform shims
// ---------------------------------------------------------------------------
#ifdef ARDUINO
#   include <Arduino.h>
static inline uint32_t ts_millis() { return millis(); }
static inline void ts_log(const char* msg) { Serial.print(msg); }
#else
// Native/unit-test stub
#   include <cstdio>
static uint32_t g_ts_stub_millis = 0;
static inline uint32_t ts_millis() { return g_ts_stub_millis; }
static inline void ts_log(const char*) {}

// Allow test harness to advance the stub clock
void timesync_stub_set_millis(uint32_t ms) { g_ts_stub_millis = ms; }
#endif

// ---------------------------------------------------------------------------
// MSG_TYPE_TIME_SYNC wire byte — may already be defined in config.h when the
// integrator adds it; guard to avoid redefinition.
// ---------------------------------------------------------------------------
#ifndef MSG_TYPE_TIME_SYNC
#   define MSG_TYPE_TIME_SYNC  0x19
#endif

// Minimum packet length: 1 (type) + 8 (epoch_us) = 9 bytes
#define TIMESYNC_MIN_PACKET_LEN  9

// ---------------------------------------------------------------------------
// Module state — static, no heap allocation
// ---------------------------------------------------------------------------
static float    s_offset_ms      = 0.0f;   // smoothed clock_offset_ms
static uint8_t  s_packet_count   = 0;      // successful packet count
static bool     s_synced         = false;

// ---------------------------------------------------------------------------
void timesync_init() {
    s_offset_ms    = 0.0f;
    s_packet_count = 0;
    s_synced       = false;
}

// ---------------------------------------------------------------------------
void timesync_handle_packet(const uint8_t* buf, size_t len) {
    if (!buf || len < TIMESYNC_MIN_PACKET_LEN) return;
    if (buf[0] != MSG_TYPE_TIME_SYNC) return;

    // Decode epoch_us from bytes [1..8] (little-endian uint64_t)
    uint64_t epoch_us = 0;
    for (int i = 0; i < 8; i++) {
        epoch_us |= ((uint64_t)buf[1 + i]) << (8 * i);
    }

    uint64_t epoch_ms = epoch_us / 1000ULL;

    // clock_offset_ms = how far our millis() clock is behind real UTC
    // (positive means millis() is behind; negative means ahead)
    float raw_offset = (float)((int64_t)epoch_ms - (int64_t)ts_millis());

    // Single-pole IIR: s_offset = alpha * raw + (1 - alpha) * s_offset
    if (s_packet_count == 0) {
        // Bootstrap: accept first sample as-is
        s_offset_ms = raw_offset;
    } else {
        s_offset_ms = TIMESYNC_IIR_ALPHA * raw_offset
                    + (1.0f - TIMESYNC_IIR_ALPHA) * s_offset_ms;
    }

    s_packet_count++;
    if (s_packet_count >= TIMESYNC_MIN_PACKETS) {
        s_synced = true;
    }
}

// ---------------------------------------------------------------------------
uint64_t timesync_now_utc_ms() {
    if (!s_synced) return 0ULL;
    int64_t now = (int64_t)ts_millis() + (int64_t)s_offset_ms;
    if (now < 0) now = 0;
    return (uint64_t)now;
}

// ---------------------------------------------------------------------------
bool timesync_is_synced() {
    return s_synced;
}

// ---------------------------------------------------------------------------
uint32_t timesync_resolve_report_ts(uint32_t evt_timestamp_utc) {
    // Already a real epoch (e.g. a relayed peer event stamped by its origin).
    if (evt_timestamp_utc > TIMESYNC_PLAUSIBLE_EPOCH) return evt_timestamp_utc;

    // Mesh broadcast or NTP (ntp_timesync feeds this same module).
    if (s_synced) {
        uint64_t ms = timesync_now_utc_ms();
        if (ms / 1000ULL > TIMESYNC_PLAUSIBLE_EPOCH) return (uint32_t)(ms / 1000ULL);
    }

#ifdef ARDUINO
    // GPS as the last real source — a unit with a fix but no mesh/NTP sync.
    extern uint32_t gps_get_utc_epoch();
    uint32_t g = gps_get_utc_epoch();
    if (g > TIMESYNC_PLAUSIBLE_EPOCH) return g;
#endif

    // Nothing better available. Returning the raw value keeps behaviour
    // identical to before rather than inventing a timestamp.
    return evt_timestamp_utc;
}
