///|
/// Local zenoh object mirror skeleton for W4A. This package is offline-only:
/// it models keys, contracts, and trace monitor inputs without running zenohd.

///|
pub fn package_id() -> String {
  "fieldbus_core/zenoh_mirror"
}

///|
pub fn zenoh_mirror_medium_id() -> String {
  "zenoh_mirror"
}

///|
pub(all) struct ZenohMirrorCapabilities {
  medium_id : String
  object_mirror : Bool
  protocol_session : Bool
  partitionable : Bool
  bounded_delay : Bool
  total_order_delivery : Bool
  hard_realtime_sink : Bool
  validation_scope : String
} derive(Eq, Debug)

///|
pub fn ZenohMirrorCapabilities::local_non_live() -> ZenohMirrorCapabilities {
  {
    medium_id: zenoh_mirror_medium_id(),
    object_mirror: true,
    protocol_session: false,
    partitionable: true,
    bounded_delay: false,
    total_order_delivery: false,
    hard_realtime_sink: false,
    validation_scope: "offline object mirror; no zenohd, no zenoh-pico, no live transport",
  }
}

///|
pub fn ZenohMirrorCapabilities::is_hard_realtime_evidence(
  self : ZenohMirrorCapabilities,
) -> Bool {
  self.bounded_delay && self.hard_realtime_sink
}

///|
pub(all) enum ZenohMirrorKind {
  SignalMirror
  CommandBuffer
  QueryableMirror
  EventStreamMirror
  LifecycleMirror
} derive(Eq, Debug)

///|
pub fn ZenohMirrorKind::label(self : ZenohMirrorKind) -> String {
  match self {
    SignalMirror => "signal_mirror"
    CommandBuffer => "command_buffer"
    QueryableMirror => "queryable_mirror"
    EventStreamMirror => "event_stream_mirror"
    LifecycleMirror => "lifecycle_mirror"
  }
}

///|
pub(all) struct ZenohCia402Mirror {
  object_id : String
  label : String
  key : String
  mirror_kind : ZenohMirrorKind
  direction : @trace.TraceDirection
  source_medium_id : String
  behavior : String
} derive(Eq, Debug)

///|
pub(all) struct ZenohMirrorReport {
  capabilities : ZenohMirrorCapabilities
  object_count : Int
  binding_count : Int
  trace_event_count : Int
  checked_events : Int
  matched_steps : Int
  hard_realtime_evidence : Bool
  conformance : @protocol_ast.ConformanceReport
} derive(Debug)

///|
pub fn ZenohMirrorReport::passes(self : ZenohMirrorReport) -> Bool {
  self.conformance.passes() &&
  self.object_count == 6 &&
  self.binding_count == 6 &&
  self.trace_event_count == 11 &&
  self.checked_events == 8 &&
  self.matched_steps == 8 &&
  !self.hard_realtime_evidence
}

///|
pub fn zenoh_cia402_mirrors(axis_id? : Int = 1) -> Array[ZenohCia402Mirror] {
  [
    mirror_asset(
      object_id="axis.controlword",
      label="cia402.controlword.write",
      key=axis_key(axis_id, "command/controlword"),
      mirror_kind=CommandBuffer,
      direction=@trace.Tx,
      behavior="soft command buffer for mirrored 0x6040 controlword; not a hard realtime PDO path",
    ),
    mirror_asset(
      object_id="axis.statusword",
      label="cia402.statusword.read",
      key=axis_key(axis_id, "state/statusword"),
      mirror_kind=SignalMirror,
      direction=@trace.Rx,
      behavior="drive statusword mirror for observation and diagnostics",
    ),
    mirror_asset(
      object_id="axis.mode",
      label="cia402.mode.write",
      key=axis_key(axis_id, "command/mode"),
      mirror_kind=CommandBuffer,
      direction=@trace.Tx,
      behavior="soft command buffer for mirrored 0x6060 mode of operation",
    ),
    mirror_asset(
      object_id="axis.target_position",
      label="cia402.target_position.write",
      key=axis_key(axis_id, "command/target-position"),
      mirror_kind=CommandBuffer,
      direction=@trace.Tx,
      behavior="soft setpoint mirror for supervision; bounded delay is intentionally false",
    ),
    mirror_asset(
      object_id="axis.actual_position",
      label="cia402.actual_position.read",
      key=axis_key(axis_id, "state/actual-position"),
      mirror_kind=SignalMirror,
      direction=@trace.Rx,
      behavior="actual position state mirror for observer replay and dashboard use",
    ),
    mirror_asset(
      object_id="axis.fault",
      label="cia402.fault.detected",
      key=axis_key(axis_id, "event/fault"),
      mirror_kind=EventStreamMirror,
      direction=@trace.Rx,
      behavior="diagnostic fault event mirror; delivery order is not treated as realtime proof",
    ),
  ]
}

///|
pub fn zenoh_cia402_bindings(
  axis_id? : Int = 1,
) -> Array[@isocontract.ObjectBinding] {
  let bindings : Array[@isocontract.ObjectBinding] = []
  for mirror in zenoh_cia402_mirrors(axis_id~) {
    bindings.push(zenoh_binding(mirror, axis_id))
  }
  bindings
}

///|
pub fn zenoh_cia402_contracts(
  axis_id? : Int = 1,
) -> @isocontract.ContractRegistry {
  @profile_cia402.cia402_contracts_for_bindings(zenoh_cia402_bindings(axis_id~))
}

