///|
/// Inter-frame block mode syntax (AV1 §5.11.20 through §5.11.30).
///
/// This file reads bits: it turns a tile's entropy stream into one block's
/// reference frame, motion vector, interpolation filters and mode. The candidate
/// stack those trees predict against is derived in `av1_mv.mbt`, and the samples
/// they select are interpolated in `av1_mc.mbt`.
///
/// Every probability row here is a per-tile mutable copy, because a tile adapts
/// its probabilities as it decodes. Grouping them in one struct lets a frame
/// context be snapshotted and restored as a unit.
///
/// Reads single and compound reference modes, OBMC, local/global motion and
/// inter-intra blending. Reconstruction checks live in `av1_inter_tile.mbt`;
/// unsupported prediction combinations are rejected there before writing pixels.

///|
/// Motion vector joints (AV1 §6.10.18): which of the two components is nonzero.
/// The zero joint needs no constant; it is the branch that reads neither.
let av1_mv_joint_hnzvz : Int = 1

///|
let av1_mv_joint_hzvnz : Int = 2

///|
let av1_mv_joint_hnzvnz : Int = 3

///|
/// Half-pel magnitudes coded by motion vector class 0 (AV1 §6.10.18).
let av1_class0_size : Int = 2

///|
/// Size_Group[ MiSize ] (AV1 §9.3): the context an intra block inside an inter
/// frame uses for its luma mode, and the base of the inter-intra contexts. One
/// row per size class.
let av1_size_group : Array[Int] = [
  0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 0, 0, 1, 1, 2, 2,
]

///|
/// ref_count_ctx( counts0, counts1 ) (AV1 §9.3.4).
fn av1_ref_count_ctx(counts0 : Int, counts1 : Int) -> Int {
  if counts0 < counts1 {
    0
  } else if counts0 == counts1 {
    1
  } else {
    2
  }
}

///|
/// Mutable inter probability rows for one tile. The `mv_*` arrays replicate the
/// normative one-dimensional defaults over the motion vector context (inter, or
/// intra block copy) and the vector component, because a tile adapts every copy
/// separately.
priv struct Av1InterCdfs {
  single_ref : Array[Array[Array[Int]]]
  new_mv : Array[Array[Int]]
  zero_mv : Array[Array[Int]]
  ref_mv : Array[Array[Int]]
  drl_mode : Array[Array[Int]]
  is_inter : Array[Array[Int]]
  skip_mode : Array[Array[Int]]
  interp_filter : Array[Array[Int]]
  use_obmc : Array[Array[Int]]
  motion_mode : Array[Array[Int]]
  y_mode : Array[Array[Int]]
  inter_tx_type_set1 : Array[Array[Int]]
  inter_tx_type_set2 : Array[Int]
  inter_tx_type_set3 : Array[Array[Int]]
  mv_joint : Array[Array[Int]]
  mv_sign : Array[Array[Array[Int]]]
  mv_class : Array[Array[Array[Int]]]
  mv_class0_bit : Array[Array[Array[Int]]]
  mv_class0_fr : Array[Array[Array[Array[Int]]]]
  mv_class0_hp : Array[Array[Array[Int]]]
  mv_bit : Array[Array[Array[Array[Int]]]]
  mv_fr : Array[Array[Array[Int]]]
  mv_hp : Array[Array[Array[Int]]]
  comp_mode : Array[Array[Int]]
  comp_ref_type : Array[Array[Int]]
  compound_mode : Array[Array[Int]]
  compound_idx : Array[Array[Int]]
  comp_group_idx : Array[Array[Int]]
  uni_comp_ref : Array[Array[Array[Int]]]
  comp_ref : Array[Array[Array[Int]]]
  comp_bwd_ref : Array[Array[Array[Int]]]
  compound_type : Array[Array[Int]]
  inter_intra : Array[Array[Int]]
  inter_intra_mode : Array[Array[Int]]
  wedge_inter_intra : Array[Array[Int]]
  wedge_index : Array[Array[Int]]
}

///|
/// Replicate a `[component][row]` default table over the motion vector context.
fn av1_mv_ctx_rows(table : Array[Array[Int]]) -> Array[Array[Array[Int]]] {
  Array::makei(2, fn(_) { table.map(fn(row) { row.copy() }) })
}

///|
/// Replicate a flat default table over the motion vector context and component.
fn av1_mv_ctx_comp_rows(row : Array[Int]) -> Array[Array[Array[Int]]] {
  Array::makei(2, fn(_) { Array::makei(2, fn(_) { row.copy() }) })
}

///|
/// Fresh rows for one tile. Retain the result for the tile's lifetime: a second
/// call silently restarts probability adaptation.
fn av1_inter_cdfs() -> Av1InterCdfs {
  {
    single_ref: av1_single_ref_cdfs(),
    new_mv: av1_new_mv_cdfs(),
    zero_mv: av1_zero_mv_cdfs(),
    ref_mv: av1_ref_mv_cdfs(),
    drl_mode: av1_drl_mode_cdfs(),
    is_inter: av1_is_inter_cdfs(),
    skip_mode: av1_skip_mode_cdfs(),
    interp_filter: av1_interp_filter_cdfs(),
    use_obmc: av1_use_obmc_cdfs(),
    motion_mode: av1_motion_mode_cdfs(),
    y_mode: av1_y_mode_cdfs(),
    inter_tx_type_set1: av1_inter_tx_type_set1_cdfs(),
    inter_tx_type_set2: av1_inter_tx_type_set2_cdfs(),
    inter_tx_type_set3: av1_inter_tx_type_set3_cdfs(),
    mv_joint: Array::makei(2, fn(_) { av1_mv_joint_cdfs() }),
    mv_sign: av1_mv_ctx_comp_rows(av1_mv_sign_cdfs()),
    mv_class: av1_mv_ctx_rows(av1_mv_class_cdfs()),
    mv_class0_bit: av1_mv_ctx_comp_rows(av1_mv_class0_bit_cdfs()),
    mv_class0_fr: Array::makei(2, fn(_) {
      av1_mv_class0_fr_cdfs().map(fn(rows) { rows.map(fn(row) { row.copy() }) })
    }),
    mv_class0_hp: av1_mv_ctx_comp_rows(av1_mv_class0_hp_cdfs()),
    mv_bit: Array::makei(2, fn(_) {
      Array::makei(2, fn(_) { av1_mv_bit_cdfs().map(fn(row) { row.copy() }) })
    }),
    mv_fr: av1_mv_ctx_rows(av1_mv_fr_cdfs()),
    mv_hp: av1_mv_ctx_comp_rows(av1_mv_hp_cdfs()),
    comp_mode: av1_comp_mode_cdfs(),
    comp_ref_type: av1_comp_ref_type_cdfs(),
    compound_mode: av1_compound_mode_cdfs(),
    compound_idx: av1_compound_idx_cdfs(),
    comp_group_idx: av1_comp_group_idx_cdfs(),
    uni_comp_ref: av1_uni_comp_ref_cdfs(),
    comp_ref: av1_comp_ref_cdfs(),
    comp_bwd_ref: av1_comp_bwd_ref_cdfs(),
    compound_type: av1_compound_type_cdfs(),
    inter_intra: av1_inter_intra_cdfs(),
    inter_intra_mode: av1_inter_intra_mode_cdfs(),
    wedge_inter_intra: av1_wedge_inter_intra_cdfs(),
    wedge_index: av1_wedge_index_cdfs(),
  }
}

///|
/// The frame- and tile-level state every inter block of one tile reads. `refs` is
/// indexed by reference name (`av1_last_frame` through `av1_alt_ref_frame`), and
/// `None` marks a slot the reference buffer has never filled.
priv struct Av1InterInfo {
  cdfs : Av1InterCdfs
  field : Av1MotionField
  frame : Av1MvFrame
  refs : Array[Av1RefFrame?]
  /// `interpolation_filter`: 4 (`av1_interp_switchable`) means each block codes
  /// its own; anything else fixes both directions.
  interpolation_filter : Int
  enable_dual_filter : Bool
  skip_mode_present : Bool
  skip_mode_frame : Array[Int]
  /// `reference_select`: whether `read_ref_frames` may read `comp_mode` at all.
  reference_select : Bool
  enable_interintra_compound : Bool
  /// Each reference name's distance from this frame's order hint, clipped to
  /// `[0, MAX_FRAME_DISTANCE]`. The distance-weighted compound blend weighs the
  /// two predictions with these (AV1 §7.11.3.15).
  ref_distances : Array[Int]
  /// Sequence-level masked-compound switches. `read_compound_type` reads its
  /// symbols only when one of them is set; the frame gate refuses such frames,
  /// so the decoder here rejects the stream instead of guessing a blend.
  enable_masked_compound : Bool
  enable_jnt_comp : Bool
  /// Frame dimensions in MI units; the motion field spans the whole frame.
  mi_cols : Int
  mi_rows : Int
  /// This tile's coded rectangle in MI units, exclusive at the end.
  tile_row_start : Int
  tile_col_start : Int
  tile_row_end : Int
  tile_col_end : Int
}

