///|
pub(all) enum PlaylistKind {
  Empty
  Media
  Master
  Hybrid
} derive(Eq, @debug.Debug)

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

///|
pub(all) enum ParseError {
  MissingHeader(Int)
  InvalidTag(Int, String)
  InvalidAttribute(Int, String)
  InvalidNumber(Int, String, String)
  UriWithoutContext(Int, String)
  MissingSegmentUri(Int)
  MissingVariantUri(Int)
} derive(Eq, @debug.Debug)

///|
pub(all) struct HlsAttribute {
  name : String
  value : String
  quoted : Bool
} derive(Eq, @debug.Debug)

///|
pub(all) struct ByteRange {
  length : Int
  offset : Int?
} derive(Eq, @debug.Debug)

///|
pub(all) struct KeyInfo {
  key_method : String
  uri : String?
  iv : String?
  key_format : String?
  key_format_versions : String?
  line : Int
} derive(Eq, @debug.Debug)

///|
pub(all) struct SegmentMap {
  uri : String
  byte_range : ByteRange?
  line : Int
} derive(Eq, @debug.Debug)

///|
pub(all) struct Segment {
  uri : String
  title : String
  duration_ms : Int
  byte_range : ByteRange?
  key : KeyInfo?
  map : SegmentMap?
  discontinuity : Bool
  program_date_time : String?
  line : Int
} derive(Eq, @debug.Debug)

///|
pub(all) struct Resolution {
  width : Int
  height : Int
} derive(Eq, @debug.Debug)

///|
pub(all) struct VariantStream {
  uri : String
  bandwidth : Int
  average_bandwidth : Int?
  codecs : String?
  resolution : Resolution?
  frame_rate_milli : Int?
  audio_group : String?
  subtitles_group : String?
  closed_captions : String?
  line : Int
} derive(Eq, @debug.Debug)

///|
pub(all) struct Rendition {
  rendition_type : String
  group_id : String
  name : String
  language : String?
  uri : String?
  default : Bool
  autoselect : Bool
  forced : Bool
  line : Int
} derive(Eq, @debug.Debug)

///|
pub(all) struct HlsPlaylist {
  version : Int
  target_duration : Int
  media_sequence : Int
  discontinuity_sequence : Int
  start_offset_ms : Int?
  end_list : Bool
  independent_segments : Bool
  segments : Array[Segment]
  variants : Array[VariantStream]
  renditions : Array[Rendition]
  unknown_tags : Array[String]
  kind : PlaylistKind
} derive(Eq, @debug.Debug)

///|
pub(all) struct ValidationIssue {
  severity : Severity
  code : String
  message : String
  line : Int
} derive(Eq, @debug.Debug)

///|
pub(all) struct ValidationReport {
  ok : Bool
  errors : Int
  warnings : Int
  infos : Int
  issues : Array[ValidationIssue]
} derive(Eq, @debug.Debug)

///|
pub(all) struct PlaylistStats {
  kind : PlaylistKind
  segment_count : Int
  variant_count : Int
  rendition_count : Int
  total_duration_ms : Int
  max_segment_ms : Int
  encrypted_segments : Int
  discontinuities : Int
} derive(Eq, @debug.Debug)

///|
pub(all) enum UriRole {
  SegmentUri
  VariantUri
  RenditionUri
  KeyUri
  MapUri
} derive(Eq, @debug.Debug)

///|
pub(all) struct UriEntry {
  role : UriRole
  uri : String
  line : Int
} derive(Eq, @debug.Debug)

///|
pub(all) struct UriAudit {
  total : Int
  absolute_uris : Int
  relative_uris : Int
  data_uris : Int
  empty_uris : Int
  parent_traversal_uris : Int
  entries : Array[UriEntry]
} derive(Eq, @debug.Debug)

///|
pub(all) struct LadderReport {
  variants : Int
  lowest_bandwidth : Int
  highest_bandwidth : Int
  monotonic : Bool
  duplicate_bandwidths : Int
  has_resolution_gap : Bool
} derive(Eq, @debug.Debug)

///|
pub fn ParseError::message(self : ParseError) -> String {
  match self {
    MissingHeader(line) =>
      "line \{line}: expected #EXTM3U before playlist content"
    InvalidTag(line, reason) => "line \{line}: invalid tag: \{reason}"
    InvalidAttribute(line, reason) =>
      "line \{line}: invalid attribute list: \{reason}"
    InvalidNumber(line, field, value) =>
      "line \{line}: invalid number for \{field}: \{value}"
    UriWithoutContext(line, uri) =>
      "line \{line}: uri has no pending segment or variant tag: \{uri}"
    MissingSegmentUri(line) =>
      "line \{line}: #EXTINF must be followed by a media segment URI"
    MissingVariantUri(line) =>
      "line \{line}: #EXT-X-STREAM-INF must be followed by a variant URI"
  }
}

///|
pub fn ValidationIssue::error(
  code : String,
  message : String,
  line~ : Int = 0,
) -> ValidationIssue {
  { severity: Error, code, message, line }
}

///|
pub fn ValidationIssue::warning(
  code : String,
  message : String,
  line~ : Int = 0,
) -> ValidationIssue {
  { severity: Warning, code, message, line }
}

///|
pub fn ValidationIssue::info(
  code : String,
  message : String,
  line~ : Int = 0,
) -> ValidationIssue {
  { severity: Info, code, message, line }
}

///|
pub fn HlsPlaylist::empty() -> HlsPlaylist {
  {
    version: 0,
    target_duration: 0,
    media_sequence: 0,
    discontinuity_sequence: 0,
    start_offset_ms: None,
    end_list: false,
    independent_segments: false,
    segments: [],
    variants: [],
    renditions: [],
    unknown_tags: [],
    kind: Empty,
  }
}