///|
/// Selection rule for a multiplexed DBC message.
pub enum DbcWorkspaceMuxSelector {
  DbcWorkspaceMuxNone
  DbcWorkspaceMuxValue(UInt)
  DbcWorkspaceMuxRange(UInt, UInt)
}

///|
pub fn dbc_workspace_mux_variants() -> Array[DbcWorkspaceMuxSelector] {
  [DbcWorkspaceMuxNone, DbcWorkspaceMuxValue(0), DbcWorkspaceMuxRange(0, 1)]
}

///|
/// A named group of DBC signals used by a calibration tool.
pub struct DbcWorkspaceSignalGroup {
  name : String
  message_id : UInt
  signals : Array[Signal]
  mut active : Bool
}

///|
pub fn dbc_workspace_signal_group(
  name : String,
  message_id : UInt,
  signals? : Array[Signal] = [],
) -> DbcWorkspaceSignalGroup {
  { name, message_id, signals: signals.copy(), active: true }
}

///|
pub fn DbcWorkspaceSignalGroup::name(self : DbcWorkspaceSignalGroup) -> String {
  self.name
}

///|
pub fn DbcWorkspaceSignalGroup::message_id(
  self : DbcWorkspaceSignalGroup,
) -> UInt {
  self.message_id
}

///|
pub fn DbcWorkspaceSignalGroup::signals(
  self : DbcWorkspaceSignalGroup,
) -> Array[Signal] {
  self.signals.copy()
}

///|
pub fn DbcWorkspaceSignalGroup::active(self : DbcWorkspaceSignalGroup) -> Bool {
  self.active
}

///|
pub fn DbcWorkspaceSignalGroup::set_active(
  self : DbcWorkspaceSignalGroup,
  active : Bool,
) -> Unit {
  self.active = active
}

///|
pub fn DbcWorkspaceSignalGroup::add_signal(
  self : DbcWorkspaceSignalGroup,
  item : Signal,
) -> Bool {
  if self.find(item.name()) is Some(_) {
    false
  } else {
    self.signals.push(item)
    true
  }
}

///|
pub fn DbcWorkspaceSignalGroup::find(
  self : DbcWorkspaceSignalGroup,
  name : String,
) -> Signal? {
  for item in self.signals {
    if item.name() == name {
      return Some(item)
    }
  }
  None
}

///|
pub fn DbcWorkspaceSignalGroup::contains(
  self : DbcWorkspaceSignalGroup,
  name : String,
) -> Bool {
  self.find(name) is Some(_)
}

///|
pub fn DbcWorkspaceSignalGroup::length(self : DbcWorkspaceSignalGroup) -> Int {
  self.signals.length()
}

///|
/// A physical value associated with a named DBC signal.
pub struct DbcWorkspaceValue {
  name : String
  value : Double
}

///|
pub fn dbc_workspace_value(name : String, value : Double) -> DbcWorkspaceValue {
  { name, value }
}

///|
pub fn DbcWorkspaceValue::name(self : DbcWorkspaceValue) -> String {
  self.name
}

///|
pub fn DbcWorkspaceValue::value(self : DbcWorkspaceValue) -> Double {
  self.value
}

///|
/// A deterministic value set that can be converted to a DBC map.
pub struct DbcWorkspaceValueSet {
  values : Array[DbcWorkspaceValue]
  mut revision : UInt
}

///|
pub fn new_dbc_workspace_value_set() -> DbcWorkspaceValueSet {
  { values: [], revision: 0 }
}

///|
pub fn DbcWorkspaceValueSet::put(
  self : DbcWorkspaceValueSet,
  value : DbcWorkspaceValue,
) -> Unit {
  match self.find_index(value.name()) {
    Some(index) => self.values[index] = value
    None => self.values.push(value)
  }
  self.revision += 1
}

///|
pub fn DbcWorkspaceValueSet::put_number(
  self : DbcWorkspaceValueSet,
  name : String,
  value : Double,
) -> Unit {
  self.put(dbc_workspace_value(name, value))
}

///|
pub fn DbcWorkspaceValueSet::find(
  self : DbcWorkspaceValueSet,
  name : String,
) -> DbcWorkspaceValue? {
  match self.find_index(name) {
    Some(index) => Some(self.values[index])
    None => None
  }
}

///|
pub fn DbcWorkspaceValueSet::contains(
  self : DbcWorkspaceValueSet,
  name : String,
) -> Bool {
  self.find(name) is Some(_)
}

