///|
pub(all) struct LagPoint {
  lag : Int32
  recurrent : Int32
  eligible : Int32
  rate : Double?
} derive(Debug, Eq)

///|
pub extend LagPoint with @debug.Debug::{to_repr}

///|
pub extend LagPoint with Eq::{equal, not_equal}

///|
/// Positive lag compares A[i] against B[i+lag]; no clock alignment is inferred.
pub fn Recurrence::lag_profile(
  self : Recurrence,
  minimum : Int32,
  maximum : Int32,
) -> Result[Array[LagPoint], RqaError] {
  if minimum > maximum ||
    minimum < -4096 ||
    maximum > 4096 ||
    maximum - minimum > 8192 {
    return Err(Invalid("lag profile range"))
  }
  let out : Array[LagPoint] = []
  for lag = minimum; lag <= maximum; lag = lag + 1 {
    let mut recurrent = 0
    let mut eligible = 0
    for i in 0..= 0 && j < self.width {
        let p = i * self.width + j
        if self.mask[p] {
          eligible += 1
          if self.bits[p] {
            recurrent += 1
          }
        }
      }
    }
    out.push({
      lag,
      recurrent,
      eligible,
      rate: if eligible == 0 {
        None
      } else {
        Some(recurrent.to_double() / eligible.to_double())
      },
    })
  }
  Ok(out)
}

///|
/// None if every requested lag has no eligible pairs. Ties prefer greater support,
/// then smaller absolute lag, then the smaller signed lag.
pub fn best_lag(profile : Array[LagPoint]) -> Int32? {
  let mut best : LagPoint? = None
  for p in profile {
    if p.lag < -4096 ||
      p.lag > 4096 ||
      p.eligible < 1 ||
      p.eligible > 4096 ||
      p.recurrent < 0 ||
      p.recurrent > p.eligible {
      continue
    }
    guard p.rate is Some(rate) else { continue }
    if !finite(rate) ||
      (rate - p.recurrent.to_double() / p.eligible.to_double()).abs() > 1.0e-12 {
      continue
    }
    match best {
      None => best = Some(p)
      Some(old) => {
        let previous = old.rate.unwrap()
        if rate > previous ||
          (
            rate == previous &&
            (
              p.eligible > old.eligible ||
              (
                p.eligible == old.eligible &&
                (
                  p.lag.abs() < old.lag.abs() ||
                  (p.lag.abs() == old.lag.abs() && p.lag < old.lag)
                )
              )
            )
          ) {
          best = Some(p)
        }
      }
    }
  }
  best.map(fn(x) { x.lag })
}