///|
/// One block's decoded inter syntax, in the fields the reconstruction and the
/// neighbour contexts of later blocks need.
pub(all) struct Av1InterBlock {
  mi_row : Int
  mi_col : Int
  width4 : Int
  height4 : Int
  block_width : Int
  block_height : Int
  size_index : Int
  avail_u : Bool
  avail_l : Bool
  mut skip : Int
  mut skip_mode : Bool
  mut segment_ref : Int
  mut segment_skip : Bool
  mut segment_global : Bool
  mut is_inter : Bool
  mut y_mode : Int
  mut ref_frame : Int
  /// The second reference name of a compound block (`av1_none_frame` for a
  /// single-reference block). AV1 5.11.25 `read_ref_frames` fills both names.
  mut ref_frame1 : Int
  mut mv_row : Int
  mut mv_col : Int
  /// The second motion vector of a compound block, in its reference's units.
  mut mv_row1 : Int
  mut mv_col1 : Int
  /// `read_interintra_mode`: whether this inter block also codes an intra
  /// prediction, and which one. Only the symbols are read here; a block that
  /// selects one makes the frame be refused, because the blend needs the intra
  /// predictor wired into the inter path.
  mut interintra : Int
  mut interintra_mode : Int
  mut wedge_interintra : Int
  mut wedge_index_ii : Int
  /// `read_compound_type`: which blend a compound block uses.
  mut compound_type : Int
  /// The masked-compound symbols the block read, for its neighbours' contexts.
  mut compound_idx : Int
  mut comp_group_idx : Int
  /// `wedge_index` / `wedge_sign` for a wedge block, `mask_type` for a
  /// difference-weighted one; zero for the blends that read neither.
  mut wedge_index : Int
  mut wedge_sign : Int
  mut mask_type : Int
  /// Vertical and horizontal subpel filter, in the order the syntax codes them.
  mut interp_vertical : Int
  mut interp_horizontal : Int
  /// The reference-list index into the candidate stack, from `drl_mode`.
  mut ref_mv_idx : Int
  /// `read_motion_mode`: 0 = SIMPLE, 1 = OBMC, 2 = local warp. Only the symbol
  /// is decoded here; a block that selects a mode the predictor cannot act on
  /// makes the frame be refused rather than mis-reconstructed.
  mut motion_mode : Int
  /// The warp model a LOCALWARP block estimated from its neighbours, or an
  /// empty array when the model is identity (or could not be represented), in
  /// which case the block predicts with its own motion vector.
  mut warp : Array[Av1WarpModel]
}

///|
/// A fresh block. An inter frame's intra blocks keep the reference at INTRA and
/// the filters at the eight-tap default, which is what their neighbours read.
fn av1_inter_block(
  mi_row : Int,
  mi_col : Int,
  width4 : Int,
  height4 : Int,
  size_index : Int,
  avail_u : Bool,
  avail_l : Bool,
) -> Av1InterBlock {
  {
    mi_row,
    mi_col,
    width4,
    height4,
    block_width: width4 * 4,
    block_height: height4 * 4,
    size_index,
    avail_u,
    avail_l,
    skip: 0,
    skip_mode: false,
    segment_ref: -1,
    segment_skip: false,
    segment_global: false,
    is_inter: false,
    y_mode: 0,
    ref_frame: av1_intra_frame,
    ref_frame1: av1_none_frame,
    mv_row: 0,
    mv_col: 0,
    mv_row1: 0,
    mv_col1: 0,
    interintra: 0,
    interintra_mode: 0,
    wedge_interintra: 0,
    wedge_index_ii: 0,
    compound_type: av1_compound_average,
    compound_idx: 1,
    comp_group_idx: 0,
    wedge_index: 0,
    wedge_sign: 0,
    mask_type: 0,
    interp_vertical: av1_interp_eighttap,
    interp_horizontal: av1_interp_eighttap,
    ref_mv_idx: 0,
    motion_mode: 0,
    warp: [],
  }
}

///|
/// The reference name of the block above or to the left, or INTRA_FRAME when
/// that neighbour does not exist (AV1 §5.11.23).
fn av1_field_reference(field : Av1MotionField, row : Int, col : Int) -> Int {
  if row < 0 || col < 0 || row >= field.rows || col >= field.cols {
    av1_intra_frame
  } else {
    field.refs[(row * field.cols + col) * 2]
  }
}

///|
/// read_is_inter( ) (AV1 §5.11.20). A segmentation feature can force the answer,
/// but the frame gate refuses segmentation.
fn av1_read_is_inter(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
) -> Bool {
  if block.skip_mode {
    block.is_inter = true
    return true
  }
  if block.segment_ref >= 0 {
    block.is_inter = block.segment_ref != av1_intra_frame
    return true
  }
  if block.segment_global {
    block.is_inter = true
    return true
  }
  let left = av1_field_reference(info.field, block.mi_row, block.mi_col - 1)
  let above = av1_field_reference(info.field, block.mi_row - 1, block.mi_col)
  let left_intra = left <= av1_intra_frame
  let above_intra = above <= av1_intra_frame
  let mut ctx = 0
  if block.avail_u && block.avail_l {
    ctx = if left_intra && above_intra {
      3
    } else if left_intra || above_intra {
      1
    } else {
      0
    }
  } else if block.avail_u {
    ctx = if above_intra { 2 } else { 0 }
  } else if block.avail_l {
    ctx = if left_intra { 2 } else { 0 }
  }
  if ctx >= info.cdfs.is_inter.length() {
    return false
  }
  block.is_inter = decoder.symbol(info.cdfs.is_inter[ctx]) == 1
  true
}

///|
/// read_skip_mode( ) (AV1 §5.11.21). A block that skips its modes entirely copies
/// the two frame-level skip-mode references.
fn av1_read_skip_mode(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
) -> Bool {
  block.skip_mode = false
  if block.segment_skip ||
    block.segment_global ||
    block.segment_ref >= 0 ||
    !info.skip_mode_present ||
    block.block_width < 8 ||
    block.block_height < 8 {
    return true
  }
  let mut ctx = 0
  if block.avail_u {
    ctx = ctx + av1_field_skip_mode(info.field, block.mi_row - 1, block.mi_col)
  }
  if block.avail_l {
    ctx = ctx + av1_field_skip_mode(info.field, block.mi_row, block.mi_col - 1)
  }
  if ctx >= info.cdfs.skip_mode.length() {
    return false
  }
  if decoder.symbol(info.cdfs.skip_mode[ctx]) == 1 {
    block.skip_mode = true
    block.skip = 1
    block.is_inter = true
    block.ref_frame = info.skip_mode_frame[0]
  }
  true
}

///|
fn av1_field_skip_mode(field : Av1MotionField, row : Int, col : Int) -> Int {
  if row < 0 || col < 0 || row >= field.rows || col >= field.cols {
    0
  } else {
    field.skip_modes[row * field.cols + col]
  }
}

///|
/// AV1's `checkBackward`: whether a reference name sits in the backward half of
/// the reference list. The comp_mode and comp_ref_type contexts use it.
fn av1_ref_is_backward(name : Int) -> Bool {
  name >= av1_bwd_ref_frame && name <= av1_alt_ref_frame
}

///|
/// AV1's `isSamedirRefPair`: whether two names point the same way.
fn av1_samedir_ref_pair(ref0 : Int, ref1 : Int) -> Bool {
  (ref0 >= av1_bwd_ref_frame) == (ref1 >= av1_bwd_ref_frame)
}

///|
/// comp_mode context (AV1 §8.3). Reads the two neighbours' reference pairs: a
/// single-reference or intra neighbour collapses to one name, a compound one
/// keeps both, and the context counts how far each sits into the backward half.
fn av1_comp_mode_ctx(info : Av1InterInfo, block : Av1InterBlock) -> Int {
  let above = av1_neighbour_pair(info.field, block.mi_row - 1, block.mi_col)
  let left = av1_neighbour_pair(info.field, block.mi_row, block.mi_col - 1)
  let above_intra = above[0] == av1_intra_frame
  let left_intra = left[0] == av1_intra_frame
  let above_single = above[1] == av1_none_frame
  let left_single = left[1] == av1_none_frame
  if block.avail_u && block.avail_l {
    if above_single && left_single {
      (if av1_ref_is_backward(above[0]) { 1 } else { 0 }) ^
      (if av1_ref_is_backward(left[0]) { 1 } else { 0 })
    } else if above_single {
      2 + (if av1_ref_is_backward(above[0]) || above_intra { 1 } else { 0 })
    } else if left_single {
      2 + (if av1_ref_is_backward(left[0]) || left_intra { 1 } else { 0 })
    } else {
      4
    }
  } else if block.avail_u {
    if above_single {
      if av1_ref_is_backward(above[0]) {
        1
      } else {
        0
      }
    } else {
      3
    }
  } else if block.avail_l {
    if left_single {
      if av1_ref_is_backward(left[0]) {
        1
      } else {
        0
      }
    } else {
      3
    }
  } else {
    1
  }
}

