///|
/// InvalidData includes failures for which the decoder cannot identify a more
/// specific cause. Unsupported is never guessed from an opaque syntax failure.
pub(all) enum DecodeErrorKind {
  InvalidData
  TruncatedInput
  Unsupported
  MissingSequence
  MissingReference
  LimitExceeded
  InvalidState
  InvalidBuffer
  NoPresentation
} derive(Eq, Debug)

///|
/// Zero-based checked field or failing payload start, not a bit-level entropy
/// position. Incremental input uses absolute stream offsets. None is used when
/// an assembled AVIF payload cannot be mapped to one container byte location.
pub struct DecodeError {
  kind : DecodeErrorKind
  byte_offset : Int64?
  context : String
  message : String
} derive(Eq, Debug)

///|
fn decode_error(
  kind : DecodeErrorKind,
  context : String,
  message : String,
  offset? : Int? = None,
) -> DecodeError {
  {
    kind,
    context,
    message,
    byte_offset: offset.map(value => value.to_int64()),
  }
}

///|
priv struct DecodeDiagnostic {
  mut context : String
  mut byte_offset : Int64?
  mut error : DecodeError?
}

///|
fn decode_context(
  budget : DecodeBudget?,
  context : String,
  offset : Int?,
) -> Unit {
  match budget {
    Some({ diagnostic: Some(trace), .. }) => {
      trace.context = context
      trace.byte_offset = offset.map(value => value.to_int64())
    }
    _ => ()
  }
}

///|
fn decode_fail(
  budget : DecodeBudget?,
  kind : DecodeErrorKind,
  message : String,
) -> Unit {
  match budget {
    Some({ diagnostic: Some(trace), .. }) =>
      if trace.error is None {
        trace.error = Some({
          kind,
          byte_offset: trace.byte_offset,
          context: trace.context,
          message,
        })
      }
    _ => ()
  }
}

///|
fn decode_record_error(budget : DecodeBudget?, error : DecodeError) -> Unit {
  match budget {
    Some({ diagnostic: Some(trace), .. }) => trace.error = Some(error)
    _ => ()
  }
}

///|
fn decode_trace_error(trace : DecodeDiagnostic?, error : DecodeError) -> Unit {
  match trace {
    Some(value) => value.error = Some(error)
    None => ()
  }
}

///|
fn avif_boxes_result(data : Array[Byte]) -> Result[Array[Box], DecodeError] {
  try {
    let boxes : Array[Box] = []
    walk(data, 0, data.length(), -1, boxes)
    Ok(boxes)
  } catch {
    AvifError::Diagnostic(error) => Err(error)
    AvifError::Unsupported =>
      Err(
        decode_error(
          Unsupported,
          "container",
          "Unsupported container size or syntax",
        ),
      )
    _ =>
      Err(decode_error(InvalidData, "container", "Invalid container structure"))
  }
}

///|
pub fn avif_container_parse_result(
  data : Array[Byte],
  limits? : DecodeLimits = DecodeLimits::default(),
) -> Result[AvifContainer, DecodeError] {
  let budget = match decode_checked_budget(data.length(), limits) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  match
    avif_container_parse_limited(
      data,
      limits=Some(limits),
      diagnostic=budget.diagnostic,
    ) {
    Some(container) => Ok(container)
    None => Err(decode_failure(budget))
  }
}

