///|
fn[T : ToJson] analysis_optional(value : T?) -> Json {
  value.map(fn(item) { Json(item) }).unwrap_or(Json::null())
}

///|
/// 可空字段统一接受 null 与缺省,数组中的空值仍由各字段类型校验。
fn analysis_json(value : Json) -> Json {
  match value {
    Object(fields) => {
      let result : Map[String, Json] = Map([])
      for key, child in fields {
        if !(child is Null) {
          result[key] = analysis_json(child)
        }
      }
      Json::object(result)
    }
    Array(values) => Json(values.map(analysis_json))
    _ => value
  }
}

///|
pub struct InvestigationGap {
  from_us : UInt64
  to_us : UInt64
  collector : String?
  view : String?
} derive(ToJson, @json.FromJson)

///|
pub struct FocusRule {
  prefix : String
  expected_origins : Array[UInt32]?
  allowed_more_specifics : Array[String]?
} derive(ToJson, @json.FromJson)

///|
pub struct AnalysisQuery {
  side : String?
  origin_asns : Array[UInt32]?
  path_asns : Array[UInt32]?
  communities : Array[UInt32]?
  origin_unknown : Bool?
  prefix : String?
  prefix_mode : String?
  peer_asn : UInt32?
  outbound : Bool?
  path_id : UInt32?
  collector : String?
  view : String?
  from_us : UInt64?
  to_us : UInt64?
  kind : String?
} derive(ToJson, @json.FromJson)

///|
pub struct ComparisonWindow {
  from_us : UInt64
  to_us : UInt64
} derive(ToJson, @json.FromJson)

///|
pub struct InvestigationRules {
  schema_version : String
  version : String
  from_us : UInt64
  to_us : UInt64
  bucket_us : UInt64?
  incident_us : UInt64?
  capacity : Int32?
  max_bytes : Int32?
  continuous : Bool?
  gaps : Array[InvestigationGap]?
  focus : Array[FocusRule]?
  query : AnalysisQuery?
  comparison : Array[ComparisonWindow]?
  sequence_prefix : String?
  scope_prefixes : Array[String]?
  scope_peer_asns : Array[UInt32]?
} derive(ToJson, @json.FromJson)

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

///|
pub struct InvestigationEvent {
  id : String
  time_us : UInt64
  prefix : String
  peer : String
  peer_asn : UInt32
  peer_ip : String
  outbound : Bool
  path_id : UInt32?
  source : Source?
  kind : String
  accepted : Bool
  changes_state : Bool
  before : RouteSnapshot?
  after : RouteSnapshot?
  changes : Array[Change]
  selected : Bool
  before_path : Json?
  after_path : Json?
} derive(ToJson)

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

///|
pub struct InvestigationInterval {
  key : String
  prefix : String
  peer : String
  peer_asn : UInt32
  peer_ip : String
  outbound : Bool
  collector : String?
  view : String?
  path_id : UInt32?
  from_us : UInt64
  to_us : UInt64
  state : String
  duration_us : UInt64?
  left_censored : Bool
  right_censored : Bool
  start_source : Source?
  end_source : Source?
  origin_as : UInt32?
  path_length : Int32?
} derive(ToJson)

///|
priv struct InvestigationInput {
  event : RouteEvent?
  state : StateChange?
  source : Source?
  time : UInt64
  baseline : Bool
  gap : InvestigationGap?
  late : Bool
}

///|
/// 邻接只取可证明有序的 AS_SEQUENCE,集合和联盟段不连接为有序链。
fn investigation_path_view(snapshot : RouteSnapshot?) -> Json? {
  let snapshot = match snapshot {
    Some(value) => value
    None => return None
  }
  let edges = []
  let mut previous : UInt32? = None
  for segment in snapshot.attributes.path {
    if segment.kind != 2 {
      previous = None
      continue
    }
    for asn in segment.asns {
      if previous is Some(before) {
        edges.push((before, asn))
      }
      previous = Some(asn)
    }
  }
  Some(
    Json::object({
      "text": Json(path_text(snapshot.attributes.path)),
      "segments": Json(snapshot.attributes.path),
      "adjacencies": Json(edges),
      "origin_as": origin_as(snapshot.attributes.path)
      .map(fn(asn) { Json(asn) })
      .unwrap_or(Json::null()),
      "active": Json(snapshot.active),
      "interpretation": Json("observed_as_adjacency_not_physical_link"),
    }),
  )
}

///|
pub struct Investigation {
  priv rules : InvestigationRules
  priv inputs : Array[InvestigationInput]
  priv mut bytes : Int32
  priv sources : Map[String, Unit]
  priv latest_routes : Map[String, UInt64]
  priv latest_peers : Map[String, UInt64]
  priv resets : Map[String, UInt64]
  priv baseline_scopes : Map[String, Unit]
}

