///|
/// A presentation and its original container time, expressed in timescale
/// ticks. packet_index identifies the encoded sample; packets without a
/// presentation are consumed but do not produce a frame.
pub struct Av1ContainerFrame {
  packet_index : Int
  timestamp : Int64?
  duration : Int64?
  timescale : Int64
  frame : Av1NativeFrame
}

///|
/// Result classification for a timestamp seek. `Next` means the selected
/// presentation is the first one in container decode order whose known PTS is
/// later than the request; it does not assert that the media has a gap.
pub(all) enum Av1ContainerSeekOutcome {
  Exact
  Next
  End
  UnknownTimestamp
} derive(Eq, Debug)

///|
/// A timestamp seek result. `frame` is present for `Exact`, `Next` and
/// `UnknownTimestamp`; `End` has no frame. Timestamps use the track's raw
/// media timescale. MP4 edit lists are applied only through the explicit
/// `Av1PresentationTimeline` API.
pub struct Av1ContainerSeekResult {
  requested_timestamp : Int64
  outcome : Av1ContainerSeekOutcome
  frame : Av1ContainerFrame?
}

///|
/// Owned compressed packets with lazy pixel reconstruction. Container tables
/// are checked at construction, while AV1 syntax/entropy failures may occur
/// later. Such failures remain sticky until reset(). Existing frames survive
/// advancing, reset and destruction of this session.
pub struct Av1ContainerDecoder {
  priv track : Av1DemuxedTrack
  priv limits : DecodeLimits
  priv mut decoder : Av1VideoDecoder
  priv mut next_index : Int
  priv mut failure : DecodeError?
  // A true entry means the packet was decoded successfully by a fresh AV1
  // decoder and is safe as a random-access start. A candidate is decoded
  // again when selected so that the fresh decoder state is restored without
  // sharing mutable reference slots between seeks.
  priv verified_random_access : Array[Bool]
}

///|
pub fn Av1ContainerDecoder::new(
  data : Array[Byte],
  format? : Av1ContainerFormat? = None,
  track_id? : Int64? = None,
  limits? : DecodeLimits = DecodeLimits::default(),
) -> Result[Av1ContainerDecoder, DecodeError] {
  let track = match av1_demux(data, format~, track_id~, limits~) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({
    track,
    limits,
    decoder: av1_video_decoder(),
    next_index: 0,
    failure: None,
    verified_random_access: Array::make(track.packets.length(), false),
  })
}

///|
pub fn Av1ContainerDecoder::format(
  self : Av1ContainerDecoder,
) -> Av1ContainerFormat {
  self.track.format
}

///|
pub fn Av1ContainerDecoder::timescale(self : Av1ContainerDecoder) -> Int64 {
  self.track.timescale
}

///|
/// Return the explicit presentation mapping for this track. The decoder's
/// default `next_frame`/`seek_timestamp` APIs continue to expose raw media
/// timestamps; callers opt into MP4 edits through this value.
pub fn Av1ContainerDecoder::presentation_timeline(
  self : Av1ContainerDecoder,
) -> Result[Av1PresentationTimeline, DecodeError] {
  Av1PresentationTimeline::new(self.track)
}

///|
pub fn Av1ContainerDecoder::packet_count(self : Av1ContainerDecoder) -> Int {
  self.track.packets.length()
}

///|
/// Index of the next encoded packet, not a count of displayed pictures.
pub fn Av1ContainerDecoder::position(self : Av1ContainerDecoder) -> Int {
  self.next_index
}

///|
pub fn Av1ContainerDecoder::reset(self : Av1ContainerDecoder) -> Unit {
  self.decoder = av1_video_decoder()
  self.next_index = 0
  self.failure = None
}

///|
fn Av1ContainerDecoder::record_failure_at(
  self : Av1ContainerDecoder,
  index : Int,
  error : DecodeError,
) -> Result[Av1ContainerFrame?, DecodeError] {
  // An OBU offset is relative to a copied/assembled packet, not the file.
  let failure = {
    ..error,
    byte_offset: None,
    context: "container.packet/" + error.context,
    message: "Packet \{index}: \{error.message}",
  }
  self.failure = Some(failure)
  Err(failure)
}