///|
/// comp_ref_type context (AV1 §8.3): whether each neighbour is single- or
/// compound-reference, and for compound ones whether the pair points one way.
fn av1_comp_ref_type_ctx(info : Av1InterInfo, block : Av1InterBlock) -> Int {
  let above = av1_neighbour_pair(info.field, block.mi_row - 1, block.mi_col)
  let left = av1_neighbour_pair(info.field, block.mi_row, block.mi_col - 1)
  let a0 = above[0]
  let a1 = above[1]
  let l0 = left[0]
  let l1 = left[1]
  let above_intra = a0 == av1_intra_frame
  let left_intra = l0 == av1_intra_frame
  let above_comp = block.avail_u && !above_intra && a1 != av1_none_frame
  let left_comp = block.avail_l && !left_intra && l1 != av1_none_frame
  let above_uni = above_comp && av1_samedir_ref_pair(a0, a1)
  let left_uni = left_comp && av1_samedir_ref_pair(l0, l1)
  let samedir = if av1_samedir_ref_pair(a0, l0) { 1 } else { 0 }
  if block.avail_u && !above_intra && block.avail_l && !left_intra {
    if !above_comp && !left_comp {
      1 + 2 * samedir
    } else if !above_comp {
      if !left_uni {
        1
      } else {
        3 + samedir
      }
    } else if !left_comp {
      if !above_uni {
        1
      } else {
        3 + samedir
      }
    } else if !above_uni && !left_uni {
      0
    } else if !above_uni || !left_uni {
      2
    } else {
      3 +
      (if (a0 == av1_bwd_ref_frame) == (l0 == av1_bwd_ref_frame) {
        1
      } else {
        0
      })
    }
  } else if block.avail_u && block.avail_l {
    if above_comp {
      1 + 2 * (if above_uni { 1 } else { 0 })
    } else if left_comp {
      1 + 2 * (if left_uni { 1 } else { 0 })
    } else {
      2
    }
  } else if above_comp {
    4 * (if above_uni { 1 } else { 0 })
  } else if left_comp {
    4 * (if left_uni { 1 } else { 0 })
  } else {
    2
  }
}

///|
/// read_ref_frames( ) (AV1 §5.11.24): the binary tree over the seven named
/// reference frames. The tree context counts how often each name appears in the
/// two neighbours, forward against backward.
fn av1_read_ref_frames(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
) -> Bool {
  if block.skip_mode {
    // A skip-mode block names both references from the frame's SkipModeFrames
    // pair and codes no reference symbols at all (AV1 §5.11.25).
    block.ref_frame = info.skip_mode_frame[0]
    block.ref_frame1 = info.skip_mode_frame[1]
    return true
  }
  // A compound block codes two reference names; a single one keeps the second
  // at NONE, which is what its neighbours and the predictor read.
  block.ref_frame1 = av1_none_frame
  if block.segment_ref >= 0 {
    block.ref_frame = block.segment_ref
    return true
  }
  if block.segment_skip || block.segment_global {
    block.ref_frame = av1_last_frame
    return true
  }
  if info.reference_select && block.width4 >= 2 && block.height4 >= 2 {
    if decoder.symbol(info.cdfs.comp_mode[av1_comp_mode_ctx(info, block)]) != 0 {
      return av1_read_compound_refs(decoder, info, block)
    }
  }
  let left = av1_neighbour_pair(info.field, block.mi_row, block.mi_col - 1)
  let above = av1_neighbour_pair(info.field, block.mi_row - 1, block.mi_col)
  let counts = fn(name) {
    let mut count = 0
    if block.avail_u {
      if above[0] == name {
        count = count + 1
      }
      if above[1] == name {
        count = count + 1
      }
    }
    if block.avail_l {
      if left[0] == name {
        count = count + 1
      }
      if left[1] == name {
        count = count + 1
      }
    }
    count
  }
  let symbol = fn(index, counts0, counts1) {
    let ctx = av1_ref_count_ctx(counts0, counts1)
    if ctx >= info.cdfs.single_ref.length() ||
      index >= info.cdfs.single_ref[ctx].length() {
      -1
    } else {
      decoder.symbol(info.cdfs.single_ref[ctx][index])
    }
  }
  let forward = counts(av1_last_frame) +
    counts(av1_last2_frame) +
    counts(av1_last3_frame) +
    counts(av1_golden_frame)
  let backward = counts(av1_bwd_ref_frame) +
    counts(av1_alt_ref2_frame) +
    counts(av1_alt_ref_frame)
  let first = symbol(0, forward, backward)
  let mut name = av1_last_frame
  if first == 1 {
    // Backward half of the tree: ALTREF, or one of the two nearer ones.
    let second = symbol(
      1,
      counts(av1_bwd_ref_frame) + counts(av1_alt_ref2_frame),
      counts(av1_alt_ref_frame),
    )
    if second == 0 {
      let third = symbol(
        5,
        counts(av1_bwd_ref_frame),
        counts(av1_alt_ref2_frame),
      )
      if third < 0 {
        return false
      }
      name = if third == 1 { av1_alt_ref2_frame } else { av1_bwd_ref_frame }
    } else if second == 1 {
      name = av1_alt_ref_frame
    } else {
      return false
    }
  } else if first == 0 {
    let second = symbol(
      2,
      counts(av1_last_frame) + counts(av1_last2_frame),
      counts(av1_last3_frame) + counts(av1_golden_frame),
    )
    if second == 1 {
      let third = symbol(4, counts(av1_last3_frame), counts(av1_golden_frame))
      if third < 0 {
        return false
      }
      name = if third == 1 { av1_golden_frame } else { av1_last3_frame }
    } else if second == 0 {
      let third = symbol(3, counts(av1_last_frame), counts(av1_last2_frame))
      if third < 0 {
        return false
      }
      name = if third == 1 { av1_last2_frame } else { av1_last_frame }
    } else {
      return false
    }
  } else {
    return false
  }
  block.ref_frame = name
  true
}

