/// The frame entropy context AV1 snapshots per reference slot (AV1 §6.10
/// `disable_cdf_update`, §8.3 `frame_end_update_cdf`, §7.20 `save_cdfs`,
/// §5.9 tile adaptation, §9.3 `init_coeff_cdfs` / `init_non_coeff_cdfs`).
///
/// A snapshot is an ordered list of CDF tables, each table the row-major list
/// of q15 rows a symbol decoder adapts. Every tile starts its tables from the
/// frame context (the spec's `TileXxx` arrays are working copies of the frame
/// arrays), adapts them while symbols are decoded and, when
/// `disable_frame_end_update_cdf` is 0, the context_update_tile_id tile writes
/// its final rows back into the frame context the next frame inherits through
/// `primary_ref_frame`.
///
/// Table order (capture, restore and defaults keep this order):
///   0..4    partition trees w8, w16, w32, w64, w128
///   5..45   intra tile state: skip, y_mode, uv_cfl, uv_no_cfl, angle_delta,
///           cfl_sign, cfl_alpha, palette_y_mode, palette_uv_mode,
///           palette_y_size, palette_uv_size, palette_y_index, palette_uv_index,
///           filter_intra, filter_intra_mode_cdf, txb_skip, tx_set1, tx_set2,
///           luma eob, eob512, eob_small, extra, base_eob, base, br, sign,
///           chroma eob, eob512, eob_small, extra, base_eob, base, br, sign,
///           vartx, tx_depth 8/16/32/64, seg_id, delta_q
///   46..66  inter mode rows: single_ref, new_mv, zero_mv, ref_mv, drl_mode,
///           is_inter, skip_mode, interp_filter, y_mode, tx_type_set1, set2,
///           set3, mv_joint, mv_sign, mv_class, mv_class0_bit, mv_class0_fr,
///           mv_class0_hp, mv_bit, mv_fr, mv_hp
///   67..81  inter tools: use_obmc, motion_mode, comp_mode, comp_ref_type,
///           compound_mode, compound_idx, comp_group_idx, uni_comp_ref,
///           comp_ref, comp_bwd_ref, compound_type, inter_intra,
///           inter_intra_mode, wedge_inter_intra, wedge_index
///   82..84  loop restoration selectors: switchable, wiener, sgr
///
/// Each table keeps the shape its default constructor produces, so a restored
/// snapshot is shape-identical to the defaults a tile would otherwise start
/// from and every row can be copied in place. An intra frame has no inter rows
/// to adapt; its snapshots still carry the inter defaults so a later inter
/// frame restoring the snapshot sees every table.

///|
/// Whether a frame inherits the entropy context stored in its primary
/// reference slot (AV1 §6.10 `load_cdfs`). The snapshot save in
/// `av1_frame_map_update` and the capture in `av1_decode_intra_tile_state`
/// always run, so the machinery is exercised end to end.
let av1_cdf_load_enabled : Bool = true

///|
pub(all) struct Av1FrameCdfs {
  tables : Array[Array[Array[Int]]]
}

///|
let av1_cdf_partition_count : Int = 5

///|
let av1_cdf_intra_count : Int = 41

///|
let av1_cdf_inter_count : Int = 36

///|
let av1_cdf_lr_count : Int = 3

///|
let av1_cdf_inter_base : Int = av1_cdf_partition_count + av1_cdf_intra_count

///|
let av1_cdf_lr_base : Int = av1_cdf_inter_base + av1_cdf_inter_count

///|
fn av1_cdf_rows_copy(rows : Array[Array[Int]]) -> Array[Array[Int]] {
  rows.map(fn(row) { row.copy() })
}

///|
/// Flatten one group of a 3-level live structure into rows.
fn av1_cdf_rows_of3(rows : Array[Array[Array[Int]]]) -> Array[Array[Int]] {
  let flat : Array[Array[Int]] = []
  for group in rows {
    for row in group {
      flat.push(row.copy())
    }
  }
  flat
}