///|
pub fn Investigation::new(text : String) -> Investigation raise ParseError {
  if text.length() > 1048576 {
    raise Invalid(0, "规则配置超过 1 MiB")
  }
  let rules : InvestigationRules = @json.from_json(
    analysis_json(@json.parse(text)),
  ) catch {
    _ => raise Invalid(0, "调查规则 JSON 无效")
  }
  if rules.schema_version != "1.0" ||
    rules.version == "" ||
    rules.from_us >= rules.to_us ||
    rules.to_us > 4294967296000000UL ||
    rules.bucket_us.unwrap_or(60000000UL) < 1UL ||
    rules.incident_us.unwrap_or(60000000UL) < 1UL ||
    rules.capacity.unwrap_or(20000) < 1 ||
    rules.capacity.unwrap_or(20000) > 1000000 ||
    rules.max_bytes.unwrap_or(67108864) < 1024 {
    raise Invalid(0, "规则版本、半开时间窗口或资源预算无效")
  }
  if (rules.to_us - rules.from_us) / rules.bucket_us.unwrap_or(60000000UL) >
    10000UL {
    raise Invalid(0, "时间桶数量超过 10000")
  }
  for gap in rules.gaps.unwrap_or([]) {
    if gap.from_us >= gap.to_us ||
      gap.to_us > rules.to_us ||
      (gap.collector is Some(_)) != (gap.view is Some(_)) {
      raise Invalid(0, "缺档区间或观测点无效")
    }
  }
  for focus in rules.focus.unwrap_or([]) {
    let parent = parse_prefix_text(focus.prefix)
    for child in focus.allowed_more_specifics.unwrap_or([]) {
      let child = parse_prefix_text(child)
      if !parent.contains(child) || child.length <= parent.length {
        raise Invalid(
          0, "允许的更具体前缀必须严格包含于关注前缀",
        )
      }
    }
  }
  if rules.sequence_prefix is Some(prefix) {
    ignore(parse_prefix_text(prefix))
  }
  for prefix in rules.scope_prefixes.unwrap_or([]) {
    ignore(parse_prefix_text(prefix))
  }
  if rules.focus.unwrap_or([]).length() > 128 ||
    rules.gaps.unwrap_or([]).length() > 1000 {
    raise Invalid(0, "关注前缀或缺档条目过多")
  }
  if rules.query is Some(query) {
    if !["before", "after", "either"].contains(query.side.unwrap_or("either")) {
      raise Invalid(0, "查询 side 必须为 before/after/either")
    }
    ignore(
      Query::new(
        prefix?=query.prefix,
        prefix_mode=query.prefix_mode.unwrap_or("exact"),
      ),
    )
  }
  if rules.comparison is Some(windows) {
    if rules.sequence_prefix is None {
      raise Invalid(0, "对比需要 sequence_prefix 指定同一资源")
    }
    if windows.length() != 2 {
      raise Invalid(0, "对比需要两个窗口")
    }
    for window in windows {
      if window.from_us < rules.from_us ||
        window.to_us > rules.to_us ||
        window.from_us >= window.to_us {
        raise Invalid(0, "对比窗口必须位于调查范围内")
      }
    }
  }
  {
    rules,
    inputs: [],
    bytes: 0,
    sources: Map([]),
    latest_routes: Map([]),
    latest_peers: Map([]),
    resets: Map([]),
    baseline_scopes: Map([]),
  }
}

///|
/// 空 RIB 的有效 peer 表同样证明显式基线存在。
pub fn Investigation::set_baseline_scope(
  self : Investigation,
  collector : String,
  view : String,
) -> Unit raise ParseError {
  if collector == "" || view == "" {
    raise Invalid(0, "基线观测点身份不能为空")
  }
  self.baseline_scopes[source_scope(
    Some({
      file: "",
      record: 0L,
      offset: 0L,
      event_index: 0,
      collector: Some(collector),
      view: Some(view),
    }),
  )] = ()
}

///|
/// 稳定标识只使用来源元组,调用方必须传入包内文件标签。
pub fn investigation_source_id(source : Source?) -> String {
  match source {
    Some(s) =>
      Json([
        s.collector.map(fn(value) { Json(value) }).unwrap_or(Json::null()),
        s.view.map(fn(value) { Json(value) }).unwrap_or(Json::null()),
        Json(s.file),
        Json(s.record),
        Json(s.offset),
        Json(s.event_index),
      ]).stringify()
    None => "unknown"
  }
}

