///|
/// Zenoh-style session/fabric replay for W4B.

///|
pub(all) struct ZenohFabricSessionReport {
  seed : Int
  axis_id : Int
  capabilities : ZenohMirrorCapabilities
  fabric_capabilities : @sim.FabricCapabilities
  session_trace : @trace.TraceLog
  fabric_trace_event_count : Int
  session_trace_event_count : Int
  partition_count : Int
  heal_count : Int
  drop_count : Int
  late_count : Int
  reorder_count : Int
  late_delivery_boundary_ns : Int64
  liveness_boundary : String
  live_interop_evidence : Bool
  conformance : @protocol_ast.ConformanceReport
} derive(Debug)

///|
pub fn ZenohFabricSessionReport::passes(
  self : ZenohFabricSessionReport,
) -> Bool {
  self.conformance.passes() &&
  self.conformance.checked_events == 8 &&
  self.conformance.matched_steps == 8 &&
  self.fabric_capabilities.unlocks_partition_faults() &&
  self.partition_count == 1 &&
  self.heal_count == 1 &&
  self.drop_count == 1 &&
  self.late_count == 1 &&
  self.reorder_count == 1 &&
  self.fabric_trace_event_count > 0 &&
  self.session_trace_event_count > self.fabric_trace_event_count &&
  self.late_delivery_boundary_ns > 0L &&
  !self.live_interop_evidence &&
  !self.capabilities.is_hard_realtime_evidence()
}

///|
pub fn zenoh_fabric_session_trace_fixture(
  seed? : Int = 44,
  axis_id? : Int = 1,
) -> @trace.TraceLog {
  let profile_trace = zenoh_cia402_trace_fixture(axis_id~)
  let fabric_run = @sim.run_fabric_partition_heal(seed~)
  let log = @trace.TraceLog::new()
  let mut event_id = 1
  for index, event in profile_trace.events {
    if index == 3 {
      for fabric_event in fabric_run.trace.events {
        append_session_envelope_event(log, fabric_event, event_id~, seed~)
        event_id += 1
      }
    }
    append_retimed_event(log, event, event_id~, seed~)
    event_id += 1
  }
  log
}

///|
pub fn zenoh_fabric_session_observer_report(
  seed? : Int = 44,
  axis_id? : Int = 1,
) -> @protocol_ast.ConformanceReport {
  zenoh_cia402_observer_backend(axis_id~).check_trace(
    zenoh_fabric_session_trace_fixture(seed~, axis_id~),
  )
}

///|
pub fn zenoh_fabric_session_report(
  seed? : Int = 44,
  axis_id? : Int = 1,
) -> ZenohFabricSessionReport {
  let fabric_run = @sim.run_fabric_partition_heal(seed~)
  let session_trace = zenoh_fabric_session_trace_fixture(seed~, axis_id~)
  let conformance = zenoh_cia402_observer_backend(axis_id~).check_trace(
    session_trace,
  )
  {
    seed,
    axis_id,
    capabilities: ZenohMirrorCapabilities::local_non_live(),
    fabric_capabilities: fabric_run.capabilities,
    session_trace,
    fabric_trace_event_count: fabric_run.trace.len(),
    session_trace_event_count: session_trace.len(),
    partition_count: fabric_run.partition_count,
    heal_count: fabric_run.heal_count,
    drop_count: fabric_run.drop_count,
    late_count: fabric_run.late_count,
    reorder_count: fabric_run.reorder_count,
    late_delivery_boundary_ns: max_delivery_delay_ns(fabric_run.deliveries),
    liveness_boundary: "local replay liveness after partition/heal; no zenoh runtime or transport interop",
    live_interop_evidence: false,
    conformance,
  }
}

///|
fn append_retimed_event(
  log : @trace.TraceLog,
  event : @trace.TraceEvent,
  event_id~ : Int,
  seed~ : Int,
) -> Unit {
  let time_ns = Int64::from_int(event_id) * 10L
  log.append(
    @trace.TraceEvent::make(
      event_id~,
      parent_id=event.parent_id,
      vtime=@core.VTime::from_ns(time_ns),
      clock_domain="sim",
      raw_ns=time_ns,
      node_id=event.node_id,
      medium_id=event.medium_id,
      channel_id=event.channel_id,
      direction=event.direction,
      payload_digest=event.payload_digest,
      rng_step=event_id,
      seed~,
      backend=@core.SimNative,
      label=event.label,
    ),
  )
}

///|
fn append_session_envelope_event(
  log : @trace.TraceLog,
  event : @trace.TraceEvent,
  event_id~ : Int,
  seed~ : Int,
) -> Unit {
  let direction = match event.label {
    "fabric.partition" | "fabric.heal" => @trace.Meta
    _ => @trace.Probe
  }
  let time_ns = Int64::from_int(event_id) * 10L
  log.append(
    @trace.TraceEvent::make(
      event_id~,
      parent_id=event.parent_id,
      vtime=@core.VTime::from_ns(time_ns),
      clock_domain="sim",
      raw_ns=time_ns,
      node_id="fabric",
      medium_id="zenoh.fabric.session",
      channel_id=event.channel_id,
      direction~,
      payload_digest=event.payload_digest,
      rng_step=event_id,
      seed~,
      backend=@core.SimNative,
      label="zenoh.session." + event.label,
    ),
  )
}

///|
fn max_delivery_delay_ns(deliveries : Array[@sim.FabricDelivery]) -> Int64 {
  let mut max_delay = 0L
  for delivery in deliveries {
    if delivery.delayed_by.ns() > max_delay {
      max_delay = delivery.delayed_by.ns()
    }
  }
  max_delay
}