///|
/// The compound half of `read_ref_frames` (AV1 §5.11.25): `comp_ref_type`
/// selects the unidirectional or the bidirectional pair, and each shape then
/// names its two references from the same neighbour-count contexts the single
/// tree uses. `read` mirrors the single tree's `symbol` helper but borrows the
/// table, so each pair keeps its own defaults.
fn av1_read_compound_refs(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
) -> Bool {
  let left = av1_neighbour_pair(info.field, block.mi_row, block.mi_col - 1)
  let above = av1_neighbour_pair(info.field, block.mi_row - 1, block.mi_col)
  let counts = fn(name) {
    let mut count = 0
    if block.avail_u {
      if above[0] == name {
        count = count + 1
      }
      if above[1] == name {
        count = count + 1
      }
    }
    if block.avail_l {
      if left[0] == name {
        count = count + 1
      }
      if left[1] == name {
        count = count + 1
      }
    }
    count
  }
  let read = fn(
    table : Array[Array[Array[Int]]],
    index : Int,
    counts0 : Int,
    counts1 : Int,
  ) -> Int {
    let ctx = av1_ref_count_ctx(counts0, counts1)
    if ctx >= table.length() || index >= table[ctx].length() {
      -1
    } else {
      decoder.symbol(table[ctx][index])
    }
  }
  // `comp_ref_type` zero asks for the unidirectional pair, one for the
  // bidirectional one (AV1 §5.11.23).
  let dir = decoder.symbol(
    info.cdfs.comp_ref_type[av1_comp_ref_type_ctx(info, block)],
  )
  if dir == 0 {
    // Unidirectional: both names point the same way.
    let forward = counts(av1_last_frame) +
      counts(av1_last2_frame) +
      counts(av1_last3_frame) +
      counts(av1_golden_frame)
    let backward = counts(av1_bwd_ref_frame) +
      counts(av1_alt_ref2_frame) +
      counts(av1_alt_ref_frame)
    if read(info.cdfs.uni_comp_ref, 0, forward, backward) == 1 {
      block.ref_frame = av1_bwd_ref_frame
      block.ref_frame1 = av1_alt_ref_frame
      return true
    }
    let p1 = read(
      info.cdfs.uni_comp_ref,
      1,
      counts(av1_last2_frame),
      counts(av1_last3_frame) + counts(av1_golden_frame),
    )
    if p1 < 0 {
      return false
    }
    if p1 == 0 {
      block.ref_frame = av1_last_frame
      block.ref_frame1 = av1_last2_frame
      return true
    }
    let p2 = read(
      info.cdfs.uni_comp_ref,
      2,
      counts(av1_last3_frame),
      counts(av1_golden_frame),
    )
    if p2 < 0 {
      return false
    }
    if p2 == 1 {
      block.ref_frame = av1_last_frame
      block.ref_frame1 = av1_golden_frame
    } else {
      block.ref_frame = av1_last_frame
      block.ref_frame1 = av1_last3_frame
    }
    return true
  }
  // Bidirectional: a forward name, then a backward one.
  let ctx_ref = read(
    info.cdfs.comp_ref,
    0,
    counts(av1_last_frame) + counts(av1_last2_frame),
    counts(av1_last3_frame) + counts(av1_golden_frame),
  )
  if ctx_ref < 0 {
    return false
  }
  if ctx_ref == 0 {
    let p1 = read(
      info.cdfs.comp_ref,
      1,
      counts(av1_last_frame),
      counts(av1_last2_frame),
    )
    if p1 < 0 {
      return false
    }
    block.ref_frame = if p1 == 1 { av1_last2_frame } else { av1_last_frame }
  } else {
    let p2 = read(
      info.cdfs.comp_ref,
      2,
      counts(av1_last3_frame),
      counts(av1_golden_frame),
    )
    if p2 < 0 {
      return false
    }
    block.ref_frame = if p2 == 1 { av1_golden_frame } else { av1_last3_frame }
  }
  let ctx_bwd = read(
    info.cdfs.comp_bwd_ref,
    0,
    counts(av1_bwd_ref_frame) + counts(av1_alt_ref2_frame),
    counts(av1_alt_ref_frame),
  )
  if ctx_bwd < 0 {
    return false
  }
  if ctx_bwd == 0 {
    let p1 = read(
      info.cdfs.comp_bwd_ref,
      1,
      counts(av1_bwd_ref_frame),
      counts(av1_alt_ref2_frame),
    )
    if p1 < 0 {
      return false
    }
    block.ref_frame1 = if p1 == 1 {
      av1_alt_ref2_frame
    } else {
      av1_bwd_ref_frame
    }
  } else {
    block.ref_frame1 = av1_alt_ref_frame
  }
  true
}

///|
/// The two reference names stored at one MI unit, for the reference-count
/// contexts. An unwritten or intra neighbour reports INTRA_FRAME and NONE.
fn av1_neighbour_pair(
  field : Av1MotionField,
  row : Int,
  col : Int,
) -> Array[Int] {
  let pair = [av1_intra_frame, av1_none_frame]
  if row < 0 || col < 0 || row >= field.rows || col >= field.cols {
    return pair
  }
  let unit = (row * field.cols + col) * 2
  pair[0] = field.refs[unit]
  pair[1] = field.refs[unit + 1]
  pair
}

///|
/// `CompoundType` values (AV1 §5.11.25 `read_compound_type`). AVERAGE and
/// DISTANCE are the two a stream can reach without masked compound.
let av1_compound_average : Int = 0

///|
let av1_compound_distance : Int = 1

///|
let av1_compound_wedge : Int = 2

///|
let av1_compound_diffwtd : Int = 3

///|
/// `Compound_Mode_Ctx_Map` (AV1 §8.3): the compound_mode context from the
/// reference-count and new-MV stack contexts.
let av1_compound_mode_ctx_map : Array[Array[Int]] = [
  [0, 1, 1, 1, 1],
  [1, 2, 3, 4, 4],
  [4, 4, 5, 6, 7],
]

///|
/// The mode tree of §5.11.24: whether the block codes a fresh vector, falls back
/// to the frame's global motion, or reuses one of the stacked candidates.
fn av1_read_inter_mode(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
  stack : Av1MvStack,
) -> Bool {
  if block.skip_mode {
    block.y_mode = av1_mode_nearest_nearest_mv
    return true
  }
  if block.segment_skip || block.segment_global {
    block.y_mode = av1_mode_global_mv
    return true
  }
  if block.ref_frame1 > av1_intra_frame {
    // A compound block reads one symbol: NEAREST_NEARESTMV or NEW_NEWMV.
    // The compound row is the reference-motion context halved, so 2 and 3 share
    // a row while 4, 5 and anything larger saturate at row two (AV1 §8.3).
    let row = if stack.refmv_context < 0 {
      0
    } else if stack.refmv_context > 5 {
      2
    } else {
      stack.refmv_context >> 1
    }
    let column = if stack.newmv_context < 0 {
      0
    } else if stack.newmv_context > 4 {
      4
    } else {
      stack.newmv_context
    }
    let ctx = av1_compound_mode_ctx_map[row][column]
    if ctx >= info.cdfs.compound_mode.length() {
      return false
    }
    block.y_mode = av1_mode_nearest_nearest_mv +
      decoder.symbol(info.cdfs.compound_mode[ctx])
    return true
  }
  if stack.newmv_context < 0 || stack.newmv_context >= info.cdfs.new_mv.length() {
    return false
  }
  if decoder.symbol(info.cdfs.new_mv[stack.newmv_context]) == 0 {
    block.y_mode = av1_mode_new_mv
    return true
  }
  if stack.zero_mv_context < 0 ||
    stack.zero_mv_context > 1 ||
    stack.zero_mv_context >= info.cdfs.zero_mv.length() {
    return false
  }
  if decoder.symbol(info.cdfs.zero_mv[stack.zero_mv_context]) == 0 {
    block.y_mode = av1_mode_global_mv
    return true
  }
  if stack.refmv_context < 0 || stack.refmv_context >= info.cdfs.ref_mv.length() {
    return false
  }
  block.y_mode = if decoder.symbol(info.cdfs.ref_mv[stack.refmv_context]) == 0 {
    av1_mode_nearest_mv
  } else {
    av1_mode_near_mv
  }
  true
}

///|
/// has_nearmv( ) (AV1 §5.11.24).
fn av1_has_nearmv(mode : Int) -> Bool {
  mode == av1_mode_near_mv
}

///|
/// The compound modes, as the `compound_mode` symbol indexes them (AV1 §5.11.24
/// against libdav1d's `comp_inter_pred_modes`): 0 NEAREST_NEARESTMV,
/// 1 NEAR_NEARMV, 2 NEW_NEWMV, 3 GLOBAL_GLOBALMV, 4 NEW_NEARESTMV,
/// 5 NEW_NEARMV, 6 NEAREST_NEWMV, 7 NEAR_NEWMV. The mode constants themselves
/// continue the single-reference numbering, so a compound mode is
/// `NEAREST_NEARESTMV + symbol`.

///|
/// A compound mode whose two lists both code a fresh vector (NEW_NEWMV), the
/// one whose depth reference index is read with the new-motion tree.
fn av1_compound_both_new(mode : Int) -> Bool {
  mode - av1_mode_nearest_nearest_mv == 7
}

///|
/// Whether one list of a compound mode codes a fresh vector.
fn av1_compound_list_new(mode : Int, list : Int) -> Bool {
  let offset = mode - av1_mode_nearest_nearest_mv
  if list == 0 {
    offset == 3 || offset == 5 || offset == 7
  } else {
    offset == 2 || offset == 4 || offset == 7
  }
}

///|
/// A compound mode with a NEARMV constituent, which reads the depth reference
/// index with the near-motion tree start (AV1 §5.11.24).
fn av1_compound_has_nearmv(mode : Int) -> Bool {
  let offset = mode - av1_mode_nearest_nearest_mv
  offset == 1 || offset == 4 || offset == 5
}

///|
/// A compound mode whose two lists both follow the frame's global motion, the
/// one that codes no subpel filter at all (AV1 §5.11.27).
fn av1_compound_both_global(mode : Int) -> Bool {
  mode - av1_mode_nearest_nearest_mv == 6
}

///|