///|
fn Av1ContainerDecoder::record_seek_failure(
  self : Av1ContainerDecoder,
  index : Int,
  error : DecodeError,
) -> Result[Av1ContainerSeekResult, DecodeError] {
  let failure = {
    ..error,
    byte_offset: None,
    context: "container.packet/" + error.context,
    message: "Packet \{index}: \{error.message}",
  }
  self.failure = Some(failure)
  Err(failure)
}

///|
fn Av1ContainerDecoder::record_failure(
  self : Av1ContainerDecoder,
  error : DecodeError,
) -> Result[Av1ContainerFrame?, DecodeError] {
  self.record_failure_at(self.next_index, error)
}

///|
/// Assemble and decode one owned container packet. The caller supplies the
/// decoder and budget so seek candidate validation and replay can share one
/// mutable reference state and one DecodeLimits budget.
fn av1_container_decode_packet(
  track : Av1DemuxedTrack,
  limits : DecodeLimits,
  decoder : Av1VideoDecoder,
  index : Int,
  budget : DecodeBudget,
  fresh? : Bool = false,
) -> Result[Array[Av1FramePlanes], DecodeError] {
  if index < 0 || index >= track.packets.length() {
    return Err(
      decode_error(
        InvalidState,
        "container.seek",
        "Packet index is outside the track",
      ),
    )
  }
  // Failed random-access probes share work counters with fallback decoding,
  // but their diagnostic must not mask a later MissingReference/LimitExceeded.
  if budget.diagnostic is Some(trace) {
    trace.error = None
    trace.context = "container.packet"
    trace.byte_offset = None
  }
  let packet = track.packets[index]
  let prefix = if index == 0 || fresh { track.codec_initialization } else { [] }
  if packet.data.length() > limits.max_input_bytes ||
    prefix.length() > limits.max_input_bytes - packet.data.length() {
    return Err(
      decode_error(
        LimitExceeded,
        "payload",
        "Initialization and sample exceed max_input_bytes",
      ),
    )
  }
  let sample = if prefix.is_empty() {
    packet.data
  } else {
    Array::makei(prefix.length() + packet.data.length(), i => {
      if i < prefix.length() {
        prefix[i]
      } else {
        packet.data[i - prefix.length()]
      }
    })
  }
  match
    av1_video_decode_temporal_unit_planes(
      decoder,
      sample,
      allow_monochrome=true,
      budget=Some(budget),
    ) {
    Some(value) => Ok(value)
    None => Err(decode_failure(budget))
  }
}