///|
fn Investigation::append(
  self : Investigation,
  input : InvestigationInput,
  bytes : Int32,
) -> Unit raise ParseError {
  if self.inputs.length() >= self.rules.capacity.unwrap_or(20000) ||
    bytes > self.rules.max_bytes.unwrap_or(67108864) - self.bytes {
    raise Invalid(
      0, "调查输入超过事件数或内存预算,请缩小范围",
    )
  }
  let id = investigation_source_id(input.source)
  if id == "unknown" {
    raise Invalid(0, "调查要求显式来源")
  }
  if self.sources.contains(id) {
    raise Invalid(0, "重复来源事件")
  }
  self.sources[id] = ()
  self.bytes += bytes
  self.inputs.push(input)
}

///|
pub fn Investigation::observe(
  self : Investigation,
  event : RouteEvent,
  baseline~ : Bool = false,
) -> Unit raise ParseError {
  if self.rules.scope_peer_asns is Some(peers) &&
    !peers.is_empty() &&
    !peers.contains(event.peer_asn) {
    return
  }
  if self.rules.scope_prefixes is Some(prefixes) &&
    !prefixes.is_empty() &&
    !prefixes.any(prefix => {
      parse_prefix_text(prefix).contains(parse_prefix_text(event.prefix))
    }) {
    return
  }
  if baseline &&
    (
      event.action != "rib" ||
      event_time(event.timestamp, event.microseconds) > self.rules.from_us
    ) {
    raise Invalid(0, "基线必须是起点以前的 RIB")
  }
  let time = event_time(event.timestamp, event.microseconds)
  let key = snapshot_route_key(event)
  let peer = scoped_peer_key(
    event.peer_asn,
    event.peer_ip,
    event.outbound,
    event.source,
  )
  let late = self.latest_routes
    .get(key)
    .map(fn(t) { t > time })
    .unwrap_or(false) ||
    self.resets.get(peer).map(fn(t) { t > time }).unwrap_or(false)
  if !late {
    self.latest_routes[key] = time
    if self.latest_peers.get(peer).map(fn(t) { time > t }).unwrap_or(true) {
      self.latest_peers[peer] = time
    }
  }
  // 四倍字符数作为输入体积预算,宿主另限制派生输出体积。
  self.append(
    {
      event: Some(event),
      state: None,
      source: event.source,
      time,
      baseline,
      gap: None,
      late,
    },
    event.to_json().stringify().length() * 4 + 512,
  )
}

///|
pub fn Investigation::observe_state(
  self : Investigation,
  state : StateChange,
) -> Unit raise ParseError {
  let time = event_time(state.timestamp, state.microseconds)
  let peers = [false, true].map(fn(direction) {
    scoped_peer_key(state.peer_asn, state.peer_ip, direction, state.source)
  })
  let late = peers.any(fn(peer) {
    self.latest_peers.get(peer).map(fn(t) { t > time }).unwrap_or(false)
  })
  if !late && state.old_state == 6 && state.new_state != 6 {
    for peer in peers {
      self.resets[peer] = time
      self.latest_peers[peer] = time
    }
  }
  self.append(
    {
      event: None,
      state: Some(state),
      source: state.source,
      time,
      baseline: false,
      gap: None,
      late,
    },
    1024,
  )
}

///|
fn analysis_side_matches(
  query : AnalysisQuery,
  snapshot : RouteSnapshot?,
) -> Bool {
  let snapshot = match snapshot {
    Some(s) if s.active => s
    _ => return false
  }
  let attributes = snapshot.attributes
  if query.origin_unknown.unwrap_or(false) &&
    origin_as(attributes.path) is Some(_) {
    return false
  }
  if query.origin_asns is Some(values) &&
    !values.is_empty() &&
    !origin_as(attributes.path)
    .map(fn(asn) { values.contains(asn) })
    .unwrap_or(false) {
    return false
  }
  if query.path_asns is Some(values) &&
    !values.is_empty() &&
    !attributes.path.any(fn(segment) {
      segment.asns.any(fn(asn) { values.contains(asn) })
    }) {
    return false
  }
  if query.communities is Some(values) &&
    !values.is_empty() &&
    !attributes.communities.any(fn(value) { values.contains(value) }) {
    return false
  }
  true
}

