///|
pub(all) struct CompactionReport {
  low_watermark : Int
  removed_versions : Int
  retained_versions : Int
  removed_keys : Int
} derive(Debug, Eq)

///|
pub(all) struct ValidationIssue {
  code : String
  key : String
  message : String
} derive(Debug, Eq)

///|
pub fn Engine::oldest_active_snapshot(self : Engine) -> Int {
  let mut oldest = self.current_version
  let mut found = false
  for _, snapshot in self.active_snapshots {
    if !found || snapshot < oldest {
      oldest = snapshot
      found = true
    }
  }
  oldest
}

///|
pub fn Engine::compact(self : Engine) -> CompactionReport {
  let low_watermark = self.oldest_active_snapshot()
  let keys : Array[String] = []
  for key, _ in self.histories {
    keys.push(key)
  }
  let mut removed_versions = 0
  let mut retained_versions = 0
  let mut removed_keys = 0
  for key in keys {
    let history = match self.histories.get(key) {
      Some(value) => value
      None => continue
    }
    let mut anchor = -1
    for index, entry in history {
      if entry.version <= low_watermark {
        anchor = index
      }
    }
    let retained : Array[VersionedValue] = []
    if anchor >= 0 {
      retained.push(history[anchor])
    }
    for entry in history {
      if entry.version > low_watermark {
        retained.push(entry)
      }
    }
    removed_versions = removed_versions + history.length() - retained.length()
    if retained.length() == 1 &&
      retained[0].value is None &&
      self.active_snapshots.length() == 0 {
      ignore(self.histories.remove(key))
      removed_keys = removed_keys + 1
    } else {
      self.histories[key] = retained
      retained_versions = retained_versions + retained.length()
    }
  }
  { low_watermark, removed_versions, retained_versions, removed_keys }
}

///|
pub fn Engine::stats(self : Engine) -> EngineStats {
  let mut versions = 0
  for _, history in self.histories {
    versions = versions + history.length()
  }
  {
    current_version: self.current_version,
    keys: self.histories.length(),
    versions,
    active_transactions: self.active_snapshots.length(),
    committed_transactions: self.committed_transactions,
    aborted_transactions: self.aborted_transactions,
    wal_records: self.wal_records.length(),
  }
}

///|
pub fn Engine::validate(self : Engine) -> Array[ValidationIssue] {
  let issues : Array[ValidationIssue] = []
  for key, history in self.histories {
    let mut previous = 0
    for entry in history {
      if entry.version <= previous || entry.version > self.current_version {
        issues.push({
          code: "VERSION_ORDER",
          key,
          message: "history versions must increase and not exceed engine version",
        })
      }
      previous = entry.version
    }
  }
  let mut wal_version = 0
  for record in self.wal_records {
    if !record.is_valid() {
      issues.push({
        code: "WAL_CHECKSUM",
        key: "",
        message: "WAL record checksum is invalid",
      })
    }
    if record.commit_version <= wal_version {
      issues.push({
        code: "WAL_ORDER",
        key: "",
        message: "WAL commit versions must increase",
      })
    }
    wal_version = record.commit_version
  }
  issues
}

///|
pub fn CompactionReport::to_json(self : CompactionReport) -> String {
  "{\"low_watermark\":\{self.low_watermark},\"removed_versions\":\{self.removed_versions},\"retained_versions\":\{self.retained_versions},\"removed_keys\":\{self.removed_keys}}"
}

///|
pub fn EngineStats::to_json(self : EngineStats) -> String {
  "{\"current_version\":\{self.current_version},\"keys\":\{self.keys},\"versions\":\{self.versions},\"active_transactions\":\{self.active_transactions},\"committed_transactions\":\{self.committed_transactions},\"aborted_transactions\":\{self.aborted_transactions},\"wal_records\":\{self.wal_records}}"
}