///|
/// Severity of a diagnostic; warnings do not invalidate a database.
pub(all) enum Severity {
  Error
  Warning
} derive(Eq, @debug.Debug)

///|
/// One-based line/column and zero-based UTF-16 code-unit offset.
pub(all) struct SourceLocation {
  line : Int
  column : Int
  offset : Int
} derive(Eq, @debug.Debug)

///|
/// Stable code, readable explanation and source/object context.
pub(all) struct Diagnostic {
  severity : Severity
  code : String
  message : String
  location : SourceLocation
  message_name : String?
  signal_name : String?
} derive(Eq, @debug.Debug)

///|
/// Parsing and codec failures carry the same structured diagnostic format.
pub suberror DbcError {
  Failure(Diagnostic)
} derive(@debug.Debug)

///|
fn diagnostic(
  code : String,
  message : String,
  location? : SourceLocation = { line: 0, column: 0, offset: 0 },
  message_name? : String? = None,
  signal_name? : String? = None,
  severity? : Severity = Error,
) -> Diagnostic {
  { severity, code, message, location, message_name, signal_name }
}

///|
fn fail(code : String, message : String) -> Unit raise DbcError {
  raise Failure(diagnostic(code, message))
}

///|
/// Intel's start is the LSB; Motorola's start is the MSB (DBC sawtooth).
pub(all) enum ByteOrder {
  Intel
  Motorola
} derive(Eq, @debug.Debug)

///|
/// Basic multiplexing: one unsigned selector and equality-selected branches.
pub(all) enum MultiplexInfo {
  Always
  Multiplexer
  Branch(Int64)
} derive(Eq, @debug.Debug)

///|
/// Encoding range behavior. Reject is the default; Clamp is always explicit.
pub(all) enum RangePolicy {
  Ignore
  Warn
  Reject
  Clamp
} derive(Eq, @debug.Debug)

///|
/// Strict rejects unknown statements; permissive preserves noncritical ones.
pub(all) enum ParseMode {
  Strict
  Permissive
} derive(Eq, @debug.Debug)

///|
/// Normalized arbitration ID, with frame kind participating in identity.
pub(all) struct FrameId {
  id : UInt
  extended : Bool
} derive(Eq, @debug.Debug)

///|
/// Create a checked standard (11-bit) or extended (29-bit) identifier.
pub fn FrameId::new(
  id : UInt,
  extended? : Bool = false,
) -> FrameId raise DbcError {
  if id > (if extended { 0x1FFFFFFFU } else { 0x7FFU }) {
    fail("id.range", "arbitration ID exceeds frame kind")
  }
  { id, extended }
}

///|
/// Interpret bit 31 as DBC's extended-ID flag; bits 29/30 are rejected.
pub fn frame_id_from_dbc(raw : UInt) -> FrameId raise DbcError {
  if (raw & 0x60000000U) != 0U {
    fail("id.flags", "unsupported DBC identifier flags")
  }
  FrameId::new(raw & 0x1FFFFFFFU, extended=(raw & 0x80000000U) != 0U)
}

///|
/// Convert a normalized ID to its conventional DBC representation.
pub fn FrameId::to_dbc(self : FrameId) -> UInt {
  self.id | (if self.extended { 0x80000000U } else { 0U })
}

///|
/// Exact raw values retain all 64 bits; doubles are reserved for physical data.
pub(all) enum RawValue {
  Unsigned(UInt64)
  Signed(Int64)
} derive(Eq, @debug.Debug)

///|
/// Named raw value, independent of factor/offset.
pub(all) struct ValueDescription {
  raw : Int64
  label : String
} derive(Eq, @debug.Debug)

///|
/// A participant in the database's network topology.
pub(all) struct Node {
  name : String
  location : SourceLocation
} derive(Eq, @debug.Debug)

///|
/// Integer DBC signal with complete core layout/scaling metadata.
pub(all) struct Signal {
  name : String
  start_bit : Int
  bit_length : Int
  byte_order : ByteOrder
  signed : Bool
  factor : Double
  offset : Double
  minimum : Double
  maximum : Double
  unit : String
  receivers : Array[String]
  multiplex : MultiplexInfo
  values : Array[ValueDescription]
  location : SourceLocation
} derive(Eq, @debug.Debug)

///|
/// Define a signal; semantic validation remains available for programmatic models.
pub fn Signal::new(
  name : String,
  start_bit : Int,
  bit_length : Int,
  byte_order? : ByteOrder = Intel,
  signed? : Bool = false,
  factor? : Double = 1.0,
  offset? : Double = 0.0,
  minimum? : Double = 0.0,
  maximum? : Double = 0.0,
  unit? : String = "",
  receivers? : Array[String] = [],
  multiplex? : MultiplexInfo = Always,
) -> Signal {
  {
    name,
    start_bit,
    bit_length,
    byte_order,
    signed,
    factor,
    offset,
    minimum,
    maximum,
    unit,
    receivers,
    multiplex,
    values: [],
    location: { line: 0, column: 0, offset: 0 },
  }
}

///|
/// DBC payload length is bytes, not a transport DLC code.
pub(all) struct Message {
  raw_dbc_id : UInt
  frame_id : FrameId
  name : String
  length : Int
  transmitter : String
  signals : Array[Signal]
  location : SourceLocation
} derive(Eq, @debug.Debug)

