///|
/// A sink that receives structured log entries.
///
/// Direct handler calls may raise errors. Logger methods catch and ignore those
/// errors so logging failures do not interrupt application code.
pub(open) trait Handler {
  /// Write or otherwise process one log entry.
  fn handle(Self, Entry) -> Unit raise
}

///|
/// Output format used by built-in handlers.
pub(all) enum Format {
  /// Human-readable key-value text.
  Text
  /// One JSON object per line.
  Jsonl
}

///|
/// A handler that writes log entries to standard output.
struct Stdout {
  format : Format
}

///|
/// Create a standard-output handler.
///
/// The default format is `Jsonl`.
pub fn Stdout::Stdout(format? : Format = Jsonl) -> Stdout {
  Stdout::{ format, }
}

///|
pub impl Handler for Stdout with fn handle(self : Stdout, entry : Entry) -> Unit {
  match self.format {
    Text => println(entry.to_string())
    Jsonl => println(entry.to_json().stringify())
  }
}

///|
/// Error raised by `Multi` when one or more child handlers fail.
pub(all) suberror MultiError {
  /// The errors collected from failing child handlers.
  MultiError(Array[Error])
}

///|
/// A handler that fans out each entry to multiple child handlers.
///
/// Every child handler is attempted. If any fail, `MultiError` is raised after
/// all handlers have been called.
pub(all) struct Multi(Array[&Handler])

///|
pub impl Handler for Multi with fn handle(self : Multi, entry : Entry) -> Unit {
  let errors = []
  for handler in self.0 {
    handler.handle(entry) catch {
      error => errors.push(error)
    }
  }
  if !errors.is_empty() {
    raise MultiError(errors)
  }
}

///|
/// A handler that appends log entries to a file.
///
/// `File` is native-only. It keeps the file open until `close` is called or the
/// handler becomes unreachable.
struct File {
  format : Format
  file : @fs.File
}

///|
/// Open a file handler in append mode.
///
/// The default format is `Jsonl`.
pub fn File::File(
  path : String,
  format? : Format = Jsonl,
) -> File raise @fs.IOError {
  let file = @fs.File::open_append(path)
  File::{ format, file }
}

///|
fn File::write_entry(self : File, entry : Entry) -> Unit raise @fs.IOError {
  let sb = StringBuilder::new()
  match self.format {
    Text => sb.write_string(entry.to_string())
    Jsonl => sb.write_string(entry.to_json().stringify())
  }
  sb.write_char('\n')
  @fs.File::write_string(self.file, sb.to_string())
  @fs.File::flush(self.file)
}

///|
/// Close the file handle.
///
/// Calling `close` more than once is safe.
pub fn File::close(self : File) -> Unit {
  @fs.File::close(self.file)
}

///|
pub impl Handler for File with fn handle(self : File, entry : Entry) -> Unit raise {
  self.write_entry(entry)
}