///|
pub fn DbcWorkspaceValueSet::remove(
  self : DbcWorkspaceValueSet,
  name : String,
) -> Bool {
  match self.find_index(name) {
    Some(index) => {
      ignore(self.values.remove(index))
      self.revision += 1
      true
    }
    None => false
  }
}

///|
pub fn DbcWorkspaceValueSet::values(
  self : DbcWorkspaceValueSet,
) -> Array[DbcWorkspaceValue] {
  self.values.copy()
}

///|
pub fn DbcWorkspaceValueSet::names(
  self : DbcWorkspaceValueSet,
) -> Array[String] {
  self.values.map(item => item.name())
}

///|
pub fn DbcWorkspaceValueSet::revision(self : DbcWorkspaceValueSet) -> UInt {
  self.revision
}

///|
pub fn DbcWorkspaceValueSet::to_map(
  self : DbcWorkspaceValueSet,
) -> Map[String, Double] {
  let result : Map[String, Double] = Map([])
  for item in self.values {
    result[item.name()] = item.value()
  }
  result
}

///|
fn DbcWorkspaceValueSet::find_index(
  self : DbcWorkspaceValueSet,
  name : String,
) -> Int? {
  for index, item in self.values {
    if item.name() == name {
      return Some(index)
    }
  }
  None
}

///|
/// A message binding with a stable runtime name and mux selector.
pub struct DbcWorkspaceBinding {
  name : String
  message : Message
  selector : DbcWorkspaceMuxSelector
  extended : Bool
  mut default_values : DbcWorkspaceValueSet
  mut encoded : Int
  mut decoded : Int
}

///|
pub fn dbc_workspace_binding(
  name : String,
  message : Message,
  selector? : DbcWorkspaceMuxSelector = DbcWorkspaceMuxNone,
  extended? : Bool = false,
) -> DbcWorkspaceBinding {
  {
    name,
    message,
    selector,
    extended,
    default_values: new_dbc_workspace_value_set(),
    encoded: 0,
    decoded: 0,
  }
}

///|
pub fn DbcWorkspaceBinding::name(self : DbcWorkspaceBinding) -> String {
  self.name
}

///|
pub fn DbcWorkspaceBinding::message(self : DbcWorkspaceBinding) -> Message {
  self.message
}

///|
pub fn DbcWorkspaceBinding::selector(
  self : DbcWorkspaceBinding,
) -> DbcWorkspaceMuxSelector {
  self.selector
}

///|
pub fn DbcWorkspaceBinding::extended(self : DbcWorkspaceBinding) -> Bool {
  self.extended
}

///|
pub fn DbcWorkspaceBinding::set_default(
  self : DbcWorkspaceBinding,
  values : DbcWorkspaceValueSet,
) -> Unit {
  self.default_values = values
}

///|
pub fn DbcWorkspaceBinding::defaults(
  self : DbcWorkspaceBinding,
) -> DbcWorkspaceValueSet {
  self.default_values
}

///|
pub fn DbcWorkspaceBinding::encoded(self : DbcWorkspaceBinding) -> Int {
  self.encoded
}

///|
pub fn DbcWorkspaceBinding::decoded(self : DbcWorkspaceBinding) -> Int {
  self.decoded
}

///|
pub fn DbcWorkspaceBinding::matches_mux(
  self : DbcWorkspaceBinding,
  mux : UInt,
) -> Bool {
  match self.selector {
    DbcWorkspaceMuxNone => true
    DbcWorkspaceMuxValue(value) => mux == value
    DbcWorkspaceMuxRange(start, end) => mux >= start && mux <= end
  }
}

///|
pub fn DbcWorkspaceBinding::encode(
  self : DbcWorkspaceBinding,
  values : DbcWorkspaceValueSet,
) -> Frame raise DbcRuntimeError {
  let data = self.message.encode_values(values.to_map(), initial=[])
  self.encoded += 1
  if self.message.dlc() > 8 {
    fd_frame(self.message.id(), data, extended=self.extended) catch {
      _ => raise DbcRuntimeError::PayloadTooLong
    }
  } else {
    data_frame(self.message.id(), data, extended=self.extended) catch {
      _ => raise DbcRuntimeError::PayloadTooLong
    }
  }
}

///|
pub fn DbcWorkspaceBinding::decode(
  self : DbcWorkspaceBinding,
  frame : Frame,
) -> DecodedMessage raise DbcRuntimeError {
  self.decoded += 1
  self.message.decode(frame.data())
}