///|
pub fn zenoh_cia402_protocol() -> @protocol_ast.GlobalProtocol {
  @profile_cia402.cia402_enable_fault_reset_protocol()
}

///|
pub fn zenoh_cia402_observer_backend(
  axis_id? : Int = 1,
) -> @protocol_ast.ObserverBackend {
  @protocol_ast.ObserverBackend::new(
    registry=zenoh_cia402_contracts(axis_id~),
    protocol=zenoh_cia402_protocol(),
    roles=[@protocol_ast.role(name="controller")],
  )
}

///|
pub fn zenoh_cia402_trace_fixture(axis_id? : Int = 1) -> @trace.TraceLog {
  let log = @trace.TraceLog::new()
  log.append(
    zenoh_event(1, "zenoh.mirror.session.open", @trace.Meta, 0L, None, axis_id~),
  )
  log.append(
    zenoh_event(
      2,
      "cia402.controlword.write",
      @trace.Tx,
      10L,
      Some(0x0006),
      axis_id~,
    ),
  )
  log.append(
    zenoh_event(
      3,
      "cia402.statusword.read",
      @trace.Rx,
      20L,
      Some(0x0021),
      axis_id~,
    ),
  )
  log.append(
    zenoh_event(4, "cia402.mode.write", @trace.Tx, 30L, Some(0x08), axis_id~),
  )
  log.append(
    zenoh_event(
      5,
      "cia402.target_position.write",
      @trace.Tx,
      40L,
      Some(607_000),
      axis_id~,
    ),
  )
  log.append(
    zenoh_event(
      6,
      "cia402.actual_position.read",
      @trace.Rx,
      50L,
      Some(606_400),
      axis_id~,
    ),
  )
  log.append(
    zenoh_event(
      7,
      "zenoh.mirror.diagnostic.partitionable",
      @trace.Meta,
      60L,
      None,
      axis_id~,
    ),
  )
  log.append(
    zenoh_event(8, "cia402.fault.detected", @trace.Rx, 70L, Some(1), axis_id~),
  )
  log.append(
    zenoh_event(
      9,
      "cia402.controlword.write",
      @trace.Tx,
      80L,
      Some(0x0080),
      axis_id~,
    ),
  )
  log.append(
    zenoh_event(
      10,
      "cia402.statusword.read",
      @trace.Rx,
      90L,
      Some(0x0040),
      axis_id~,
    ),
  )
  log.append(
    zenoh_event(
      11,
      "zenoh.mirror.session.close",
      @trace.Meta,
      100L,
      None,
      axis_id~,
    ),
  )
  log
}

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

///|
pub fn zenoh_cia402_mirror_report(axis_id? : Int = 1) -> ZenohMirrorReport {
  let capabilities = ZenohMirrorCapabilities::local_non_live()
  let mirrors = zenoh_cia402_mirrors(axis_id~)
  let bindings = zenoh_cia402_bindings(axis_id~)
  let trace = zenoh_cia402_trace_fixture(axis_id~)
  let conformance = zenoh_cia402_observer_backend(axis_id~).check_trace(trace)
  {
    capabilities,
    object_count: mirrors.length(),
    binding_count: bindings.length(),
    trace_event_count: trace.len(),
    checked_events: conformance.checked_events,
    matched_steps: conformance.matched_steps,
    hard_realtime_evidence: capabilities.is_hard_realtime_evidence(),
    conformance,
  }
}

///|
fn mirror_asset(
  object_id~ : String,
  label~ : String,
  key~ : String,
  mirror_kind~ : ZenohMirrorKind,
  direction~ : @trace.TraceDirection,
  behavior~ : String,
) -> ZenohCia402Mirror {
  {
    object_id,
    label,
    key,
    mirror_kind,
    direction,
    source_medium_id: zenoh_mirror_medium_id(),
    behavior,
  }
}

///|
fn zenoh_binding(
  mirror : ZenohCia402Mirror,
  axis_id : Int,
) -> @isocontract.ObjectBinding {
  @isocontract.ObjectBinding::make(
    object_id=mirror.object_id,
    binding_id="zenoh.axis" + axis_id.to_string() + "." + mirror.object_id,
    medium_id=zenoh_mirror_medium_id(),
    label=mirror.label,
    direction=mirror.direction,
    kind=@isocontract.ZenohKey,
    key=mirror.key,
    authority=@isocontract.Role("controller"),
  )
}

///|
fn axis_key(axis_id : Int, suffix : String) -> String {
  "cell/axis/" + axis_id.to_string() + "/" + suffix
}

///|
fn zenoh_event(
  event_id : Int,
  label : String,
  direction : @trace.TraceDirection,
  time_ns : Int64,
  payload_digest : Int?,
  axis_id~ : Int,
) -> @trace.TraceEvent {
  @trace.TraceEvent::make(
    event_id~,
    parent_id=None,
    vtime=@core.VTime::from_ns(time_ns),
    clock_domain="sim",
    raw_ns=time_ns,
    node_id="controller",
    medium_id=zenoh_mirror_medium_id(),
    channel_id=None,
    direction~,
    payload_digest~,
    rng_step=event_id,
    seed=440 + axis_id,
    backend=@core.SimNative,
    label~,
  )
}