///|
/// Construct an in-memory message retaining its conventional DBC ID.
pub fn Message::new(
  frame_id : FrameId,
  name : String,
  length : Int,
  transmitter? : String = "Vector__XXX",
  signals? : Array[Signal] = [],
) -> Message {
  {
    raw_dbc_id: frame_id.to_dbc(),
    frame_id,
    name,
    length,
    transmitter,
    signals,
    location: { line: 0, column: 0, offset: 0 },
  }
}

///|
/// Scope of attributes; object references use original DBC IDs.
pub(all) enum Scope {
  DatabaseScope
  NodeScope
  MessageScope
  SignalScope
} derive(Eq, @debug.Debug)

///|
/// Object addressed by comments or attributes.
pub(all) enum ObjectRef {
  DatabaseRef
  NodeRef(String)
  MessageRef(UInt)
  SignalRef(UInt, String)
} derive(Eq, Hash, @debug.Debug)

///|
/// Typed attribute grammar; bounds are physical numeric bounds.
pub(all) enum AttributeType {
  IntType(Int64, Int64)
  HexType(Int64, Int64)
  FloatType(Double, Double)
  StringType
  EnumType(Array[String])
} derive(Eq, @debug.Debug)

///|
/// Attribute data; enum assignments are zero-based integer indices.
pub(all) enum AttributeValue {
  Integer(Int64)
  Real(Double)
  Text(String)
} derive(Eq, @debug.Debug)

///|
/// Attribute schema and scope.
pub(all) struct AttributeDefinition {
  name : String
  scope : Scope
  kind : AttributeType
  location : SourceLocation
} derive(Eq, @debug.Debug)

///|
/// Attribute assignment/default (None denotes a default).
pub(all) struct Attribute {
  name : String
  target : ObjectRef?
  value : AttributeValue
  location : SourceLocation
} derive(Eq, @debug.Debug)

///|
/// Database, node, message or signal comment, preserving UTF-8 text.
pub(all) struct Comment {
  target : ObjectRef
  text : String
  location : SourceLocation
} derive(Eq, @debug.Debug)

///|
/// Preserved permissive-mode extension with an explicit warning.
pub(all) struct UnsupportedStatement {
  keyword : String
  text : String
  location : SourceLocation
} derive(Eq, @debug.Debug)

///|
/// Semantic database; parsing implementation details are private.
pub(all) struct Database {
  mut version : String
  nodes : Array[Node]
  messages : Array[Message]
  comments : Array[Comment]
  attribute_definitions : Array[AttributeDefinition]
  attributes : Array[Attribute]
  unsupported : Array[UnsupportedStatement]
  diagnostics : Array[Diagnostic]
}

///|
/// Create an empty semantic database.
pub fn Database::new() -> Database {
  {
    version: "",
    nodes: [],
    messages: [],
    comments: [],
    attribute_definitions: [],
    attributes: [],
    unsupported: [],
    diagnostics: [],
  }
}

///|
/// Query an ID with frame kind (standard and extended IDs may coexist).
pub fn Database::message_by_id(self : Database, id : FrameId) -> Message? {
  for m in self.messages {
    if m.frame_id == id {
      return Some(m)
    }
  }
  None
}

///|
/// Query a message name.
pub fn Database::message_by_name(self : Database, name : String) -> Message? {
  for m in self.messages {
    if m.name == name {
      return Some(m)
    }
  }
  None
}

///|
/// Query a declared node.
pub fn Database::node_by_name(self : Database, name : String) -> Node? {
  for n in self.nodes {
    if n.name == name {
      return Some(n)
    }
  }
  None
}

///|
/// Query a signal within a message.
pub fn Message::signal_by_name(self : Message, name : String) -> Signal? {
  for s in self.signals {
    if s.name == name {
      return Some(s)
    }
  }
  None
}

///|
/// Search signals across messages, returning their containing message too.
pub fn Database::find_signals(
  self : Database,
  name : String,
) -> Array[(Message, Signal)] {
  let found = []
  for m in self.messages {
    for s in m.signals {
      if s.name == name {
        found.push((m, s))
      }
    }
  }
  found
}

///|
/// In-memory frame; hardware IO is deliberately outside the library.
pub(all) struct CanFrame {
  frame_id : FrameId
  payload : Array[Byte]
} derive(Eq, @debug.Debug)

///|
/// Parsed input resource budgets. All lengths use UTF-16 code units.
pub(all) struct ParseLimits {
  max_file_size : Int
  max_line_length : Int
  max_token_length : Int
  max_name_length : Int
  max_messages : Int
  max_signals_per_message : Int
  max_total_signals : Int
  max_attribute_size : Int
  max_nodes : Int
  max_tokens : Int
  max_metadata_records : Int
} derive(Eq, @debug.Debug)

///|
/// Conservative defaults for untrusted input, including 10,000 signals.
pub fn ParseLimits::default() -> ParseLimits {
  {
    max_file_size: 16 * 1024 * 1024,
    max_line_length: 65536,
    max_token_length: 65536,
    max_name_length: 128,
    max_messages: 10000,
    max_signals_per_message: 512,
    max_total_signals: 100000,
    max_attribute_size: 65536,
    max_nodes: 10000,
    max_tokens: 2000000,
    max_metadata_records: 100000,
  }
}

///|
/// Mode and explicit resource policy.
pub(all) struct ParseOptions {
  mode : ParseMode
  limits : ParseLimits
} derive(Eq, @debug.Debug)

///|
/// Strict parsing with bounded inputs.
pub fn ParseOptions::default() -> ParseOptions {
  { mode: Strict, limits: ParseLimits::default() }
}