///|
/// A schema finding returned by workspace validation.
pub enum DbcWorkspaceIssue {
  DbcWorkspaceDuplicateId(UInt)
  DbcWorkspaceDuplicateName(String)
  DbcWorkspaceInvalidMessage(String)
  DbcWorkspaceMissingBinding(UInt)
  DbcWorkspaceEmptyGroup(String)
  DbcWorkspaceMuxConflict(String)
}

///|
pub fn dbc_workspace_issue_variants() -> Array[DbcWorkspaceIssue] {
  [
    DbcWorkspaceDuplicateId(0),
    DbcWorkspaceDuplicateName("example"),
    DbcWorkspaceInvalidMessage("example"),
    DbcWorkspaceMissingBinding(0),
    DbcWorkspaceEmptyGroup("example"),
    DbcWorkspaceMuxConflict("example"),
  ]
}

///|
/// A runtime registry for DBC schemas, groups and bindings.
pub struct DbcWorkspace {
  messages : Array[Message]
  groups : Array[DbcWorkspaceSignalGroup]
  bindings : Array[DbcWorkspaceBinding]
  mut revision : UInt
  mut encoded_frames : Int
  mut decoded_frames : Int
}

///|
pub fn new_dbc_workspace() -> DbcWorkspace {
  {
    messages: [],
    groups: [],
    bindings: [],
    revision: 0,
    encoded_frames: 0,
    decoded_frames: 0,
  }
}

///|
pub fn DbcWorkspace::add_message(self : DbcWorkspace, item : Message) -> Bool {
  if self.by_id(item.id()) is Some(_) || self.by_name(item.name()) is Some(_) {
    false
  } else {
    self.messages.push(item)
    self.revision += 1
    true
  }
}

///|
pub fn DbcWorkspace::replace_message(
  self : DbcWorkspace,
  item : Message,
) -> Bool {
  match self.find_message_index(item.id()) {
    Some(index) => {
      self.messages[index] = item
      self.revision += 1
      true
    }
    None => false
  }
}

///|
pub fn DbcWorkspace::add_group(
  self : DbcWorkspace,
  group : DbcWorkspaceSignalGroup,
) -> Bool {
  if self.find_group(group.name()) is Some(_) {
    false
  } else if group.length() == 0 {
    false
  } else {
    self.groups.push(group)
    self.revision += 1
    true
  }
}

///|
pub fn DbcWorkspace::add_binding(
  self : DbcWorkspace,
  binding : DbcWorkspaceBinding,
) -> Bool {
  if self.by_id(binding.message().id()) is None ||
    self.find_binding(binding.name()) is Some(_) {
    false
  } else {
    self.bindings.push(binding)
    self.revision += 1
    true
  }
}

///|
pub fn DbcWorkspace::messages(self : DbcWorkspace) -> Array[Message] {
  self.messages.copy()
}

///|
pub fn DbcWorkspace::groups(
  self : DbcWorkspace,
) -> Array[DbcWorkspaceSignalGroup] {
  self.groups.copy()
}

///|
pub fn DbcWorkspace::bindings(
  self : DbcWorkspace,
) -> Array[DbcWorkspaceBinding] {
  self.bindings.copy()
}

///|
pub fn DbcWorkspace::revision(self : DbcWorkspace) -> UInt {
  self.revision
}

///|
pub fn DbcWorkspace::encoded_frames(self : DbcWorkspace) -> Int {
  self.encoded_frames
}

///|
pub fn DbcWorkspace::decoded_frames(self : DbcWorkspace) -> Int {
  self.decoded_frames
}

///|
pub fn DbcWorkspace::by_id(self : DbcWorkspace, id : UInt) -> Message? {
  for item in self.messages {
    if item.id() == id {
      return Some(item)
    }
  }
  None
}

///|
pub fn DbcWorkspace::by_name(self : DbcWorkspace, name : String) -> Message? {
  for item in self.messages {
    if item.name() == name {
      return Some(item)
    }
  }
  None
}

///|
pub fn DbcWorkspace::find_group(
  self : DbcWorkspace,
  name : String,
) -> DbcWorkspaceSignalGroup? {
  for item in self.groups {
    if item.name() == name {
      return Some(item)
    }
  }
  None
}

///|
pub fn DbcWorkspace::find_binding(
  self : DbcWorkspace,
  name : String,
) -> DbcWorkspaceBinding? {
  for item in self.bindings {
    if item.name() == name {
      return Some(item)
    }
  }
  None
}

///|
/// Encode a named binding into a CAN frame.
pub fn DbcWorkspace::encode(
  self : DbcWorkspace,
  name : String,
  values : DbcWorkspaceValueSet,
) -> Frame raise DbcRuntimeError {
  match self.find_binding(name) {
    Some(binding) => {
      let frame = binding.encode(values)
      self.encoded_frames += 1
      frame
    }
    None => raise DbcRuntimeError::MissingSignalValue
  }
}