///|
fn avif_decode_checked_planes(
  data : Array[Byte],
  limits : DecodeLimits,
) -> Result[(Av1FramePlanes, Av1FramePlanes?, Bool), DecodeError] {
  let budget = match decode_checked_budget(data.length(), limits) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  // Envelope validation retains exact container field positions before the
  // existing independent color/alpha parsers resolve item associations.
  match avif_boxes_result(data) {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  match
    avif_decode_image_planes(data, limits=Some(limits), budget=Some(budget)) {
    Some(planes) => Ok(planes)
    None => {
      let error = decode_failure(budget)
      if error.context == "container" {
        Err(error)
      } else {
        Err({ ..error, byte_offset: None, context: "avif/" + error.context, })
      }
    }
  }
}

///|
pub fn avif_decode_native_result(
  data : Array[Byte],
  limits? : DecodeLimits = DecodeLimits::default(),
) -> Result[AvifNativeImage, DecodeError] {
  match avif_decode_checked_planes(data, limits) {
    Err(error) => Err(error)
    Ok((color, alpha, premultiplied)) =>
      Ok({
        color: av1_frame_to_native(color),
        alpha: alpha.map(av1_frame_to_native),
        premultiplied,
      })
  }
}

///|
pub fn avif_decode_rgba_result(
  data : Array[Byte],
  limits? : DecodeLimits = DecodeLimits::default(),
) -> Result[Image, DecodeError] {
  match avif_decode_checked_planes(data, limits) {
    Err(error) => Err(error)
    Ok((color, alpha, premultiplied)) => {
      let converted = match alpha {
        Some(plane) => av1_frame_with_alpha(color, plane, premultiplied)
        None => av1_frame_to_rgba(color)
      }
      match converted {
        Some(image) => Ok(image)
        None =>
          Err(
            decode_error(
              if av1_color_supported(
                  color.matrix_coefficients,
                  color.color_primaries,
                  color.transfer_characteristics,
                ) {
                InvalidData
              } else {
                Unsupported
              },
              "color",
              "RGBA8 conversion failed",
            ),
          )
      }
    }
  }
}

///|
pub fn avif_decode_rgba16_result(
  data : Array[Byte],
  limits? : DecodeLimits = DecodeLimits::default(),
) -> Result[Image16, DecodeError] {
  match avif_decode_native_result(data, limits~) {
    Err(error) => Err(error)
    Ok(image) =>
      match image.to_rgba16() {
        Some(value) => Ok(value)
        None =>
          Err(
            decode_error(
              if av1_color_supported(
                  image.color.matrix_coefficients,
                  image.color.color_primaries,
                  image.color.transfer_characteristics,
                ) {
                InvalidData
              } else {
                Unsupported
              },
              "color",
              "RGBA16 conversion failed",
            ),
          )
      }
  }
}

///|
pub fn av1_decode_result(
  data : Array[Byte],
  limits? : DecodeLimits = DecodeLimits::default(),
) -> Result[Image, DecodeError] {
  let budget = match decode_checked_budget(data.length(), limits) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  match av1_decode_checked_planes(av1_video_decoder(), data, true, budget) {
    Err(error) => Err(error)
    Ok(frames) => {
      if frames.is_empty() {
        return Err(
          decode_error(NoPresentation, "presentation", "No displayed picture"),
        )
      }
      let frame = frames[0]
      match av1_frame_to_rgba(frame) {
        Some(image) => Ok(image)
        None =>
          Err(
            decode_error(
              if av1_color_supported(
                  frame.matrix_coefficients,
                  frame.color_primaries,
                  frame.transfer_characteristics,
                ) {
                InvalidData
              } else {
                Unsupported
              },
              "color",
              "RGBA8 conversion failed",
            ),
          )
      }
    }
  }
}

///|
fn decode_failure(budget : DecodeBudget) -> DecodeError {
  match budget.diagnostic {
    Some(trace) =>
      match trace.error {
        Some(error) => error
        None =>
          {
            kind: InvalidData,
            byte_offset: trace.byte_offset,
            context: trace.context,
            message: "Syntax or reconstruction validation failed",
          }
      }
    None =>
      decode_error(
        InvalidData,
        "decode",
        "Syntax or reconstruction validation failed",
      )
  }
}

///|
fn decode_checked_budget(
  length : Int,
  limits : DecodeLimits,
) -> Result[DecodeBudget, DecodeError] {
  if !decode_input_allowed(0, Some(limits)) {
    return Err(
      decode_error(
        InvalidData,
        "limits",
        "DecodeLimits fields must be positive and within the hard picture limit",
      ),
    )
  }
  if length > limits.max_input_bytes {
    return Err(
      decode_error(
        LimitExceeded,
        "input",
        "Compressed input exceeds max_input_bytes",
      ),
    )
  }
  Ok({
    limits,
    frames_left: limits.max_frames,
    pixels_left: limits.max_total_pixels,
    diagnostic: Some({ context: "decode", byte_offset: None, error: None, }),
  })
}

///|
fn av1_decode_checked_planes(
  decoder : Av1VideoDecoder,
  data : Array[Byte],
  allow_monochrome : Bool,
  budget : DecodeBudget,
) -> Result[Array[Av1FramePlanes], DecodeError] {
  if data.is_empty() && decoder.sequence is None {
    return Err(
      decode_error(TruncatedInput, "input", "Empty AV1 input", offset=Some(0)),
    )
  }
  match
    av1_video_decode_temporal_unit_planes(
      decoder,
      data,
      allow_monochrome~,
      budget=Some(budget),
    ) {
    Some(frames) => Ok(frames)
    None => Err(decode_failure(budget))
  }
}

///|
/// Valid hidden-only input returns Ok([]). Any Err requires discarding/resetting
/// the decoder: reference updates are not transactional, as in the Option API.
pub fn av1_video_decode_temporal_unit_native_result(
  decoder : Av1VideoDecoder,
  data : Array[Byte],
  allow_monochrome? : Bool = false,
  limits? : DecodeLimits = DecodeLimits::default(),
) -> Result[Array[Av1NativeFrame], DecodeError] {
  let budget = match decode_checked_budget(data.length(), limits) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  match av1_decode_checked_planes(decoder, data, allow_monochrome, budget) {
    Ok(frames) => Ok(frames.map(av1_frame_to_native))
    Err(error) => Err(error)
  }
}

///|
pub fn av1_decode_native_result(
  data : Array[Byte],
  limits? : DecodeLimits = DecodeLimits::default(),
) -> Result[Av1NativeFrame, DecodeError] {
  match
    av1_video_decode_temporal_unit_native_result(
      av1_video_decoder(),
      data,
      allow_monochrome=true,
      limits~,
    ) {
    Err(error) => Err(error)
    Ok(frames) =>
      if frames.is_empty() {
        Err(
          decode_error(NoPresentation, "presentation", "No displayed picture"),
        )
      } else {
        Ok(frames[0])
      }
  }
}

///|
pub fn av1_decode_rgba16_result(
  data : Array[Byte],
  limits? : DecodeLimits = DecodeLimits::default(),
) -> Result[Image16, DecodeError] {
  match av1_decode_native_result(data, limits~) {
    Err(error) => Err(error)
    Ok(frame) =>
      match frame.to_rgba16() {
        Some(image) => Ok(image)
        None =>
          Err(
            decode_error(
              if av1_color_supported(
                  frame.matrix_coefficients,
                  frame.color_primaries,
                  frame.transfer_characteristics,
                ) {
                InvalidData
              } else {
                Unsupported
              },
              "color",
              "RGBA16 conversion failed",
            ),
          )
      }
  }
}