///|
/// The depth reference list index (AV1 §5.11.24): walk the stacked candidates
/// and stop at the first one the bitstream selects. The two entry points differ
/// on purpose, which is why a NEARMV block can never reach candidate zero: its
/// index starts at 1 before any symbol is read, so a thin stack still yields the
/// candidate the global motion vector was parked in.
fn av1_read_drl_idx(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
  stack : Av1MvStack,
) -> Bool {
  // A compound block's NEW_NEWMV is the new-MV tree applied to both lists;
  // a compound mode with a NEARMV constituent reads the same index tree a
  // single NEARMV block does.
  let compound = block.ref_frame1 > av1_intra_frame
  let new_mv = if compound {
    av1_compound_both_new(block.y_mode)
  } else {
    block.y_mode == av1_mode_new_mv
  }
  let near_mv = if compound {
    av1_compound_has_nearmv(block.y_mode)
  } else {
    av1_has_nearmv(block.y_mode)
  }
  if !new_mv && !near_mv {
    // Only those two trees exist; every other mode names its candidate already.
    block.ref_mv_idx = 0
    return true
  }
  block.ref_mv_idx = if near_mv { 1 } else { 0 }
  let mut index = if new_mv { 0 } else { 1 }
  let limit = if near_mv { 3 } else { 2 }
  while index < limit {
    if stack.count <= index + 1 {
      return true
    }
    if index >= stack.drl_contexts.length() ||
      stack.drl_contexts[index] >= info.cdfs.drl_mode.length() {
      return false
    }
    if decoder.symbol(info.cdfs.drl_mode[stack.drl_contexts[index]]) == 0 {
      block.ref_mv_idx = index
      return true
    }
    block.ref_mv_idx = index + 1
    index = index + 1
  }
  true
}

///|
/// assign_mv( ) (AV1 §5.11.24): pick the predicted vector out of the stack and
/// code the difference only where the mode is new motion.
fn av1_assign_mv(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
  stack : Av1MvStack,
) -> Bool {
  if block.ref_frame1 > av1_intra_frame {
    // GLOBAL_GLOBALMV takes each list's frame-level global motion, which is
    // not on the candidate stack at all (AV1 §7.10.2.4).
    if av1_compound_both_global(block.y_mode) {
      block.mv_row = stack.global_rows[0]
      block.mv_col = stack.global_cols[0]
      block.mv_row1 = stack.global_rows[1]
      block.mv_col1 = stack.global_cols[1]
      return true
    }
    // Compound: both lists take the same mode, so NEW_NEWMV codes a fresh
    // vector per list against that list's candidate, and NEAREST_NEARESTMV
    // copies the nearest candidate of each list.
    // Which lists code a fresh vector depends on the mode: NEW_NEWMV codes
    // both, NEW_NEARESTMV and NEW_NEARMV only the first, NEAREST_NEWMV and
    // NEAR_NEWMV only the second (AV1 §7.10.2.6).
    let fresh0 = av1_compound_list_new(block.y_mode, 0)
    let fresh1 = av1_compound_list_new(block.y_mode, 1)
    if block.ref_mv_idx < 0 || block.ref_mv_idx >= av1_ref_mv_stack_size {
      return false
    }
    let position = if stack.count > 1 { block.ref_mv_idx } else { 0 }
    let first = av1_stack_mv(stack, position, 0)
    let second = av1_stack_mv(stack, position, 1)
    if fresh0 {
      let diff = av1_read_mv(decoder, info)
      block.mv_row = first.0 + diff.0
      block.mv_col = first.1 + diff.1
    } else {
      block.mv_row = first.0
      block.mv_col = first.1
    }
    if fresh1 {
      let diff = av1_read_mv(decoder, info)
      block.mv_row1 = second.0 + diff.0
      block.mv_col1 = second.1 + diff.1
    } else {
      block.mv_row1 = second.0
      block.mv_col1 = second.1
    }
    return true
  }
  if block.y_mode == av1_mode_global_mv {
    block.mv_row = stack.global_rows[0]
    block.mv_col = stack.global_cols[0]
    return true
  }
  let mut position = if block.y_mode == av1_mode_nearest_mv {
    0
  } else {
    block.ref_mv_idx
  }
  if block.y_mode == av1_mode_new_mv && stack.count <= 1 {
    position = 0
  }
  if position < 0 || position >= av1_ref_mv_stack_size {
    return false
  }
  let predicted = av1_stack_mv(stack, position, 0)
  if block.y_mode == av1_mode_new_mv {
    let diff = av1_read_mv(decoder, info)
    block.mv_row = predicted.0 + diff.0
    block.mv_col = predicted.1 + diff.1
  } else {
    block.mv_row = predicted.0
    block.mv_col = predicted.1
  }
  true
}

///|
/// read_mv( ) (AV1 §5.11.25): the joint names which components follow. Only the
/// intra block copy path uses the second motion vector context, and the frame
/// gate refuses it.
fn av1_read_mv(decoder : Av1SymbolDecoder, info : Av1InterInfo) -> (Int, Int) {
  let context = 0
  let joint = decoder.symbol(info.cdfs.mv_joint[context])
  let mut diff_row = 0
  let mut diff_col = 0
  if joint == av1_mv_joint_hzvnz || joint == av1_mv_joint_hnzvnz {
    diff_row = av1_read_mv_component(decoder, info, context, 0)
  }
  if joint == av1_mv_joint_hnzvz || joint == av1_mv_joint_hnzvnz {
    diff_col = av1_read_mv_component(decoder, info, context, 1)
  }
  (diff_row, diff_col)
}