///|
fn analysis_matches(
  query : AnalysisQuery?,
  event : InvestigationEvent,
) -> Bool raise ParseError {
  let query = match query {
    Some(q) => q
    None => return true
  }
  if query.from_us is Some(t) && event.time_us < t {
    return false
  }
  if query.to_us is Some(t) && event.time_us >= t {
    return false
  }
  if query.kind is Some(kind) &&
    event.kind != kind &&
    !event.changes.any(fn(change) { change.kind == kind }) {
    return false
  }
  if query.peer_asn is Some(asn) && asn != event.peer_asn {
    return false
  }
  if query.path_id is Some(id) && event.path_id != Some(id) {
    return false
  }
  if query.outbound is Some(direction) && direction != event.outbound {
    return false
  }
  if query.collector is Some(c) &&
    event.source.map(fn(s) { s.collector }) != Some(Some(c)) {
    return false
  }
  if query.view is Some(v) &&
    event.source.map(fn(s) { s.view }) != Some(Some(v)) {
    return false
  }
  if query.prefix is Some(prefix) {
    let parent = parse_prefix_text(prefix)
    let target = parse_prefix_text(event.prefix)
    let matches = match query.prefix_mode.unwrap_or("exact") {
      "more-specific" =>
        parent.length < target.length && parent.contains(target)
      "covering" => target.contains(parent)
      _ => parent == target
    }
    if !matches {
      return false
    }
  }
  let has_attributes = !query.origin_asns.unwrap_or([]).is_empty() ||
    !query.path_asns.unwrap_or([]).is_empty() ||
    !query.communities.unwrap_or([]).is_empty() ||
    query.origin_unknown.unwrap_or(false)
  if !has_attributes {
    return true
  }
  match query.side.unwrap_or("either") {
    "before" => analysis_side_matches(query, event.before)
    "after" => analysis_side_matches(query, event.after)
    _ =>
      analysis_side_matches(query, event.before) ||
      analysis_side_matches(query, event.after)
  }
}

///|
priv struct ChartCounts {
  mut events : Int32
  mut notices : Int32
  types : Map[String, Int32]
  notice_types : Map[String, Int32]
  prefixes : Map[String, (Int32, Int32)]
  peers : Map[String, (Int32, Int32)]
  buckets : Map[UInt64, (Int32, Int32)]
}

///|
fn chart_counts() -> ChartCounts {
  {
    events: 0,
    notices: 0,
    types: Map([]),
    notice_types: Map([]),
    prefixes: Map([]),
    peers: Map([]),
    buckets: Map([]),
  }
}

///|
fn ChartCounts::observe(
  self : ChartCounts,
  event : InvestigationEvent,
  rules : InvestigationRules,
) -> Unit {
  self.events += 1
  self.notices += event.changes.length()
  self.types[event.kind] = self.types.get(event.kind).unwrap_or(0) + 1
  for change in event.changes {
    self.notice_types[change.kind] = self.notice_types
      .get(change.kind)
      .unwrap_or(0) +
      1
  }
  for (map, key) in [(self.prefixes, event.prefix), (self.peers, event.peer)] {
    let old = map.get(key).unwrap_or((0, 0))
    map[key] = (old.0 + 1, old.1 + event.changes.length())
  }
  let bucket = rules.from_us +
    (event.time_us - rules.from_us) /
    rules.bucket_us.unwrap_or(60000000UL) *
    rules.bucket_us.unwrap_or(60000000UL)
  let old = self.buckets.get(bucket).unwrap_or((0, 0))
  self.buckets[bucket] = (old.0 + 1, old.1 + event.changes.length())
}

///|
fn chart_ranking(map : Map[String, (Int32, Int32)]) -> Json {
  let rows = map.to_array()
  rows.sort_by(fn(a, b) {
    if a.1.0 != b.1.0 {
      b.1.0.compare(a.1.0)
    } else {
      a.0.compare(b.0)
    }
  })
  Json(
    rows.map(fn(pair) {
      Json::object({
        "key": Json(pair.0),
        "events": Json(pair.1.0),
        "notices": Json(pair.1.1),
      })
    }),
  )
}

///|
fn ChartCounts::report(
  self : ChartCounts,
  scope : String,
  rules : InvestigationRules,
) -> Json {
  let buckets = []
  let mut start = rules.from_us
  while start < rules.to_us {
    let counts = self.buckets.get(start).unwrap_or((0, 0))
    let end = if rules.to_us - start < rules.bucket_us.unwrap_or(60000000UL) {
      rules.to_us
    } else {
      start + rules.bucket_us.unwrap_or(60000000UL)
    }
    let interrupted = !rules.continuous.unwrap_or(false) ||
      rules.gaps.unwrap_or([]).any(fn(g) { g.from_us < end && g.to_us > start })
    buckets.push(
      Json::object({
        "from_us": Json(start),
        "to_us": Json(end),
        "events": if interrupted {
          Json::null()
        } else {
          Json(counts.0)
        },
        "notices": if interrupted {
          Json::null()
        } else {
          Json(counts.1)
        },
        "observed_events": Json(counts.0),
        "observed_notices": Json(counts.1),
        "complete": Json(!interrupted),
      }),
    )
    start = end
  }
  Json::object({
    "scope": Json(scope),
    "events": Json(self.events),
    "notices": Json(self.notices),
    "types": Json(self.types),
    "notice_types": Json(self.notice_types),
    "prefixes": chart_ranking(self.prefixes),
    "peers": chart_ranking(self.peers),
    "buckets": Json(buckets),
    "announcement_withdrawal_basis": Json("accepted_raw_events_only"),
  })
}

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

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

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

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

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

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

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

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

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

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