///|
/// Flatten two groups of a 4-level live structure into rows.
fn av1_cdf_rows_of4(
  rows : Array[Array[Array[Array[Int]]]],
) -> Array[Array[Int]] {
  let flat : Array[Array[Int]] = []
  for outer in rows {
    for group in outer {
      for row in group {
        flat.push(row.copy())
      }
    }
  }
  flat
}

///|
/// Treat a single row as a one-row table.
fn av1_cdf_row_of(row : Array[Int]) -> Array[Array[Int]] {
  [row.copy()]
}

///|
/// Copy one row's contents in place. A shape mismatch cannot happen because
/// every table shape is fixed by its default constructor.
fn av1_cdf_row_into(dst : Array[Int], src : Array[Int]) -> Unit {
  if dst.length() != src.length() {
    return
  }
  for i in 0.. Unit {
  if dst.length() != table.length() {
    return
  }
  for i in 0.. Unit {
  let mut index = 0
  for group in dst {
    for row in group {
      if index >= table.length() {
        return
      }
      av1_cdf_row_into(row, table[index])
      index = index + 1
    }
  }
}

///|
/// Copy a table's row contents into a 4-level live structure.
fn av1_cdf_rows_into4(
  dst : Array[Array[Array[Array[Int]]]],
  table : Array[Array[Int]],
) -> Unit {
  let mut index = 0
  for outer in dst {
    for group in outer {
      for row in group {
        if index >= table.length() {
          return
        }
        av1_cdf_row_into(row, table[index])
        index = index + 1
      }
    }
  }
}

///|
/// Copy a one-row table into a single live row.
fn av1_cdf_row_into1(dst : Array[Int], table : Array[Array[Int]]) -> Unit {
  if table.length() != 1 {
    return
  }
  av1_cdf_row_into(dst, table[0])
}

///|
/// Deep copy a snapshot so a stored context is private to the reference slot.
fn av1_frame_cdfs_copy(snapshot : Av1FrameCdfs) -> Av1FrameCdfs {
  { tables: snapshot.tables.map(fn(table) { av1_cdf_rows_copy(table) }), }
}

///|
/// Zero the adaptation counter - the last entry of every row - in every table.
///
/// AV1's adaptation rate depends on how many symbols a row has already seen, so
/// a frame context that is saved for a later frame to load must restart that
/// schedule from zero: libaom's `av1_reset_cdf_symbol_counters` does exactly
/// this at `frame_end_update_cdf`, and the go-av1 port applies it in
/// `resetCounts` with the same reasoning. Without the reset, a loading frame
/// inherits the saving frame's counters, and from its very first adaptation the
/// two decoders move their rows at different rates.
fn av1_frame_cdfs_reset_counts(cdfs : Av1FrameCdfs) -> Unit {
  for table in cdfs.tables {
    for row in table {
      if row.length() > 0 {
        row[row.length() - 1] = 0
      }
    }
  }
}

///|
/// Append table groups into one ordered list.
fn av1_cdf_tables_concat(
  groups : Array[Array[Array[Array[Int]]]],
) -> Array[Array[Array[Int]]] {
  let tables : Array[Array[Array[Int]]] = []
  for group in groups {
    for table in group {
      tables.push(table)
    }
  }
  tables
}

///|
/// Partition trees: capture or restore the five superblock-size trees.
fn av1_partition_cdfs_capture(
  state : Av1PartitionCdfState,
) -> Array[Array[Array[Int]]] {
  [
    av1_cdf_rows_copy(state.w8),
    av1_cdf_rows_copy(state.w16),
    av1_cdf_rows_copy(state.w32),
    av1_cdf_rows_copy(state.w64),
    av1_cdf_rows_copy(state.w128),
  ]
}

///|
fn av1_partition_cdfs_restore(
  state : Av1PartitionCdfState,
  snapshot : Av1FrameCdfs,
) -> Unit {
  let tables = snapshot.tables
  if tables.length() < av1_cdf_partition_count {
    return
  }
  av1_cdf_rows_into2(state.w8, tables[0])
  av1_cdf_rows_into2(state.w16, tables[1])
  av1_cdf_rows_into2(state.w32, tables[2])
  av1_cdf_rows_into2(state.w64, tables[3])
  av1_cdf_rows_into2(state.w128, tables[4])
}

///|
/// A tile's partition trees: the snapshot's rows when one is inherited, the
/// normative defaults otherwise.
fn av1_partition_cdfs_of(snapshot : Av1FrameCdfs?) -> Av1PartitionCdfState {
  let state = av1_partition_cdf_state()
  match snapshot {
    Some(value) => av1_partition_cdfs_restore(state, value)
    None => ()
  }
  state
}

///|
/// The intra tile state tables, in the order the file header documents.
fn av1_intra_cdfs_capture(state : Av1IntraState) -> Array[Array[Array[Int]]] {
  let tables : Array[Array[Array[Int]]] = []
  tables.push(av1_cdf_rows_copy(state.skip_cdfs))
  tables.push(av1_cdf_rows_of3(state.y_mode))
  tables.push(av1_cdf_rows_copy(state.uv_cfl))
  tables.push(av1_cdf_rows_copy(state.uv_no_cfl))
  tables.push(av1_cdf_rows_copy(state.angle_delta))
  tables.push(av1_cdf_row_of(state.cfl.sign))
  tables.push(av1_cdf_rows_copy(state.cfl.alpha))
  tables.push(av1_cdf_rows_of3(state.palette.y_mode))
  tables.push(av1_cdf_rows_copy(state.palette.uv_mode))
  tables.push(av1_cdf_rows_copy(state.palette.y_size))
  tables.push(av1_cdf_rows_copy(state.palette.uv_size))
  tables.push(av1_cdf_rows_of3(state.palette.y_index))
  tables.push(av1_cdf_rows_of3(state.palette.uv_index))
  tables.push(av1_cdf_rows_copy(state.filter_intra))
  tables.push(av1_cdf_row_of(state.filter_intra_mode_cdf))
  tables.push(av1_cdf_rows_of3(state.tables.txb_skip))
  tables.push(av1_cdf_rows_of3(state.tables.tx_set1))
  tables.push(av1_cdf_rows_of3(state.tables.tx_set2))
  tables.push(av1_cdf_row_of(state.y_coeff.eob))
  tables.push(av1_cdf_row_of(state.y_coeff.eob512))
  tables.push(av1_cdf_rows_copy(state.y_coeff.eob_small))
  tables.push(av1_cdf_rows_of3(state.y_coeff.extra))
  tables.push(av1_cdf_rows_of3(state.y_coeff.base_eob))
  tables.push(av1_cdf_rows_of3(state.y_coeff.base))
  tables.push(av1_cdf_rows_of3(state.y_coeff.br))
  tables.push(av1_cdf_rows_copy(state.y_coeff.sign))
  tables.push(av1_cdf_row_of(state.uv_coeff.eob))
  tables.push(av1_cdf_row_of(state.uv_coeff.eob512))
  tables.push(av1_cdf_rows_copy(state.uv_coeff.eob_small))
  tables.push(av1_cdf_rows_of3(state.uv_coeff.extra))
  tables.push(av1_cdf_rows_of3(state.uv_coeff.base_eob))
  tables.push(av1_cdf_rows_of3(state.uv_coeff.base))
  tables.push(av1_cdf_rows_of3(state.uv_coeff.br))
  tables.push(av1_cdf_rows_copy(state.uv_coeff.sign))
  tables.push(av1_cdf_rows_copy(state.vartx.rows))
  tables.push(av1_cdf_rows_copy(state.tx_depth.cdf8))
  tables.push(av1_cdf_rows_copy(state.tx_depth.cdf16))
  tables.push(av1_cdf_rows_copy(state.tx_depth.cdf32))
  tables.push(av1_cdf_rows_copy(state.tx_depth.cdf64))
  tables.push(av1_cdf_rows_copy(state.seg_id))
  tables.push(av1_cdf_row_of(state.delta_q))
  tables
}

///|
fn av1_intra_cdfs_restore(
  state : Av1IntraState,
  snapshot : Av1FrameCdfs,
) -> Unit {
  let tables = snapshot.tables
  if tables.length() < av1_cdf_inter_base {
    return
  }
  let mut index = av1_cdf_partition_count
  av1_cdf_rows_into2(state.skip_cdfs, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.y_mode, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.uv_cfl, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.uv_no_cfl, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.angle_delta, tables[index])
  index = index + 1
  av1_cdf_row_into1(state.cfl.sign, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.cfl.alpha, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.palette.y_mode, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.palette.uv_mode, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.palette.y_size, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.palette.uv_size, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.palette.y_index, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.palette.uv_index, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.filter_intra, tables[index])
  index = index + 1
  av1_cdf_row_into1(state.filter_intra_mode_cdf, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.tables.txb_skip, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.tables.tx_set1, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.tables.tx_set2, tables[index])
  index = index + 1
  av1_cdf_row_into1(state.y_coeff.eob, tables[index])
  index = index + 1
  av1_cdf_row_into1(state.y_coeff.eob512, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.y_coeff.eob_small, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.y_coeff.extra, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.y_coeff.base_eob, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.y_coeff.base, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.y_coeff.br, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.y_coeff.sign, tables[index])
  index = index + 1
  av1_cdf_row_into1(state.uv_coeff.eob, tables[index])
  index = index + 1
  av1_cdf_row_into1(state.uv_coeff.eob512, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.uv_coeff.eob_small, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.uv_coeff.extra, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.uv_coeff.base_eob, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.uv_coeff.base, tables[index])
  index = index + 1
  av1_cdf_rows_into3(state.uv_coeff.br, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.uv_coeff.sign, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.vartx.rows, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.tx_depth.cdf8, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.tx_depth.cdf16, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.tx_depth.cdf32, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.tx_depth.cdf64, tables[index])
  index = index + 1
  av1_cdf_rows_into2(state.seg_id, tables[index])
  index = index + 1
  av1_cdf_row_into1(state.delta_q, tables[index])
  let extra = av1_cdf_lr_base + av1_cdf_lr_count
  if tables.length() >= extra + 3 {
    av1_cdf_rows_into2(state.seg_pred_cdfs, tables[extra])
    av1_cdf_rows_into2(state.delta_lf_cdfs, tables[extra + 1])
    if state.intrabc is Some(copy_state) {
      av1_cdf_row_into1(copy_state.use_cdf, tables[extra + 2])
      ignore(av1_inter_cdfs_restore(copy_state.info.cdfs, snapshot))
    }
  }
}

///|
/// Seed a freshly built intra tile state from the inherited frame context.
fn av1_intra_cdfs_of(state : Av1IntraState, snapshot : Av1FrameCdfs?) -> Unit {
  match snapshot {
    Some(value) => av1_intra_cdfs_restore(state, value)
    None => ()
  }
}

///|
/// The inter mode rows, in the order the file header documents. An intra
/// frame contributes the defaults so an inter frame restoring the snapshot
/// still sees every table.
fn av1_inter_cdfs_capture(cdfs : Av1InterCdfs) -> Array[Array[Array[Int]]] {
  [
    av1_cdf_rows_of3(cdfs.single_ref),
    av1_cdf_rows_copy(cdfs.new_mv),
    av1_cdf_rows_copy(cdfs.zero_mv),
    av1_cdf_rows_copy(cdfs.ref_mv),
    av1_cdf_rows_copy(cdfs.drl_mode),
    av1_cdf_rows_copy(cdfs.is_inter),
    av1_cdf_rows_copy(cdfs.skip_mode),
    av1_cdf_rows_copy(cdfs.interp_filter),
    av1_cdf_rows_copy(cdfs.y_mode),
    av1_cdf_rows_copy(cdfs.inter_tx_type_set1),
    av1_cdf_row_of(cdfs.inter_tx_type_set2),
    av1_cdf_rows_copy(cdfs.inter_tx_type_set3),
    av1_cdf_rows_copy(cdfs.mv_joint),
    av1_cdf_rows_of3(cdfs.mv_sign),
    av1_cdf_rows_of3(cdfs.mv_class),
    av1_cdf_rows_of3(cdfs.mv_class0_bit),
    av1_cdf_rows_of4(cdfs.mv_class0_fr),
    av1_cdf_rows_of3(cdfs.mv_class0_hp),
    av1_cdf_rows_of4(cdfs.mv_bit),
    av1_cdf_rows_of3(cdfs.mv_fr),
    av1_cdf_rows_of3(cdfs.mv_hp),
    av1_cdf_rows_copy(cdfs.use_obmc),
    av1_cdf_rows_copy(cdfs.motion_mode),
    av1_cdf_rows_copy(cdfs.comp_mode),
    av1_cdf_rows_copy(cdfs.comp_ref_type),
    av1_cdf_rows_copy(cdfs.compound_mode),
    av1_cdf_rows_copy(cdfs.compound_idx),
    av1_cdf_rows_copy(cdfs.comp_group_idx),
    av1_cdf_rows_of3(cdfs.uni_comp_ref),
    av1_cdf_rows_of3(cdfs.comp_ref),
    av1_cdf_rows_of3(cdfs.comp_bwd_ref),
    av1_cdf_rows_copy(cdfs.compound_type),
    av1_cdf_rows_copy(cdfs.inter_intra),
    av1_cdf_rows_copy(cdfs.inter_intra_mode),
    av1_cdf_rows_copy(cdfs.wedge_inter_intra),
    av1_cdf_rows_copy(cdfs.wedge_index),
  ]
}

///|
fn av1_inter_cdfs_restore(
  cdfs : Av1InterCdfs,
  snapshot : Av1FrameCdfs,
) -> Av1InterCdfs {
  let tables = snapshot.tables
  if tables.length() < av1_cdf_lr_base {
    return cdfs
  }
  let mut index = av1_cdf_inter_base
  av1_cdf_rows_into3(cdfs.single_ref, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.new_mv, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.zero_mv, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.ref_mv, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.drl_mode, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.is_inter, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.skip_mode, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.interp_filter, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.y_mode, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.inter_tx_type_set1, tables[index])
  index = index + 1
  av1_cdf_row_into1(cdfs.inter_tx_type_set2, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.inter_tx_type_set3, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.mv_joint, tables[index])
  index = index + 1
  av1_cdf_rows_into3(cdfs.mv_sign, tables[index])
  index = index + 1
  av1_cdf_rows_into3(cdfs.mv_class, tables[index])
  index = index + 1
  av1_cdf_rows_into3(cdfs.mv_class0_bit, tables[index])
  index = index + 1
  av1_cdf_rows_into4(cdfs.mv_class0_fr, tables[index])
  index = index + 1
  av1_cdf_rows_into3(cdfs.mv_class0_hp, tables[index])
  index = index + 1
  av1_cdf_rows_into4(cdfs.mv_bit, tables[index])
  index = index + 1
  av1_cdf_rows_into3(cdfs.mv_fr, tables[index])
  index = index + 1
  av1_cdf_rows_into3(cdfs.mv_hp, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.use_obmc, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.motion_mode, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.comp_mode, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.comp_ref_type, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.compound_mode, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.compound_idx, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.comp_group_idx, tables[index])
  index = index + 1
  av1_cdf_rows_into3(cdfs.uni_comp_ref, tables[index])
  index = index + 1
  av1_cdf_rows_into3(cdfs.comp_ref, tables[index])
  index = index + 1
  av1_cdf_rows_into3(cdfs.comp_bwd_ref, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.compound_type, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.inter_intra, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.inter_intra_mode, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.wedge_inter_intra, tables[index])
  index = index + 1
  av1_cdf_rows_into2(cdfs.wedge_index, tables[index])
  cdfs
}

///|
/// A tile's inter rows: the snapshot's rows when one is inherited, the
/// normative defaults otherwise.
fn av1_inter_cdfs_of(snapshot : Av1FrameCdfs?) -> Av1InterCdfs {
  let cdfs = av1_inter_cdfs()
  match snapshot {
    Some(value) => av1_inter_cdfs_restore(cdfs, value)
    None => cdfs
  }
}

///|
/// The loop-restoration selector rows. A frame without restoration units still
/// inherits these probabilities; omitting the live LR state must not reset them.
fn av1_lr_cdfs_capture(
  state : Av1RestorationTileState?,
  inherited? : Av1FrameCdfs? = None,
) -> Array[Array[Array[Int]]] {
  match state {
    Some(value) =>
      [
        av1_cdf_row_of(value.cdf_switchable),
        av1_cdf_row_of(value.cdf_wiener),
        av1_cdf_row_of(value.cdf_sgr),
      ]
    None => {
      match inherited {
        Some(snapshot) =>
          if snapshot.tables.length() >= av1_cdf_lr_base + av1_cdf_lr_count {
            return Array::makei(av1_cdf_lr_count, fn(i) {
              av1_cdf_rows_copy(snapshot.tables[av1_cdf_lr_base + i])
            })
          }
        None => ()
      }
      av1_lr_cdfs_capture(Some(av1_restoration_tile_state_new(1)))
    }
  }
}

///|
fn av1_lr_cdfs_restore(
  state : Av1RestorationTileState,
  snapshot : Av1FrameCdfs,
) -> Unit {
  let tables = snapshot.tables
  if tables.length() < av1_cdf_lr_base + av1_cdf_lr_count {
    return
  }
  av1_cdf_row_into1(state.cdf_switchable, tables[av1_cdf_lr_base])
  av1_cdf_row_into1(state.cdf_wiener, tables[av1_cdf_lr_base + 1])
  av1_cdf_row_into1(state.cdf_sgr, tables[av1_cdf_lr_base + 2])
}

///|
/// Capture the whole frame context from one decoded tile's live state. The
/// tile is the `context_update_tile_id` one, tile 0 by default: its final rows
/// become the frame context the next frame may inherit.
fn av1_frame_cdfs_capture(
  state : Av1IntraState,
  partition : Av1PartitionCdfState,
  lr : Av1RestorationTileState?,
  inherited? : Av1FrameCdfs? = None,
) -> Av1FrameCdfs {
  let inter = match state.inter {
    Some(info) => info.cdfs
    None =>
      match state.intrabc {
        Some(copy_state) => copy_state.info.cdfs
        None => av1_inter_cdfs()
      }
  }
  let extra = av1_cdf_lr_base + av1_cdf_lr_count
  let use_intrabc = match state.intrabc {
    Some(copy_state) => av1_cdf_row_of(copy_state.use_cdf)
    None =>
      match inherited {
        Some(previous) =>
          if previous.tables.length() >= extra + 3 {
            av1_cdf_rows_copy(previous.tables[extra + 2])
          } else {
            [[30531, 32768, 0]]
          }
        None => [[30531, 32768, 0]]
      }
  }
  {
    tables: av1_cdf_tables_concat([
      av1_partition_cdfs_capture(partition),
      av1_intra_cdfs_capture(state),
      av1_inter_cdfs_capture(inter),
      av1_lr_cdfs_capture(lr, inherited~),
      [
        av1_cdf_rows_copy(state.seg_pred_cdfs),
        av1_cdf_rows_copy(state.delta_lf_cdfs),
        use_intrabc,
      ],
    ]),
  }
}

///|
/// The complete context of a frame with PRIMARY_REF_NONE. Coefficient defaults
/// use this frame's base quantizer even if frame-end adaptation is disabled.
fn av1_frame_cdfs_default(base_q_idx : Int) -> Av1FrameCdfs {
  av1_frame_cdfs_capture(
    av1_intra_state(8, 8, base_q_idx, 8, false, false, false, false),
    av1_partition_cdf_state(),
    None,
  )
}