///|
/// Ordered key-value pair used by record payloads and event headers.
pub(all) struct Pair {
  key : String
  value : String
} derive(Eq, @debug.Debug)

///|
/// Construct an ordered string pair.
pub fn pair(key : StringView, value : StringView) -> Pair {
  { key: key.to_owned(), value: value.to_owned() }
}

///|
/// Structured event payload. Records keep declaration order so exported
/// snapshots stay deterministic across targets and test runs.
pub(all) enum EventValue {
  VNull
  VBool(Bool)
  VInt(Int)
  VText(String)
  VList(Array[EventValue])
  VRecord(Array[(String, EventValue)])
} derive(Eq, @debug.Debug)

///|
pub fn null_value() -> EventValue {
  VNull
}

///|
pub fn bool_value(value : Bool) -> EventValue {
  VBool(value)
}

///|
pub fn int_value(value : Int) -> EventValue {
  VInt(value)
}

///|
pub fn text_value(value : StringView) -> EventValue {
  VText(value.to_owned())
}

///|
pub fn list_value(values : ArrayView[EventValue]) -> EventValue {
  VList(values.to_owned())
}

///|
pub fn record_value(fields : ArrayView[(String, EventValue)]) -> EventValue {
  VRecord(fields.to_owned())
}

///|
pub fn field(key : StringView, value : EventValue) -> (String, EventValue) {
  (key.to_owned(), value)
}

///|
pub fn EventValue::as_text(self : EventValue) -> String? {
  match self {
    VText(value) => Some(value)
    _ => None
  }
}

///|
pub fn EventValue::as_int(self : EventValue) -> Int? {
  match self {
    VInt(value) => Some(value)
    _ => None
  }
}

///|
pub fn EventValue::as_bool(self : EventValue) -> Bool? {
  match self {
    VBool(value) => Some(value)
    _ => None
  }
}

///|
pub fn EventValue::get(self : EventValue, key : StringView) -> EventValue? {
  let wanted = key.to_owned()
  match self {
    VRecord(fields) => {
      for entry in fields {
        let (name, value) = entry
        if name == wanted {
          return Some(value)
        }
      }
      None
    }
    _ => None
  }
}

///|
pub fn EventValue::get_path(
  self : EventValue,
  path : StringView,
) -> EventValue? {
  let parts = path.to_owned().split(".").to_array()
  guard parts.length() > 0 else { return None }
  for part in parts; current = Some(self) {
    match current {
      Some(value) => continue value.get(part)
      None => break None
    }
  } nobreak {
    current
  }
}

///|
pub fn EventValue::require_path(
  self : EventValue,
  path : StringView,
) -> Result[EventValue, EventRailError] {
  match self.get_path(path) {
    Some(value) => Ok(value)
    None => Err(MissingField(path.to_owned()))
  }
}

///|
pub fn EventValue::is_scalar(self : EventValue) -> Bool {
  match self {
    VNull | VBool(_) | VInt(_) | VText(_) => true
    VList(_) | VRecord(_) => false
  }
}

///|
pub fn EventValue::field_count(self : EventValue) -> Int {
  match self {
    VRecord(fields) => fields.length()
    _ => 0
  }
}

///|
pub fn EventValue::list_count(self : EventValue) -> Int {
  match self {
    VList(values) => values.length()
    _ => 0
  }
}

///|
pub fn EventValue::to_wire(self : EventValue) -> String {
  match self {
    VNull => "null"
    VBool(value) => if value { "true" } else { "false" }
    VInt(value) => value.to_string()
    VText(value) => "\"\{escape_wire_text(value)}\""
    VList(values) => {
      let buf = StringBuilder()
      buf.write_char('[')
      for idx, item in values {
        if idx > 0 {
          buf.write_char(',')
        }
        buf.write_string(item.to_wire())
      }
      buf.write_char(']')
      buf.to_string()
    }
    VRecord(fields) => {
      let buf = StringBuilder()
      buf.write_char('{')
      for idx, entry in fields {
        let (name, value) = entry
        if idx > 0 {
          buf.write_char(',')
        }
        buf.write_string(escape_wire_text(name))
        buf.write_char('=')
        buf.write_string(value.to_wire())
      }
      buf.write_char('}')
      buf.to_string()
    }
  }
}

///|
pub fn escape_wire_text(value : String) -> String {
  let buf = StringBuilder()
  for ch in value {
    match ch {
      '\\' => buf.write_string("\\\\")
      '"' => buf.write_string("\\\"")
      '\n' => buf.write_string("\\n")
      '\r' => buf.write_string("\\r")
      '\t' => buf.write_string("\\t")
      ',' => buf.write_string("\\,")
      '=' => buf.write_string("\\=")
      '{' => buf.write_string("\\{")
      '}' => buf.write_string("\\}")
      '[' => buf.write_string("\\[")
      ']' => buf.write_string("\\]")
      _ => buf.write_char(ch)
    }
  }
  buf.to_string()
}

///|
pub fn pairs_to_wire(pairs : ArrayView[Pair]) -> String {
  let buf = StringBuilder()
  for idx, p in pairs {
    if idx > 0 {
      buf.write_char(';')
    }
    buf.write_string(escape_wire_text(p.key))
    buf.write_char('=')
    buf.write_string(escape_wire_text(p.value))
  }
  buf.to_string()
}