///|
/// Decode a frame using its message identifier.
pub fn DbcWorkspace::decode(
  self : DbcWorkspace,
  frame : Frame,
) -> DecodedMessage? raise DbcRuntimeError {
  match self.by_id(frame.id()) {
    Some(message) => {
      self.decoded_frames += 1
      Some(message.decode(frame.data()))
    }
    None => None
  }
}

///|
/// Return all message identifiers sorted for deterministic reports.
pub fn DbcWorkspace::ids(self : DbcWorkspace) -> Array[UInt] {
  let result = self.messages.map(item => item.id())
  result.sort()
  result
}

///|
/// Validate messages, groups, bindings and duplicate identifiers.
pub fn DbcWorkspace::validate(self : DbcWorkspace) -> Array[DbcWorkspaceIssue] {
  let issues : Array[DbcWorkspaceIssue] = []
  for index, left in self.messages {
    if !left.validate() {
      issues.push(DbcWorkspaceInvalidMessage(left.name()))
    }
    for right in self.messages[index + 1:] {
      if left.id() == right.id() {
        issues.push(DbcWorkspaceDuplicateId(left.id()))
      }
      if left.name() == right.name() {
        issues.push(DbcWorkspaceDuplicateName(left.name()))
      }
    }
  }
  for group in self.groups {
    if group.length() == 0 {
      issues.push(DbcWorkspaceEmptyGroup(group.name()))
    } else if self.by_id(group.message_id()) is None {
      issues.push(DbcWorkspaceMissingBinding(group.message_id()))
    }
  }
  for binding in self.bindings {
    if self.by_id(binding.message().id()) is None {
      issues.push(DbcWorkspaceMissingBinding(binding.message().id()))
    }
  }
  issues
}

///|
pub fn dbc_workspace_issue_text(issue : DbcWorkspaceIssue) -> String {
  match issue {
    DbcWorkspaceDuplicateId(id) => "duplicate id " + id.to_string()
    DbcWorkspaceDuplicateName(name) => "duplicate name " + name
    DbcWorkspaceInvalidMessage(name) => "invalid message " + name
    DbcWorkspaceMissingBinding(id) => "missing message " + id.to_string()
    DbcWorkspaceEmptyGroup(name) => "empty group " + name
    DbcWorkspaceMuxConflict(name) => "mux conflict " + name
  }
}

///|
/// Return a stable, human-readable workspace schema summary.
pub fn DbcWorkspace::schema_text(self : DbcWorkspace) -> String {
  let lines : Array[String] = []
  for item in self.messages {
    lines.push(
      "BO_ " +
      item.id().to_string() +
      " " +
      item.name() +
      " " +
      item.dlc().to_string() +
      " " +
      item.signal_count().to_string(),
    )
    for signal in item.signals() {
      lines.push(
        "  SG_ " +
        signal.name() +
        " " +
        signal.start_bit().to_string() +
        " " +
        signal.size().to_string(),
      )
    }
  }
  lines.join("\n")
}

///|
/// Return a compact fingerprint for cache keys and CI artifacts.
pub fn DbcWorkspace::fingerprint(self : DbcWorkspace) -> UInt {
  let mut hash : UInt = 2166136261
  for byte in self.schema_text() {
    hash = (hash ^ byte.to_uint()) * 16777619
  }
  hash
}

///|
/// Create a workspace from a parsed DBC document.
pub fn dbc_workspace_from_text(input : String) -> DbcWorkspace raise DbcError {
  let workspace = new_dbc_workspace()
  for item in parse_dbc(input) {
    ignore(workspace.add_message(item))
  }
  workspace
}

///|
/// Build a value set from parallel signal names and values.
pub fn dbc_workspace_values(
  names : Array[String],
  values : Array[Double],
) -> DbcWorkspaceValueSet {
  let result = new_dbc_workspace_value_set()
  let count = if names.length() < values.length() {
    names.length()
  } else {
    values.length()
  }
  for index in 0.. Bool {
  match selector {
    DbcWorkspaceMuxNone => true
    DbcWorkspaceMuxValue(expected) => value == expected
    DbcWorkspaceMuxRange(start, end) => value >= start && value <= end
  }
}

///|
fn DbcWorkspace::find_message_index(self : DbcWorkspace, id : UInt) -> Int? {
  for index, item in self.messages {
    if item.id() == id {
      return Some(index)
    }
  }
  None
}