///|
/// Plain-text formatter with timestamp, level, module, message and fields.
pub fn text_formatter() -> (LogEntry) -> String {
  fn(entry : LogEntry) -> String {
    let sb = StringBuilder()
    sb.write_string("[")
    sb.write_string(entry.timestamp.to_string())
    sb.write_string("] [")
    sb.write_string(entry.level.to_string())
    sb.write_string("]")
    if entry.mod_name.length() > 0 {
      sb.write_string(" (")
      sb.write_string(entry.mod_name)
      sb.write_string(")")
    }
    sb.write_string(" ")
    sb.write_string(entry.message)
    let n = entry.fields.length()
    let mut i = 0
    while i < n {
      let (k, v) = entry.fields[i]
      sb.write_string(" ")
      sb.write_string(k)
      sb.write_string("=")
      sb.write_string(v)
      i = i + 1
    }
    sb.to_string()
  }
}

///|
/// Minimal formatter producing only level and message.
pub fn compact_formatter() -> (LogEntry) -> String {
  fn(entry : LogEntry) -> String {
    let sb = StringBuilder()
    sb.write_string("[")
    sb.write_string(entry.level.to_string())
    sb.write_string("] ")
    sb.write_string(entry.message)
    sb.to_string()
  }
}

///|
/// JSON formatter producing structured log output.
pub fn json_formatter() -> (LogEntry) -> String {
  fn(entry : LogEntry) -> String {
    let sb = StringBuilder()
    sb.write_string("{")
    json_field(sb, "timestamp", entry.timestamp.to_string(), true)
    json_field(sb, "level", jstr(entry.level.to_string()), false)
    json_field(sb, "message", jstr(entry.message), false)
    json_field(sb, "module", jstr(entry.mod_name), false)
    json_field(sb, "file", jstr(entry.file), false)
    json_field(sb, "line", entry.line.to_string(), false)
    json_field(sb, "thread_id", entry.thread_id.to_string(), false)
    json_field(sb, "sequence", entry.sequence.to_string(), false)
    let n = entry.fields.length()
    if n > 0 {
      sb.write_string(",\"fields\":{")
      let mut i = 0
      while i < n {
        let (k, v) = entry.fields[i]
        if i > 0 {
          sb.write_string(",")
        }
        sb.write_string(jstr(k))
        sb.write_string(":")
        sb.write_string(jstr(v))
        i = i + 1
      }
      sb.write_string("}")
    }
    sb.write_string("}")
    sb.to_string()
  }
}

// Write a JSON key-value pair, with optional leading comma.

///|
fn json_field(
  sb : StringBuilder,
  key : String,
  value : String,
  first : Bool,
) -> Unit {
  if !first {
    sb.write_string(",")
  }
  sb.write_string("\"")
  sb.write_string(key)
  sb.write_string("\":")
  sb.write_string(value)
}

// Escape and quote a string for JSON output.

///|
fn jstr(s : String) -> String {
  let sb = StringBuilder()
  sb.write_string("\"")
  let chars : Iter[Char] = s.iter()
  for c in chars {
    if c == '\\' {
      sb.write_string("\\\\")
    } else if c == '"' {
      sb.write_string("\\\"")
    } else if c == '\n' {
      sb.write_string("\\n")
    } else if c == '\r' {
      sb.write_string("\\r")
    } else if c == '\t' {
      sb.write_string("\\t")
    } else {
      sb.write_char(c)
    }
  }
  sb.write_string("\"")
  sb.to_string()
}

///|
/// Separator-delimited formatter suitable for audit trails.
pub fn audit_formatter(separator : String) -> (LogEntry) -> String {
  fn(entry : LogEntry) -> String {
    let sb = StringBuilder()
    sb.write_string(entry.timestamp.to_string())
    sb.write_string(separator)
    sb.write_string(entry.level.to_string())
    sb.write_string(separator)
    sb.write_string(entry.mod_name)
    sb.write_string(separator)
    sb.write_string(entry.file)
    sb.write_string(":")
    sb.write_string(entry.line.to_string())
    sb.write_string(separator)
    sb.write_string(entry.message)
    let n = entry.fields.length()
    let mut i = 0
    while i < n {
      let (k, v) = entry.fields[i]
      sb.write_string(separator)
      sb.write_string(k)
      sb.write_string("=")
      sb.write_string(v)
      i = i + 1
    }
    sb.to_string()
  }
}

///|
/// Wraps a user-provided formatting function as a LogEntry formatter.
pub fn custom_formatter(f : (LogEntry) -> String) -> (LogEntry) -> String {
  f
}