///|
fn finding(
  observation : Observation,
  rule : RuleKind,
  flag : QualityFlag,
  severity : Severity,
  reason : String,
  config : RuleConfig,
) -> Finding {
  {
    observation_id: observation.id,
    rule,
    flag,
    severity,
    reason,
    original_value: observation.value,
    processed_at: config.processing_time,
    provenance: {
      source: "moonbit-ocean-qc",
      dataset: observation.platform,
      observed_at: observation.timestamp,
      processor: "lwq443",
      version: "0.2.0",
    },
  }
}

///|
fn check_missing(observation : Observation, config : RuleConfig) -> Finding? {
  match observation.value {
    None =>
      Some(
        finding(observation, Missing, Missing, Error, "value is absent", config),
      )
    Some(_) => None
  }
}

///|
fn check_range(observation : Observation, config : RuleConfig) -> Finding? {
  match observation.value {
    None => None
    Some(value) => {
      let below = match config.range_min {
        Some(x) => value < x
        None => false
      }
      let above = match config.range_max {
        Some(x) => value > x
        None => false
      }
      if below || above {
        Some(
          finding(
            observation,
            Range,
            Bad,
            Error,
            "outside configured physical range",
            config,
          ),
        )
      } else {
        None
      }
    }
  }
}

///|
fn check_climate(observation : Observation, config : RuleConfig) -> Finding? {
  match observation.value {
    None => None
    Some(value) => {
      let below = match config.climate_min {
        Some(x) => value < x
        None => false
      }
      let above = match config.climate_max {
        Some(x) => value > x
        None => false
      }
      if below || above {
        Some(
          finding(
            observation,
            ClimateAnomaly,
            Suspect,
            Warning,
            "outside configured climate envelope",
            config,
          ),
        )
      } else {
        None
      }
    }
  }
}

///|
fn check_delta(
  previous : Observation,
  current : Observation,
  config : RuleConfig,
) -> Finding? {
  match (previous.value, current.value, config.max_delta) {
    (Some(a), Some(b), Some(limit)) =>
      if (b - a).abs() > limit {
        Some(
          finding(
            current,
            Spike,
            Suspect,
            Warning,
            "successive values changed too quickly",
            config,
          ),
        )
      } else {
        None
      }
    _ => None
  }
}

///|
fn check_interval(
  previous : Observation,
  current : Observation,
  config : RuleConfig,
) -> Finding? {
  match config.max_interval {
    Some(limit) =>
      if current.timestamp - previous.timestamp > limit {
        Some(
          finding(
            current,
            Interval,
            Suspect,
            Warning,
            "sampling interval exceeds limit",
            config,
          ),
        )
      } else {
        None
      }
    None => None
  }
}

///|
fn check_spatial(
  previous : Observation,
  current : Observation,
  config : RuleConfig,
) -> Finding? {
  match config.max_speed {
    None => None
    Some(speed) => {
      let dt = current.timestamp - previous.timestamp
      if dt <= 0 {
        None
      } else {
        let lat_delta = current.latitude - previous.latitude
        let lon_delta = current.longitude - previous.longitude
        let distance = (lat_delta * lat_delta + lon_delta * lon_delta).sqrt()
        if distance / dt.to_double() > speed {
          Some(
            finding(
              current,
              SpatialJump,
              Suspect,
              Warning,
              "platform moved faster than configured speed",
              config,
            ),
          )
        } else {
          None
        }
      }
    }
  }
}

///|
fn check_duplicate(
  previous : Observation,
  current : Observation,
  config : RuleConfig,
) -> Finding? {
  if config.duplicate_window > 0 &&
    current.timestamp - previous.timestamp <= config.duplicate_window &&
    previous.variable == current.variable &&
    previous.value == current.value {
    Some(
      finding(
        current,
        Duplicate,
        Suspect,
        Warning,
        "duplicate observation within time window",
        config,
      ),
    )
  } else {
    None
  }
}