///|
/// Try a sync-marked packet (or packet zero) with an independent decoder.
/// A syntax/reference rejection only disqualifies this candidate; a resource
/// limit aborts the seek because the same budget must cover every attempt.
fn Av1ContainerDecoder::check_random_access(
  self : Av1ContainerDecoder,
  index : Int,
  budget : DecodeBudget,
) -> Result[(Bool, Av1VideoDecoder?, Array[Av1FramePlanes]), DecodeError] {
  let probe = av1_video_decoder()
  let decoded = match
    av1_container_decode_packet(
      self.track,
      self.limits,
      probe,
      index,
      budget,
      fresh=true,
    ) {
    Ok(value) => value
    Err(error) => {
      if error.kind == LimitExceeded {
        return Err(error)
      }
      return Ok((false, None, []))
    }
  }
  // A metadata-only or hidden-only successful parse does not by itself prove
  // a usable independent reference state. Fall back to an earlier candidate.
  if decoded.length() != 1 {
    return Ok((false, None, []))
  }
  let standalone = probe.sequence
    is Some({ reduced_still_picture_header: true, .. })
  if !standalone {
    for slot in 0.. Int? {
  let mut index = before
  while index >= 0 {
    if index == 0 ||
      self.track.packets[index].sync_sample == Some(true) ||
      self.verified_random_access[index] {
      return Some(index)
    }
    index -= 1
  }
  None
}

///|
fn av1_container_frame(
  track : Av1DemuxedTrack,
  index : Int,
  planes : Av1FramePlanes,
) -> Av1ContainerFrame {
  let packet = track.packets[index]
  {
    packet_index: index,
    timestamp: packet.presentation_timestamp,
    duration: packet.duration,
    timescale: track.timescale,
    frame: av1_frame_to_native(planes),
  }
}

///|
fn av1_container_seek_result(
  requested : Int64,
  track : Av1DemuxedTrack,
  index : Int,
  planes : Av1FramePlanes,
) -> Av1ContainerSeekResult {
  let timestamp = track.packets[index].presentation_timestamp
  let outcome = match timestamp {
    Some(value) => if value == requested { Exact } else { Next }
    None => UnknownTimestamp
  }
  {
    requested_timestamp: requested,
    outcome,
    frame: Some(av1_container_frame(track, index, planes)),
  }
}

///|
/// Scan from a clean reference state in decode order. This path is required
/// for unknown or non-monotone packet timing; sorting PTS values would change
/// the AV1 dependency order and would misclassify a known non-monotone track.
fn Av1ContainerDecoder::seek_from_start(
  self : Av1ContainerDecoder,
  requested : Int64,
  budget : DecodeBudget,
) -> Result[Av1ContainerSeekResult, DecodeError] {
  self.decoder = av1_video_decoder()
  self.next_index = 0
  while self.next_index < self.track.packets.length() {
    let index = self.next_index
    let displayed = match
      av1_container_decode_packet(
        self.track,
        self.limits,
        self.decoder,
        index,
        budget,
      ) {
      Ok(value) => value
      Err(error) => return self.record_seek_failure(index, error)
    }
    self.next_index = index + 1
    if displayed.length() > 1 {
      return self.record_seek_failure(
        index,
        decode_error(
          InvalidData,
          "presentation",
          "A container sample has multiple selected presentations",
        ),
      )
    }
    if displayed.length() == 1 {
      let timestamp = self.track.packets[index].presentation_timestamp
      match timestamp {
        None =>
          return Ok({
            requested_timestamp: requested,
            outcome: UnknownTimestamp,
            frame: Some(av1_container_frame(self.track, index, displayed[0])),
          })
        Some(value) =>
          if value >= requested {
            return Ok(
              av1_container_seek_result(
                requested,
                self.track,
                index,
                displayed[0],
              ),
            )
          }
      }
    }
  }
  Ok({ requested_timestamp: requested, outcome: End, frame: None, })
}

///|
/// Seek on a track whose packet timestamps are all known and nondecreasing.
/// The target is located in packet order, then replay begins at the nearest
/// independently decoded sync candidate. Every candidate probe and replay
/// operation consumes the supplied single budget.
fn Av1ContainerDecoder::seek_ascending(
  self : Av1ContainerDecoder,
  requested : Int64,
  budget : DecodeBudget,
) -> Result[Av1ContainerSeekResult, DecodeError] {
  let mut target = self.track.packets.length()
  for index in 0..
        if value >= requested {
          target = index
          break
        }
      None => return self.seek_from_start(requested, budget)
    }
  }
  if target == self.track.packets.length() {
    self.decoder = av1_video_decoder()
    self.next_index = self.track.packets.length()
    return Ok({ requested_timestamp: requested, outcome: End, frame: None, })
  }
  let mut candidate = self.previous_candidate(target)
  let mut accepted : (Int, Av1VideoDecoder, Array[Av1FramePlanes])? = None
  while candidate is Some(index) {
    let checked = match self.check_random_access(index, budget) {
      Ok(value) => value
      Err(error) => return self.record_seek_failure(index, error)
    }
    if checked.0 {
      accepted = Some((index, checked.1.unwrap(), checked.2))
      break
    }
    candidate = self.previous_candidate(index - 1)
  }
  let (start, decoder, first) = match accepted {
    Some(value) => value
    None =>
      // Packet zero is always the final fallback. A failure here is replayed
      // through the normal path so its diagnostic becomes sticky and exact.
      return self.seek_from_start(requested, budget)
  }
  self.decoder = decoder
  self.next_index = start
  let mut displayed = first
  let mut index = start
  while index < self.track.packets.length() {
    if index != start {
      displayed = match
        av1_container_decode_packet(
          self.track,
          self.limits,
          self.decoder,
          index,
          budget,
        ) {
        Ok(value) => value
        Err(error) => return self.record_seek_failure(index, error)
      }
    }
    self.next_index = index + 1
    if displayed.length() > 1 {
      return self.record_seek_failure(
        index,
        decode_error(
          InvalidData,
          "presentation",
          "A container sample has multiple selected presentations",
        ),
      )
    }
    if displayed.length() == 1 {
      let timestamp = self.track.packets[index].presentation_timestamp
      match timestamp {
        Some(value) =>
          if value >= requested {
            return Ok(
              av1_container_seek_result(
                requested,
                self.track,
                index,
                displayed[0],
              ),
            )
          }
        None => return self.seek_from_start(requested, budget)
      }
    }
    index += 1
  }
  Ok({ requested_timestamp: requested, outcome: End, frame: None, })
}