///|
/// read_mv_component( comp ) (AV1 §5.11.25). Magnitude class 0 codes a short
/// offset directly; the higher classes code `mv_class` magnitude bits on top of
/// a class-sized base. Both paths end in the fractional bits the frame is
/// allowed to signal, and the sign is applied last.
fn av1_read_mv_component(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  context : Int,
  component : Int,
) -> Int {
  let cdfs = info.cdfs
  if component < 0 || component > 1 || context < 0 || context > 1 {
    return 0
  }
  let sign = decoder.symbol(cdfs.mv_sign[context][component])
  let class_cdf = cdfs.mv_class[context][component]
  let class = decoder.symbol(class_cdf)
  // Bounded by the table, not a constant: the largest class is still a valid
  // magnitude and codes one mv_bit per position below it.
  if class < 0 || class >= class_cdf.length() - 1 {
    return 0
  }
  let mut magnitude = 0
  if class == 0 {
    let bit = decoder.symbol(cdfs.mv_class0_bit[context][component])
    if bit < 0 || bit > 1 {
      return 0
    }
    let fraction = if info.frame.force_integer_mv != 0 {
      3
    } else {
      decoder.symbol(cdfs.mv_class0_fr[context][component][bit])
    }
    let high = if info.frame.allow_high_precision_mv {
      decoder.symbol(cdfs.mv_class0_hp[context][component])
    } else {
      1
    }
    magnitude = ((bit << 3) | (fraction << 1) | high) + 1
  } else {
    let bits_cdf = cdfs.mv_bit[context][component]
    let mut bits = 0
    for index in 0.. Bool {
  if block.avail_u {
    let w4 = block.width4
    let mut x4 = block.mi_col + 1
    while x4 < block.mi_col + w4 && x4 < field.cols {
      let pair = av1_neighbour_pair(field, block.mi_row - 1, x4)
      if pair[0] > av1_intra_frame {
        return true
      }
      x4 = x4 + 2
    }
  }
  if block.avail_l {
    let h4 = block.height4
    let mut y4 = block.mi_row + 1
    while y4 < block.mi_row + h4 && y4 < field.rows {
      let pair = av1_neighbour_pair(field, y4, block.mi_col - 1)
      if pair[0] > av1_intra_frame {
        return true
      }
      y4 = y4 + 2
    }
  }
  false
}

///|
/// read_motion_mode( isCompound ) (AV1 §5.11.27). Returns false when the block
/// selects an invalid symbol. Only single-reference, non-interintra blocks can
/// choose OBMC or LOCALWARP; compound and skip-mode blocks imply SIMPLE.
fn av1_read_motion_mode(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
  have_top_right? : Bool = true,
) -> Bool {
  block.motion_mode = 0
  if block.skip_mode ||
    block.ref_frame1 > av1_intra_frame ||
    !info.frame.is_motion_mode_switchable {
    return true
  }
  // An interintra block keeps one vector, so the motion-mode symbol is gone
  // from its syntax (AV1 §5.11.26).
  if block.interintra != 0 {
    return true
  }
  let smaller = if block.block_width < block.block_height {
    block.block_width
  } else {
    block.block_height
  }
  if smaller < 8 {
    return true
  }
  if info.frame.force_integer_mv == 0 &&
    block.y_mode == av1_mode_global_mv &&
    av1_gm_type(info, block.ref_frame) > av1_gm_translation {
    return true
  }
  // A single-reference block names NONE as its second reference, so the
  // compound and intra-second-reference early exits do not apply to it.
  if !av1_has_overlappable_candidates(info.field, block) {
    return true
  }
  let size_index = block.size_index
  if size_index < 0 || size_index >= info.cdfs.use_obmc.length() {
    return false
  }
  // A matching, already decoded neighbour permits the three-symbol warp row.
  // Keep the top-right candidate within both tile and partition boundaries.
  let (top_mask, left_mask) = av1_warp_matching_masks(
    info.field,
    block.mi_row,
    block.mi_col,
    block.width4,
    block.height4,
    block.width4,
    block.height4,
    block.avail_l,
    block.avail_u,
    block.ref_frame,
    have_top_right=have_top_right &&
      block.mi_col + block.width4 < info.tile_col_end,
  )
  if info.frame.force_integer_mv == 0 &&
    (top_mask != 0L || left_mask != 0L) &&
    info.frame.allow_warped_motion &&
    !av1_mv_is_scaled(info, block.ref_frame) {
    // The three-symbol `motion_mode`: 0 = SIMPLE, 1 = OBMC, 2 = LOCALWARP.
    if size_index >= info.cdfs.motion_mode.length() {
      return false
    }
    let mode = decoder.symbol(info.cdfs.motion_mode[size_index])
    if mode < 0 || mode > 2 {
      return false
    }
    if mode == 2 {
      // LOCALWARP estimates a warp model from the motion vectors of the
      // already-decoded neighbours and predicts through it (§7.11.3.5,
      // §7.11.3.8). A model the prediction cannot represent - a shear, or a
      // singular fit - comes back as none, which AV1 decodes as identity: the
      // block then predicts with its own motion vector.
      block.warp = match
        av1_warp_derive(
          info.field,
          block.mi_row,
          block.mi_col,
          block.width4,
          block.height4,
          block.width4,
          block.height4,
          block.avail_l,
          block.avail_u,
          block.ref_frame,
          block.mv_row,
          block.mv_col,
          have_top_right=have_top_right &&
            block.mi_col + block.width4 < info.tile_col_end,
        ) {
        Some(model) => [model]
        None => []
      }
    }
    block.motion_mode = mode
    return true
  }
  if decoder.symbol(info.cdfs.use_obmc[size_index]) == 1 {
    block.motion_mode = 1
  }
  true
}

///|
/// `is_scaled( refFrame )` (AV1 §5.11.26): true when a stored reference's
/// visible extent differs from the current frame's. Reference scaling itself is
/// outside this decoder's scope, so this is the only per-frame test needed.
fn av1_mv_is_scaled(info : Av1InterInfo, name : Int) -> Bool {
  match av1_inter_reference(info, name) {
    Some(frame) =>
      frame.width != info.frame.frame_width ||
      frame.height != info.frame.frame_height
    None => false
  }
}

///|
/// The interpolation filter of §5.11.27, and the §8.3 context that selects its
/// row. A frame with a fixed filter sets both directions without a symbol.
fn av1_read_interp_filter(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
) -> Unit {
  // Skip mode and local warp do not code interpolation filters. Their ordinary
  // motion-compensation fallback always uses REGULAR, regardless of neighbours
  // or the frame's fixed interpolation filter.
  if block.skip_mode || block.motion_mode == 2 {
    block.interp_vertical = av1_interp_eighttap
    block.interp_horizontal = av1_interp_eighttap
    return
  }
  if info.interpolation_filter != av1_interp_switchable {
    block.interp_vertical = info.interpolation_filter
    block.interp_horizontal = info.interpolation_filter
    return
  }
  let needs = av1_needs_interp_filter(info, block)
  let mut vertical = av1_interp_eighttap
  if needs {
    let ctx = av1_interp_filter_ctx(info, block, 0)
    if ctx < info.cdfs.interp_filter.length() {
      vertical = decoder.symbol(info.cdfs.interp_filter[ctx])
    }
  }
  block.interp_vertical = vertical
  block.interp_horizontal = if info.enable_dual_filter && needs {
    let ctx = av1_interp_filter_ctx(info, block, 1)
    if ctx < info.cdfs.interp_filter.length() {
      decoder.symbol(info.cdfs.interp_filter[ctx])
    } else {
      vertical
    }
  } else {
    vertical
  }
}

///|
/// needs_interp_filter( ) (AV1 §5.11.27): a large block driven by a non-identity
/// warp model has no subpel phase of its own to filter at.
fn av1_needs_interp_filter(info : Av1InterInfo, block : Av1InterBlock) -> Bool {
  let smaller = if block.block_width < block.block_height {
    block.block_width
  } else {
    block.block_height
  }
  if block.skip_mode {
    return false
  }
  if smaller < 8 {
    return true
  }
  // A LOCALWARP block is filtered by the warp grid itself, one filter per
  // sample, so neither filter symbol is in its syntax at all (AV1 §5.11.27).
  if block.motion_mode == 2 {
    return false
  }
  if block.ref_frame1 > av1_intra_frame {
    // A compound block reads the symbol unless both of its lists are the
    // frame's global motion (AV1 §5.11.27), which is the one compound mode
    // whose two vectors have no subpel phase of their own.
    if av1_compound_both_global(block.y_mode) {
      return av1_gm_type(info, block.ref_frame) == av1_gm_translation &&
        av1_gm_type(info, block.ref_frame1) == av1_gm_translation
    }
    return true
  }
  if block.y_mode != av1_mode_global_mv {
    return true
  }
  av1_gm_type(info, block.ref_frame) == av1_gm_translation
}

///|
fn av1_gm_type(info : Av1InterInfo, name : Int) -> Int {
  if name < 0 || name >= info.frame.gm_type.length() {
    av1_gm_identity
  } else {
    info.frame.gm_type[name]
  }
}

///|
/// interp_filter_ctx( dir ) (AV1 §8.3): only a neighbour that coded from the same
/// reference contributes its own filter choice.
fn av1_interp_filter_ctx(
  info : Av1InterInfo,
  block : Av1InterBlock,
  direction : Int,
) -> Int {
  // AV1 §8.3: the direction and the compound flag select the row group, the
  // neighbours' filters the offset inside it.
  let compound = if block.ref_frame1 > av1_intra_frame { 1 } else { 0 }
  let ctx = ((direction & 1) * 2 + compound) * 4
  let mut left = 3
  let mut above = 3
  if block.avail_l &&
    av1_unit_uses_reference(
      info.field,
      block.mi_row,
      block.mi_col - 1,
      block.ref_frame,
    ) {
    left = av1_field_interp(
      info.field,
      block.mi_row,
      block.mi_col - 1,
      direction,
    )
  }
  if block.avail_u &&
    av1_unit_uses_reference(
      info.field,
      block.mi_row - 1,
      block.mi_col,
      block.ref_frame,
    ) {
    above = av1_field_interp(
      info.field,
      block.mi_row - 1,
      block.mi_col,
      direction,
    )
  }
  let shared = if left == above {
    left
  } else if left == 3 {
    above
  } else if above == 3 {
    left
  } else {
    3
  }
  ctx + shared
}

///|
fn av1_unit_uses_reference(
  field : Av1MotionField,
  row : Int,
  col : Int,
  name : Int,
) -> Bool {
  if row < 0 || col < 0 || row >= field.rows || col >= field.cols {
    return false
  }
  let unit = (row * field.cols + col) * 2
  field.refs[unit] == name || field.refs[unit + 1] == name
}

///|
fn av1_field_interp(
  field : Av1MotionField,
  row : Int,
  col : Int,
  direction : Int,
) -> Int {
  if row < 0 || col < 0 || row >= field.rows || col >= field.cols {
    return 3
  }
  let unit = row * field.cols + col
  if direction == 0 {
    field.interp_v[unit]
  } else {
    field.interp_h[unit]
  }
}

///|
/// inter_block_mode_info( ) for a block whose reference frames are already known
/// (AV1 §5.11.24): derive the candidate stack, read the mode and the vector, then
/// the filters.
fn av1_inter_block_motion(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
  have_top_right? : Bool = true,
) -> Bool {
  if !av1_read_ref_frames(decoder, info, block) {
    return false
  }
  let stack = match
    av1_find_mv_stack(
      info.field,
      {
        mi_row: block.mi_row,
        mi_col: block.mi_col,
        width4: block.width4,
        height4: block.height4,
        block_width: block.block_width,
        block_height: block.block_height,
        refs: [block.ref_frame, block.ref_frame1],
        tile_row_start: info.tile_row_start,
        tile_row_end: info.tile_row_end,
        tile_col_start: info.tile_col_start,
        tile_col_end: info.tile_col_end,
      },
      info.frame,
      block.ref_frame1 > av1_intra_frame,
    ) {
    Some(value) => value
    None => return false
  }
  if !av1_read_inter_mode(decoder, info, block, stack) {
    return false
  }
  if !av1_read_drl_idx(decoder, info, block, stack) {
    return false
  }
  if !av1_assign_mv(decoder, info, block, stack) {
    return false
  }
  if !av1_read_interintra_mode(decoder, info, block) {
    return false
  }
  if !av1_read_motion_mode(decoder, info, block, have_top_right~) {
    return false
  }
  if !av1_read_compound_type(decoder, info, block) {
    return false
  }
  av1_read_interp_filter(decoder, info, block)
  true
}

///|
/// read_interintra_mode( isCompound ) (AV1 §5.11.25): an inter block between
/// 8x8 and 32x32 codes one symbol saying whether it also mixes in an intra
/// prediction, then the intra mode and - for the wedge variant - the wedge
/// index. The `interintra` and `interintra_mode` rows are dav1d's
/// `ymode_size_context` minus one; the two wedge tables are indexed by the
/// block size itself (AV1 §9.3.8-9).
fn av1_read_interintra_mode(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
) -> Bool {
  block.interintra = 0
  if !info.enable_interintra_compound || block.ref_frame1 > av1_intra_frame {
    return true
  }
  if !(block.width4 >= 2 &&
    block.height4 >= 2 &&
    block.width4 <= 8 &&
    block.height4 <= 8 &&
    block.width4 <= block.height4 * 2 &&
    block.height4 <= block.width4 * 2) {
    return true
  }
  let group = av1_ymode_size_context(block.width4 * 4, block.height4 * 4)
  if group < 1 || group > 3 {
    return true
  }
  let ctx = group - 1
  if ctx >= info.cdfs.inter_intra.length() {
    return false
  }
  if decoder.symbol(info.cdfs.inter_intra[ctx]) == 0 {
    return true
  }
  block.interintra = 1
  if ctx >= info.cdfs.inter_intra_mode.length() {
    return false
  }
  block.interintra_mode = decoder.symbol(info.cdfs.inter_intra_mode[ctx])
  let wedge_ctx = match
    av1_intra_block_index(block.width4 * 4, block.height4 * 4) {
    Some(index) => index
    None => return false
  }
  if wedge_ctx < 0 || wedge_ctx >= info.cdfs.wedge_inter_intra.length() {
    return false
  }
  block.wedge_interintra = decoder.symbol(
    info.cdfs.wedge_inter_intra[wedge_ctx],
  )
  if block.wedge_interintra != 0 {
    if !av1_wedge_shape_ok(block.width4 * 4, block.height4 * 4) {
      return false
    }
    if wedge_ctx >= info.cdfs.wedge_index.length() {
      return false
    }
    block.wedge_index_ii = decoder.symbol(info.cdfs.wedge_index[wedge_ctx])
  }
  true
}

///|
/// The intra-variant blend weights (AV1 §7.11.3.17 `ii_weights_1d`,
/// go-av1 `decode/interintra_tables.go`): the intra share along one axis,
/// scaled by the block's larger dimension.
let av1_ii_weights : Array[Int] = [
  60, 58, 56, 54, 52, 50, 48, 47, 45, 44, 42, 41, 39, 38, 37, 35, 34, 33, 32, 31,
  30, 29, 28, 27, 26, 25, 24, 23, 22, 22, 21, 20, 19, 19, 18, 18, 17, 16, 16, 15,
  15, 14, 14, 13, 13, 12, 12, 12, 11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8, 8, 7, 7,
  7, 7, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3,
  3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
]

///|
/// The intra mode an `interintra_mode` symbol selects (AV1 §5.11.25).
fn av1_interintra_mode(mode : Int) -> Int {
  match mode {
    1 => AV1_INTRA_V
    2 => AV1_INTRA_H
    3 => AV1_INTRA_SMOOTH
    _ => AV1_INTRA_DC
  }
}

///|
/// The intra-variant blend weight at one sample (AV1 §7.11.3.17).
fn av1_ii_weight(mode : Int, i : Int, j : Int, scale : Int) -> Int {
  match mode {
    1 => av1_ii_weights[i * scale]
    2 => av1_ii_weights[j * scale]
    3 => av1_ii_weights[(if i < j { i } else { j }) * scale]
    _ => 32
  }
}

///|
/// dav1d's `dav1d_ymode_size_context` (`src/tables.c:250`): 0 for the sub-8x8
/// blocks, 1 for 8x8..16x8/32x8, 2 for 16x16..64x16, 3 for the rest. The
/// interintra and interintra-mode rows are indexed by it minus one.
fn av1_ymode_size_context(width : Int, height : Int) -> Int {
  let small = if width < height { width } else { height }
  if small <= 4 {
    0
  } else if small == 8 {
    1
  } else if small == 16 {
    2
  } else {
    3
  }
}

///|

///|
/// read_compound_type( isCompound ) (AV1 §5.11.25): which blend a compound
/// block uses. With `enable_masked_compound` off - what the frame gate used to
/// insist on - the answer is COMPOUND_AVERAGE or COMPOUND_DISTANCE and no
/// `comp_group_idx` is read at all. With it on, a group index of one asks for
/// a masked blend, and `compound_type` then picks the wedge against the
/// difference weighting, each of which reads its own parameter.
fn av1_read_compound_type(
  decoder : Av1SymbolDecoder,
  info : Av1InterInfo,
  block : Av1InterBlock,
) -> Bool {
  if block.ref_frame1 <= av1_intra_frame {
    return true
  }
  block.compound_type = av1_compound_average
  block.compound_idx = 1
  block.comp_group_idx = 0
  if block.skip_mode {
    return true
  }
  if info.enable_masked_compound {
    // `comp_group_idx` asks for a masked blend, so the next symbol is the
    // wedge/difference one and not the jnt-comp one.
    let ctx = av1_comp_group_idx_ctx(info, block)
    if ctx < 0 || ctx >= info.cdfs.comp_group_idx.length() {
      return false
    }
    let group = decoder.symbol(info.cdfs.comp_group_idx[ctx])
    block.comp_group_idx = group
    if group != 0 {
      let size = match
        av1_intra_block_index(block.width4 * 4, block.height4 * 4) {
        Some(index) => index
        None => return false
      }
      // A block size with no wedge bits can only ask for the difference
      // weighting, so that symbol is not there at all (AV1 §5.11.25).
      let masked = if !av1_wedge_shape_ok(block.width4 * 4, block.height4 * 4) {
        av1_compound_diffwtd
      } else {
        if size < 0 || size >= info.cdfs.compound_type.length() {
          return false
        }
        if decoder.symbol(info.cdfs.compound_type[size]) == 0 {
          av1_compound_wedge
        } else {
          av1_compound_diffwtd
        }
      }
      block.compound_type = masked
      if masked == av1_compound_diffwtd {
        block.mask_type = decoder.bool()
      } else {
        if size < 0 || size >= info.cdfs.wedge_index.length() {
          return false
        }
        block.wedge_index = decoder.symbol(info.cdfs.wedge_index[size])
        block.wedge_sign = decoder.bool()
      }
      return true
    }
  }
  if info.enable_jnt_comp {
    // `compound_idx` picks the distance weighting over the average.
    let ctx = av1_compound_idx_ctx(info, block)
    if ctx < 0 || ctx >= info.cdfs.compound_idx.length() {
      return false
    }
    let idx = decoder.symbol(info.cdfs.compound_idx[ctx])
    block.compound_idx = idx
    if idx == 0 {
      block.compound_type = av1_compound_distance
    }
  }
  true
}

///|
/// One per-MI grid's value at a position, zero outside the coded extent.
fn av1_field_grid(
  grid : Array[Int],
  cols : Int,
  rows : Int,
  row : Int,
  col : Int,
) -> Int {
  if row < 0 || col < 0 || row >= rows || col >= cols {
    0
  } else {
    grid[row * cols + col]
  }
}

///|
/// `comp_group_idx` context (AV1 §8.3): each neighbour contributes its own
/// group, or - for a single-reference neighbour naming ALTREF - three.
fn av1_comp_group_idx_ctx(info : Av1InterInfo, block : Av1InterBlock) -> Int {
  let field = info.field
  let mut ctx = 0
  if block.avail_u {
    let above = av1_neighbour_pair(field, block.mi_row - 1, block.mi_col)
    if above[1] == av1_none_frame {
      if above[0] == av1_alt_ref_frame {
        ctx = ctx + 3
      }
    } else {
      ctx = ctx +
        av1_field_grid(
          field.comp_group_idxs,
          field.cols,
          field.rows,
          block.mi_row - 1,
          block.mi_col,
        )
    }
  }
  if block.avail_l {
    let left = av1_neighbour_pair(field, block.mi_row, block.mi_col - 1)
    if left[1] == av1_none_frame {
      if left[0] == av1_alt_ref_frame {
        ctx = ctx + 3
      }
    } else {
      ctx = ctx +
        av1_field_grid(
          field.comp_group_idxs,
          field.cols,
          field.rows,
          block.mi_row,
          block.mi_col - 1,
        )
    }
  }
  // Two single ALTREF neighbours contribute three each; AV1 §9.3.4
  // saturates this sum to the final one of the six comp_group_idx rows.
  if ctx > 5 {
    5
  } else {
    ctx
  }
}

///|
/// `compound_idx` context (AV1 §8.3): three base when the two references sit
/// at equal distance, plus each neighbour's own compound_idx - or, for a
/// single-reference neighbour naming ALTREF, one.
fn av1_compound_idx_ctx(info : Av1InterInfo, block : Av1InterBlock) -> Int {
  let field = info.field
  let mut ctx = if av1_clipped_distance(info, block.ref_frame) ==
    av1_clipped_distance(info, block.ref_frame1) {
    3
  } else {
    0
  }
  if block.avail_u {
    let above = av1_neighbour_pair(field, block.mi_row - 1, block.mi_col)
    if above[1] == av1_none_frame {
      if above[0] == av1_alt_ref_frame {
        ctx = ctx + 1
      }
    } else {
      ctx = ctx +
        av1_field_grid(
          field.compound_idxs,
          field.cols,
          field.rows,
          block.mi_row - 1,
          block.mi_col,
        )
    }
  }
  if block.avail_l {
    let left = av1_neighbour_pair(field, block.mi_row, block.mi_col - 1)
    if left[1] == av1_none_frame {
      if left[0] == av1_alt_ref_frame {
        ctx = ctx + 1
      }
    } else {
      ctx = ctx +
        av1_field_grid(
          field.compound_idxs,
          field.cols,
          field.rows,
          block.mi_row,
          block.mi_col - 1,
        )
    }
  }
  ctx
}

///|
/// The clipped distance of one reference name, already computed per frame.
fn av1_clipped_distance(info : Av1InterInfo, name : Int) -> Int {
  if name < 0 || name >= info.ref_distances.length() {
    0
  } else {
    info.ref_distances[name]
  }
}

///|
/// The distance-weighted blend weights (AV1 §7.11.3.15), from the two reference
/// names' clipped distances and the quantized weight tables.
fn av1_distance_weights(
  info : Av1InterInfo,
  ref0 : Int,
  ref1 : Int,
) -> (Int, Int) {
  let av1_quant_dist_weight : Array[Array[Int]] = [
    [2, 3],
    [2, 5],
    [2, 7],
    [1, 31],
  ]
  let av1_quant_dist_lookup : Array[Array[Int]] = [
    [9, 7],
    [11, 5],
    [12, 4],
    [13, 3],
  ]
  if ref0 < 0 || ref0 >= info.ref_distances.length() {
    return (av1_quant_dist_lookup[3][0], av1_quant_dist_lookup[3][1])
  }
  if ref1 < 0 || ref1 >= info.ref_distances.length() {
    return (av1_quant_dist_lookup[3][0], av1_quant_dist_lookup[3][1])
  }
  let d0 = info.ref_distances[ref1]
  let d1 = info.ref_distances[ref0]
  let order = if d0 <= d1 { 1 } else { 0 }
  if d0 == 0 || d1 == 0 {
    return (
      av1_quant_dist_lookup[3][order],
      av1_quant_dist_lookup[3][1 - order],
    )
  }
  let mut i = 0
  while i < 3 {
    let c0 = av1_quant_dist_weight[i][order]
    let c1 = av1_quant_dist_weight[i][1 - order]
    if order != 0 {
      if d0 * c0 > d1 * c1 {
        break
      }
    } else if d0 * c0 < d1 * c1 {
      break
    }
    i = i + 1
  }
  (av1_quant_dist_lookup[i][order], av1_quant_dist_lookup[i][1 - order])
}

///|
/// Each reference name's clipped distance from this frame's order hint
/// (AV1 §7.11.3.15's `dist[ref]`), computed once per frame.
fn av1_ref_distances(
  sequence : Av1SequenceInfo,
  header : Av1FrameHeaderInfo,
) -> Array[Int] {
  let distances = Array::make(av1_num_ref_frames, 0)
  for name in av1_last_frame..<=av1_alt_ref_frame {
    let hint = header.order_hints[name]
    let raw = av1_relative_dist(sequence, hint, header.order_hint)
    let clipped = if raw < 0 { -raw } else { raw }
    distances[name] = if clipped > 31 { 31 } else { clipped }
  }
  distances
}

///|
/// The frame-level half of the inter state, shared by every tile of one frame.
/// The motion field spans the whole coded frame, because a block's candidates
/// are read from whatever was decoded before it in raster order.
pub(all) struct Av1InterFrame {
  field : Av1MotionField
  frame : Av1MvFrame
  /// Stored reference frames indexed by reference name, so a block's
  /// `RefFrame` selects its buffer directly.
  refs : Array[Av1RefFrame?]
  interpolation_filter : Int
  enable_dual_filter : Bool
  skip_mode_present : Bool
  skip_mode_frame : Array[Int]
  reference_select : Bool
  enable_interintra_compound : Bool
  ref_distances : Array[Int]
  enable_masked_compound : Bool
  enable_jnt_comp : Bool
  mi_cols : Int
  mi_rows : Int
}

///|
/// Bind the frame's named references to its decoded buffers and size the motion
/// field (AV1 §7.8). A name whose slot was never refreshed has no buffer, and
/// the block stage refuses to predict from it.
pub fn av1_inter_frame_of(
  header : Av1FrameHeaderInfo,
  sequence : Av1SequenceInfo,
  map : Av1FrameMap,
) -> Av1InterFrame? {
  let coded_width = av1_partition_coded_extent(header.frame_width)
  let coded_height = av1_partition_coded_extent(header.frame_height)
  let mi_cols = coded_width / 4
  let mi_rows = coded_height / 4
  if mi_cols <= 0 || mi_rows <= 0 {
    return None
  }
  if header.ref_frame_idx.length() != av1_refs_per_frame {
    return None
  }
  let refs : Array[Av1RefFrame?] = Array::make(av1_num_ref_frames, None)
  for name in av1_last_frame..<=av1_alt_ref_frame {
    let slot = header.ref_frame_idx[name - av1_last_frame]
    if slot < 0 || slot >= av1_num_ref_frames {
      return None
    }
    refs[name] = map.slots[slot]
  }
  // `motion_field_estimation` runs while reading the uncompressed header, before
  // any tile is decoded, so the grid is built from the reference buffers as they
  // stand before this frame is stored.
  let temporal = if header.use_ref_frame_mvs {
    Some(
      av1_motion_field_estimation(
        sequence,
        header.order_hint,
        header.order_hints,
        refs,
        mi_cols,
        mi_rows,
      ),
    )
  } else {
    None
  }
  Some({
    field: av1_motion_field(mi_cols, mi_rows),
    frame: av1_mv_frame_of(header, temporal~),
    refs,
    interpolation_filter: header.interpolation_filter,
    enable_dual_filter: sequence.enable_dual_filter,
    skip_mode_present: header.skip_mode_present,
    skip_mode_frame: header.skip_mode_frame,
    reference_select: header.reference_select,
    enable_interintra_compound: sequence.enable_interintra_compound,
    ref_distances: av1_ref_distances(sequence, header),
    enable_masked_compound: sequence.enable_masked_compound,
    enable_jnt_comp: sequence.enable_jnt_comp,
    mi_cols,
    mi_rows,
  })
}

///|
/// The per-tile view of a frame's inter state. Each tile restarts probability
/// adaptation from the frame context, so it gets its own rows: the inherited
/// snapshot when the frame names a primary reference, the normative defaults
/// otherwise.
fn av1_inter_info(
  config : Av1InterFrame,
  tile_row_start : Int,
  tile_col_start : Int,
  tile_row_end : Int,
  tile_col_end : Int,
  cdfs? : Av1FrameCdfs? = None,
) -> Av1InterInfo {
  {
    cdfs: av1_inter_cdfs_of(cdfs),
    field: config.field,
    frame: config.frame,
    refs: config.refs,
    interpolation_filter: config.interpolation_filter,
    enable_dual_filter: config.enable_dual_filter,
    skip_mode_present: config.skip_mode_present,
    skip_mode_frame: config.skip_mode_frame,
    reference_select: config.reference_select,
    enable_interintra_compound: config.enable_interintra_compound,
    ref_distances: config.ref_distances,
    enable_masked_compound: config.enable_masked_compound,
    enable_jnt_comp: config.enable_jnt_comp,
    mi_cols: config.mi_cols,
    mi_rows: config.mi_rows,
    tile_row_start,
    tile_col_start,
    tile_row_end,
    tile_col_end,
  }
}