///|
pub struct Source {
  file : String
  record : Int64
  offset : Int64
  event_index : Int32
  collector : String?
  view : String?
} derive(Eq, Hash, ToJson, @json.FromJson)

///|
pub extend Source with @json.FromJson::{from_json}

///|
pub extend Source with Eq::{equal, not_equal}

///|
pub extend Source with Hash::{hash, hash_combine}

///|
pub extend Source with ToJson::{to_json}

///|
/// 偏移指解压后的 MRT 记录起点,索引为记录内零起始事件序号。
pub fn Decoder::decode_at(
  self : Decoder,
  data : Bytes,
  file : String,
  record : Int64,
  offset : Int64,
) -> Batch raise ParseError {
  if record < 1L || offset < 0L {
    raise Invalid(0, "来源记录号或偏移无效")
  }
  let batch = self.decode(data)
  let events = batch.events.mapi(fn(i, event) {
    {
      ..event,
      source: Some({
        file,
        record,
        offset,
        event_index: i,
        collector: None,
        view: None,
      }),
    }
  })
  let states = batch.states.mapi(fn(i, event) {
    {
      ..event,
      source: Some({
        file,
        record,
        offset,
        event_index: i,
        collector: None,
        view: None,
      }),
    }
  })
  let diagnostics = batch.diagnostics.map(fn(item) {
    {
      ..item,
      source: Some({
        file,
        record,
        offset,
        event_index: 0,
        collector: None,
        view: None,
      }),
    }
  })
  { ..batch, events, states, diagnostics, }
}

///|
/// 一个采集器与视图使用一个独立 Decoder;本接口为事件、会话和诊断附加显式身份。
pub fn Decoder::decode_scoped(
  self : Decoder,
  data : Bytes,
  file : String,
  record : Int64,
  offset : Int64,
  collector : String,
  view : String,
) -> Batch raise ParseError {
  if collector == "" || view == "" {
    raise Invalid(0, "采集器和视图身份不能为空")
  }
  let batch = self.decode_at(data, file, record, offset)
  let scope = fn(source : Source?) {
    source.map(fn(s) { { ..s, collector: Some(collector), view: Some(view), } })
  }
  {
    ..batch,
    events: batch.events.map(fn(event) {
      { ..event, source: scope(event.source), }
    }),
    states: batch.states.map(fn(state) {
      { ..state, source: scope(state.source), }
    }),
    diagnostics: batch.diagnostics.map(fn(item) {
      { ..item, source: scope(item.source), }
    }),
  }
}

///|
fn source_scope(source : Source?) -> String {
  match source {
    Some(s) if s.collector is Some(_) || s.view is Some(_) =>
      Json([s.collector, s.view]).stringify() + "|"
    _ => ""
  }
}

///|
fn scoped_peer_key(
  asn : UInt32,
  ip : String,
  outbound : Bool,
  source : Source?,
) -> String {
  source_scope(source) + peer_key(asn, ip, outbound)
}