///|
/// Seek to the first selected presentation encountered in container decode
/// order whose known raw-media PTS is at least `requested_timestamp`.
/// `Exact` reports equality, `Next` reports a later PTS, `End` reports that no
/// selected picture qualifies, and `UnknownTimestamp` reports the first
/// selected picture that has no timestamp and therefore cannot be compared.
pub fn Av1ContainerDecoder::seek_timestamp(
  self : Av1ContainerDecoder,
  requested_timestamp : Int64,
) -> Result[Av1ContainerSeekResult, DecodeError] {
  if self.failure is Some(error) {
    return Err(error)
  }
  let budget = match decode_checked_budget(0, self.limits) {
    Ok(value) => value
    Err(error) => return self.record_seek_failure(self.next_index, error)
  }
  let mut previous : Int64? = None
  let mut ascending = true
  for packet in self.track.packets {
    match packet.presentation_timestamp {
      Some(value) => {
        match previous {
          Some(prior) => if value < prior { ascending = false }
          None => ()
        }
        previous = Some(value)
      }
      None => {
        ascending = false
        break
      }
    }
  }
  if ascending {
    self.seek_ascending(requested_timestamp, budget)
  } else {
    self.seek_from_start(requested_timestamp, budget)
  }
}

///|
/// Read the next displayed picture. Hidden-only packets share this call's
/// work budget with the eventual displayed picture. Timestamps stay in the
/// demuxer's media timeline; container presentation edits are not implicit.
pub fn Av1ContainerDecoder::next_frame(
  self : Av1ContainerDecoder,
) -> Result[Av1ContainerFrame?, DecodeError] {
  if self.failure is Some(error) {
    return Err(error)
  }
  if self.next_index == self.track.packets.length() {
    return Ok(None)
  }
  let budget = match decode_checked_budget(0, self.limits) {
    Ok(value) => value
    Err(error) => return self.record_failure(error)
  }
  while self.next_index < self.track.packets.length() {
    let index = self.next_index
    let displayed = match
      av1_container_decode_packet(
        self.track,
        self.limits,
        self.decoder,
        index,
        budget,
      ) {
      Ok(value) => value
      Err(error) => return self.record_failure_at(index, error)
    }
    if displayed.length() > 1 {
      return self.record_failure_at(
        index,
        decode_error(
          InvalidData,
          "presentation",
          "A container sample has multiple selected presentations",
        ),
      )
    }
    let packet_index = index
    self.next_index = index + 1
    if displayed.length() == 1 {
      return Ok(
        Some({
          packet_index,
          timestamp: self.track.packets[index].presentation_timestamp,
          duration: self.track.packets[index].duration,
          timescale: self.track.timescale,
          frame: av1_frame_to_native(displayed[0]),
        }),
      )
    }
  }
  Ok(None)
}