///|
pub(all) struct ConsoleAppender {
  use_json : Bool
}

///|
pub fn ConsoleAppender::new() -> ConsoleAppender {
  ConsoleAppender::{ use_json: false }
}

///|
pub fn ConsoleAppender::json(_self : ConsoleAppender) -> ConsoleAppender {
  ConsoleAppender::{ use_json: true }
}

///|
pub fn ConsoleAppender::append(
  self : ConsoleAppender,
  record : LogRecord,
) -> String {
  if self.use_json {
    format_json(record)
  } else {
    format_text(record)
  }
}

///|
pub(all) struct MemoryAppender {
  records : Array[String]
  entries : Array[LogRecord]
}

///|
pub fn MemoryAppender::new() -> MemoryAppender {
  MemoryAppender::{ records: [], entries: [] }
}

///|
pub fn MemoryAppender::append(
  self : MemoryAppender,
  record : LogRecord,
) -> MemoryAppender {
  let new_records = self.records.copy()
  new_records.push(format_text(record))
  let new_entries = self.entries.copy()
  new_entries.push(record.copy())
  MemoryAppender::{ records: new_records, entries: new_entries }
}

///|
pub fn MemoryAppender::get_logs(self : MemoryAppender) -> Array[String] {
  self.records.copy()
}

///|
pub fn MemoryAppender::get_records(self : MemoryAppender) -> Array[LogRecord] {
  copy_records(self.entries, 0, [])
}

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

// Rolling Appender — keeps only the last N records

///|
pub(all) struct RollingAppender {
  records : Array[String]
  entries : Array[LogRecord]
  max_count : Int
  dropped_count : Int
}

///|
pub fn RollingAppender::new(max_count : Int) -> RollingAppender {
  let capacity = if max_count < 0 { 0 } else { max_count }
  RollingAppender::{
    records: [],
    entries: [],
    max_count: capacity,
    dropped_count: 0,
  }
}

///|
pub fn RollingAppender::append(
  self : RollingAppender,
  record : LogRecord,
) -> RollingAppender {
  let appended_logs = self.records.copy()
  appended_logs.push(format_text(record))
  let appended_entries = self.entries.copy()
  appended_entries.push(record.copy())
  let discarded = if appended_logs.length() > self.max_count {
    appended_logs.length() - self.max_count
  } else {
    0
  }
  RollingAppender::{
    records: keep_last_logs(appended_logs, self.max_count),
    entries: keep_last_records(appended_entries, self.max_count),
    max_count: self.max_count,
    dropped_count: self.dropped_count + discarded,
  }
}

///|
fn keep_last_logs(logs : Array[String], max_count : Int) -> Array[String] {
  if max_count <= 0 {
    []
  } else {
    let start = if logs.length() > max_count {
      logs.length() - max_count
    } else {
      0
    }
    copy_logs(logs, start, [])
  }
}

///|
fn copy_logs(
  src : Array[String],
  idx : Int,
  acc : Array[String],
) -> Array[String] {
  if idx >= src.length() {
    acc
  } else {
    acc.push(src[idx])
    copy_logs(src, idx + 1, acc)
  }
}

///|
fn keep_last_records(
  records : Array[LogRecord],
  max_count : Int,
) -> Array[LogRecord] {
  if max_count <= 0 {
    []
  } else {
    let start = if records.length() > max_count {
      records.length() - max_count
    } else {
      0
    }
    copy_records(records, start, [])
  }
}

///|
fn copy_records(
  src : Array[LogRecord],
  idx : Int,
  acc : Array[LogRecord],
) -> Array[LogRecord] {
  if idx >= src.length() {
    acc
  } else {
    acc.push(src[idx].copy())
    copy_records(src, idx + 1, acc)
  }
}

///|
pub fn RollingAppender::get_logs(self : RollingAppender) -> Array[String] {
  self.records.copy()
}

///|
pub fn RollingAppender::get_records(self : RollingAppender) -> Array[LogRecord] {
  copy_records(self.entries, 0, [])
}

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

///|
pub fn RollingAppender::capacity(self : RollingAppender) -> Int {
  self.max_count
}

///|
pub fn RollingAppender::dropped(self : RollingAppender) -> Int {
  self.dropped_count
}

///|
pub fn RollingAppender::is_full(self : RollingAppender) -> Bool {
  self.max_count > 0 && self.records.length() == self.max_count
}

///|
pub fn RollingAppender::clear(self : RollingAppender) -> RollingAppender {
  RollingAppender::{
    records: [],
    entries: [],
    max_count: self.max_count,
    dropped_count: 0,
  }
}