///|
pub(all) enum Severity {
  Info
  Warning
  Error
} derive(Eq, Debug)

///|
pub(all) enum ParsePolicy {
  Strict
  Lenient
} derive(Eq, Debug)

///|
pub(all) enum ChecksumStatus {
  PresentValid
  PresentInvalid
  MissingChecksum
  NotChecked
} derive(Eq, Debug)

///|
pub(all) enum NmeaErrorCode {
  FramingMissingStart
  FramingInvalidPrefix
  FramingMissingChecksumDelimiter
  FramingTooLong
  ChecksumMissing
  ChecksumMalformed
  ChecksumMismatch
  FieldMissing
  FieldEmpty
  FieldInvalidInteger
  FieldInvalidDecimal
  CoordinateInvalid
  DateInvalid
  TimeInvalid
  SentenceUnsupported
  SentenceInvalid
  StreamLimitExceeded
  GsvAssemblyInvalid
  FusionConflict
  GeographicInvalid
  TrackQualityIssue
  IntegrityInvalid
} derive(Eq, Debug)

///|
pub(all) struct SourceRef {
  record_index : Int
  field_index : Int?
  byte_offset : Int?
} derive(Eq, Debug)

///|
pub(all) struct Diagnostic {
  code : String
  severity : Severity
  message : String
  source : SourceRef
} derive(Eq, Debug)

///|
pub(all) struct NmeaError {
  diagnostic : Diagnostic
} derive(Eq, Debug)

///|
pub struct SentenceResult[T] {
  record_index_value : Int
  formatter_value : String?
  checksum_status_value : ChecksumStatus
  value_value : T?
  error_value : NmeaError?
} derive(Eq, Debug)

///|
pub struct BatchResult[T] {
  item_values : Array[SentenceResult[T]]
} derive(Eq, Debug)

///|
pub fn SourceRef::sentence(record_index : Int) -> SourceRef {
  { record_index, field_index: None, byte_offset: None }
}

///|
pub fn SourceRef::byte(record_index : Int, byte_offset : Int) -> SourceRef {
  { record_index, field_index: None, byte_offset: Some(byte_offset) }
}

///|
pub fn SourceRef::field(
  record_index : Int,
  field_index : Int,
  byte_offset? : Int? = None,
) -> SourceRef {
  { record_index, field_index: Some(field_index), byte_offset }
}

///|
pub fn SourceRef::record_index(self : SourceRef) -> Int {
  self.record_index
}

///|
pub fn SourceRef::field_index(self : SourceRef) -> Int? {
  self.field_index
}

///|
pub fn SourceRef::byte_offset(self : SourceRef) -> Int? {
  self.byte_offset
}

///|
pub fn NmeaErrorCode::code(self : NmeaErrorCode) -> String {
  match self {
    FramingMissingStart => "framing.missing_start"
    FramingInvalidPrefix => "framing.invalid_prefix"
    FramingMissingChecksumDelimiter => "framing.missing_checksum_delimiter"
    FramingTooLong => "framing.too_long"
    ChecksumMissing => "checksum.missing"
    ChecksumMalformed => "checksum.malformed"
    ChecksumMismatch => "checksum.mismatch"
    FieldMissing => "field.missing"
    FieldEmpty => "field.empty"
    FieldInvalidInteger => "field.invalid_integer"
    FieldInvalidDecimal => "field.invalid_decimal"
    CoordinateInvalid => "coordinate.invalid"
    DateInvalid => "date.invalid"
    TimeInvalid => "time.invalid"
    SentenceUnsupported => "sentence.unsupported"
    SentenceInvalid => "sentence.invalid"
    StreamLimitExceeded => "stream.limit_exceeded"
    GsvAssemblyInvalid => "gsv.assembly_invalid"
    FusionConflict => "fusion.conflict"
    GeographicInvalid => "geo.invalid"
    TrackQualityIssue => "track.quality_issue"
    IntegrityInvalid => "integrity.invalid"
  }
}

///|
pub fn Diagnostic::new(
  code : NmeaErrorCode,
  severity : Severity,
  message : String,
  source : SourceRef,
) -> Diagnostic {
  { code: code.code(), severity, message, source }
}

///|
pub fn Diagnostic::code(self : Diagnostic) -> String {
  self.code
}

///|
pub fn Diagnostic::severity(self : Diagnostic) -> Severity {
  self.severity
}

///|
pub fn Diagnostic::message(self : Diagnostic) -> String {
  self.message
}

///|
pub fn Diagnostic::source(self : Diagnostic) -> SourceRef {
  self.source
}

///|
pub fn NmeaError::from_diagnostic(diagnostic : Diagnostic) -> NmeaError {
  { diagnostic, }
}

///|
pub fn NmeaError::diagnostic(self : NmeaError) -> Diagnostic {
  self.diagnostic
}

///|
pub fn NmeaError::code(self : NmeaError) -> String {
  self.diagnostic.code()
}

///|
pub fn NmeaError::severity(self : NmeaError) -> Severity {
  self.diagnostic.severity()
}

///|
pub fn[T] SentenceResult::ok(
  record_index : Int,
  formatter : String,
  checksum_status : ChecksumStatus,
  value? : T? = None,
) -> SentenceResult[T] {
  {
    record_index_value: record_index,
    formatter_value: Some(formatter),
    checksum_status_value: checksum_status,
    value_value: value,
    error_value: None,
  }
}

///|
pub fn[T] SentenceResult::err(
  record_index : Int,
  checksum_status : ChecksumStatus,
  error : NmeaError,
) -> SentenceResult[T] {
  {
    record_index_value: record_index,
    formatter_value: None,
    checksum_status_value: checksum_status,
    value_value: None,
    error_value: Some(error),
  }
}

///|
pub fn[T] SentenceResult::record_index(self : SentenceResult[T]) -> Int {
  self.record_index_value
}

///|
pub fn[T] SentenceResult::formatter(self : SentenceResult[T]) -> String? {
  self.formatter_value
}

///|
pub fn[T] SentenceResult::checksum_status(
  self : SentenceResult[T],
) -> ChecksumStatus {
  self.checksum_status_value
}

///|
pub fn[T] SentenceResult::value(self : SentenceResult[T]) -> T? {
  self.value_value
}

///|
pub fn[T] SentenceResult::error(self : SentenceResult[T]) -> NmeaError? {
  self.error_value
}

///|
pub fn[T] BatchResult::new(items : Array[SentenceResult[T]]) -> BatchResult[T] {
  { item_values: items.copy() }
}

///|
pub fn[T] BatchResult::items(self : BatchResult[T]) -> Array[SentenceResult[T]] {
  self.item_values.copy()
}

///|
pub fn[T] BatchResult::count(self : BatchResult[T]) -> Int {
  self.item_values.length()
}