// Log replay utilities — for testing and verification

///|
pub(all) struct LogReplay {
  records : Array[LogRecord]
  position : Int
}

///|
pub fn LogReplay::from_logger(logger : Logger) -> LogReplay {
  LogReplay::{ records: logger.records(), position: 0 }
}

///|
pub fn LogReplay::from_records(records : Array[LogRecord]) -> LogReplay {
  LogReplay::{ records: copy_replay_records(records, 0, []), position: 0 }
}

///|
fn copy_replay_records(
  records : Array[LogRecord],
  index : Int,
  result : Array[LogRecord],
) -> Array[LogRecord] {
  if index >= records.length() {
    result
  } else {
    result.push(records[index].copy())
    copy_replay_records(records, index + 1, result)
  }
}

///|
pub fn LogReplay::next(self : LogReplay) -> (LogReplay, LogRecord) {
  match self.next_record() {
    (next_replay, Some(record)) => (next_replay, record)
    (next_replay, None) =>
      (next_replay, LogRecord::new(Level::INFO, "EOF", [], 0))
  }
}

///|
pub fn LogReplay::next_record(self : LogReplay) -> (LogReplay, LogRecord?) {
  if self.position >= self.records.length() {
    (self, None)
  } else {
    let record = self.records[self.position].copy()
    (LogReplay::{ ..self, position: self.position + 1 }, Some(record))
  }
}

///|
pub fn LogReplay::peek(self : LogReplay) -> LogRecord? {
  if self.position >= self.records.length() {
    None
  } else {
    Some(self.records[self.position].copy())
  }
}

///|
pub fn LogReplay::has_more(self : LogReplay) -> Bool {
  self.position < self.records.length()
}

///|
pub fn LogReplay::total(self : LogReplay) -> Int {
  self.records.length()
}

///|
pub fn LogReplay::remaining(self : LogReplay) -> Int {
  let count = self.records.length() - self.position
  if count < 0 {
    0
  } else {
    count
  }
}

///|
pub fn LogReplay::reset(self : LogReplay) -> LogReplay {
  LogReplay::{ ..self, position: 0 }
}

///|
pub fn LogReplay::seek(self : LogReplay, position : Int) -> LogReplay {
  let target = if position < 0 {
    0
  } else if position > self.records.length() {
    self.records.length()
  } else {
    position
  }
  LogReplay::{ ..self, position: target }
}

///|
pub fn LogReplay::current_position(self : LogReplay) -> Int {
  self.position
}

///|
pub fn LogReplay::replay_into(
  self : LogReplay,
  logger : Logger,
) -> (LogReplay, Logger) {
  replay_remaining(self.records, self.position, logger)
}

///|
fn replay_remaining(
  records : Array[LogRecord],
  position : Int,
  logger : Logger,
) -> (LogReplay, Logger) {
  if position >= records.length() {
    (
      LogReplay::{ records: copy_replay_records(records, 0, []), position },
      logger,
    )
  } else {
    replay_remaining(
      records,
      position + 1,
      logger._append_record(records[position]),
    )
  }
}

// Log assertion utilities for testing

///|
pub fn assert_log_contains(logger : Logger, substring : String) -> Bool {
  log_contains_at(logger.logs(), substring, 0)
}

///|
fn log_contains_at(logs : Array[String], substring : String, idx : Int) -> Bool {
  if idx >= logs.length() {
    false
  } else if logs[idx].contains(substring) {
    true
  } else {
    log_contains_at(logs, substring, idx + 1)
  }
}

///|
pub fn assert_log_count(logger : Logger, expected : Int) -> Bool {
  logger.logs().length() == expected
}

///|
pub fn assert_log_level_count(logger : Logger, level : Level) -> Int {
  count_by_level(logger.records(), level, 0, 0)
}

///|
fn count_by_level(
  records : Array[LogRecord],
  level : Level,
  idx : Int,
  count : Int,
) -> Int {
  if idx >= records.length() {
    count
  } else {
    let increment = if level_ordinal(records[idx].level) >= level_ordinal(level) {
      1
    } else {
      0
    }
    count_by_level(records, level, idx + 1, count + increment)
  }
}