///|
/// A structured log record passed to handlers.
pub struct Entry {
  /// The severity level of the record.
  level : Level
  /// The optional category associated with the record.
  category : String?
  /// The timestamp captured when the record was created.
  timestamp : @x/time.ZonedDateTime
  /// The source location captured from the logging callsite, if available.
  source : SourceLoc?
  /// Structured fields attached to the record.
  fields : Map[String, Json]
}

///|
fn Entry::Entry(
  level~ : Level,
  category? : String,
  source? : SourceLoc,
  timestamp? : Int64 = @env.now().reinterpret_as_int64(),
  fields? : Map[String, Json] = Map([]),
) -> Entry {
  let timestamp = try! @x/time.unix(
    timestamp / 1_000,
    nanosecond=(timestamp % 1_000).to_int() * 1_000_000,
  )
  Entry::{ level, category, timestamp, source, fields }
}

///|
pub impl ToJson for Entry with fn to_json(self : Entry) -> Json {
  let object : Map[String, Json] = {
    "timestamp": self.timestamp.to_string(),
    "level": self.level.to_string(),
  }
  if self.category is Some(category) {
    object["category"] = Json::string(category)
  }
  if self.source is Some(source) {
    object["source"] = Json::string(source.to_string())
  }
  // Hoist structured fields to the top level; on a name clash the field wins.
  for key, value in self.fields {
    object[key] = value
  }
  Json::object(object)
}

///|
pub impl Show for Entry with fn output(self, logger) -> Unit {
  logger <+ "timestamp=\{self.timestamp} level=\{self.level}"
  if self.category is Some(category) {
    logger <+ " category=\{category}"
  }
  if self.source is Some(source) {
    logger <+ " source=\{source}"
  }
  for key, value in self.fields {
    logger <+ " \{key}=\{value.stringify()}"
  }
}