///|
/// Timebase contract for MCU + Linux cooperative HIL systems.
///
/// This module defines the clock discipline interface between:
///   - A hard-real-time MCU domain (RZ/N2L, HPM6E00, etc.)
///   - A soft-real-time Linux domain (ARM Cortex-A, x86, etc.)
///   - An optional FPGA/PRU domain (absolute-precision event generator)
///
/// Design principles:
///   - Each clock domain is an independent Timebase implementation
///   - Cross-domain synchronization uses explicit epoch + drift measurement
///   - No implicit shared mutable state between domains
///   - Lock-free event passing at domain boundaries
///
/// Clock hierarchy:
///   FPGA/PRU (if present)  →  Reference timebase
///   MCU (EtherCAT master)  →  Primary execution timebase
///   Linux (supervisor)     →  Observation/logging timebase

///|
/// ClockDomain identifies the source of a timestamp.
pub(all) enum ClockDomain {
  /// Hard-real-time MCU domain (TCM/ILM, sub-microsecond jitter).
  Mcu
  /// Soft-real-time Linux domain (kernel timer, microsecond-class jitter).
  Linux
  /// FPGA/PRU domain (cycle-exact, no jitter).
  Fpga
  /// Simulated/virtual domain (deterministic replay).
  Virtual
} derive(Eq, Debug)

///|
/// DomainTimestamp pairs a raw nanosecond value with its clock domain.
pub(all) struct DomainTimestamp {
  domain : ClockDomain
  ns : Int64
} derive(Eq, Debug)

///|
/// Timebase — the trait each clock domain implements.
///
/// All methods are non-blocking and must execute in bounded time.
/// MCU implementations typically read a hardware timer register.
/// Linux implementations use clock_gettime(CLOCK_MONOTONIC).
/// Virtual implementations advance from an explicit schedule.
pub(open) trait Timebase {
  /// Return the clock domain identifier.
  fn domain(Self) -> ClockDomain

  /// Read the current time in this domain.
  fn now_ns(Self) -> Int64

  /// Return the timer resolution in nanoseconds.
  /// MCU: typically 1-10 ns. Linux: typically 1000 ns.
  fn resolution_ns(Self) -> Int64

  /// Monotonicity guarantee: returns true if this timebase never goes backward.
  fn is_monotonic(Self) -> Bool
}

///|
/// TimebaseSync captures the synchronization state between two clock domains.
pub(all) struct TimebaseSync {
  /// The reference (master) clock domain.
  reference_domain : ClockDomain
  /// The local (slave) clock domain.
  local_domain : ClockDomain
  /// Measured offset: local_ns = reference_ns + offset_ns.
  mut offset_ns : Int64
  /// Estimated drift rate in ppb (parts per billion).
  /// Positive = local runs faster than reference.
  mut drift_ppb : Int64
  /// Number of synchronization samples taken.
  mut sync_count : Int64
  /// Nanosecond timestamp of last sync measurement (in reference domain).
  mut last_sync_ns : Int64
} derive(Eq, Debug)

///|
pub fn TimebaseSync::new(
  reference_domain : ClockDomain,
  local_domain : ClockDomain,
) -> TimebaseSync {
  {
    reference_domain,
    local_domain,
    offset_ns: 0L,
    drift_ppb: 0L,
    sync_count: 0L,
    last_sync_ns: 0L,
  }
}

///|
/// Update the sync state with a new measurement pair.
pub fn TimebaseSync::update(
  self : TimebaseSync,
  reference_ns : Int64,
  local_ns : Int64,
) -> Unit {
  let new_offset = local_ns - reference_ns
  if self.sync_count > 0L && self.last_sync_ns > 0L {
    let dt = reference_ns - self.last_sync_ns
    if dt > 0L {
      let offset_change = new_offset - self.offset_ns
      // drift_ppb = (offset_change / dt) * 1e9
      self.drift_ppb = offset_change * 1_000_000_000L / dt
    }
  }
  self.offset_ns = new_offset
  self.last_sync_ns = reference_ns
  self.sync_count = self.sync_count + 1L
}

///|
/// Convert a reference-domain timestamp to the local domain.
pub fn TimebaseSync::to_local(
  self : TimebaseSync,
  reference_ns : Int64,
) -> Int64 {
  reference_ns + self.offset_ns
}

///|
/// Convert a local-domain timestamp to the reference domain.
pub fn TimebaseSync::to_reference(
  self : TimebaseSync,
  local_ns : Int64,
) -> Int64 {
  local_ns - self.offset_ns
}

///|
/// VirtualTimebase — deterministic timebase for testing and replay.
/// Time only advances when explicitly stepped.
pub struct VirtualTimebase {
  mut current_ns : Int64
}

///|
pub fn VirtualTimebase::new() -> VirtualTimebase {
  { current_ns: 0L }
}

///|
pub fn VirtualTimebase::set(self : VirtualTimebase, ns : Int64) -> Unit {
  self.current_ns = ns
}

///|
pub fn VirtualTimebase::advance(
  self : VirtualTimebase,
  delta_ns : Int64,
) -> Unit {
  self.current_ns = self.current_ns + delta_ns
}

///|
pub impl Timebase for VirtualTimebase with fn domain(self) {
  ignore(self)
  Virtual
}

///|
pub impl Timebase for VirtualTimebase with fn now_ns(self) {
  self.current_ns
}

///|
pub impl Timebase for VirtualTimebase with fn resolution_ns(self) {
  ignore(self)
  1L
}

///|
pub impl Timebase for VirtualTimebase with fn is_monotonic(self) {
  ignore(self)
  true
}

///|
/// CycleTimingReport captures per-cycle timing quality metrics.
pub(all) struct CycleTimingReport {
  /// Nominal cycle period in nanoseconds.
  nominal_period_ns : Int64
  /// Measured cycle period in nanoseconds (wall-clock delta).
  actual_period_ns : Int64
  /// Jitter = |actual - nominal| in nanoseconds.
  jitter_ns : Int64
  /// Which clock domain produced the measurement.
  domain : ClockDomain
} derive(Eq, Debug)

///|
/// Compute a CycleTimingReport from two consecutive timestamps.
pub fn cycle_timing_report(
  nominal_period_ns : Int64,
  prev_ns : Int64,
  curr_ns : Int64,
  domain : ClockDomain,
) -> CycleTimingReport {
  let actual = curr_ns - prev_ns
  let jitter = if actual > nominal_period_ns {
    actual - nominal_period_ns
  } else {
    nominal_period_ns - actual
  }
  { nominal_period_ns, actual_period_ns: actual, jitter_ns: jitter, domain }
}