/// AV1 loop-restoration unit syntax reader.
///
/// Unit parameters are read on the shared tile entropy decoder exactly
/// once per root superblock, before that SB's first partition symbol
/// (AV1 spec §5.11.57; libaom decodeframe.c:1305-1346). The three mode
/// CDF rows, the per-plane coefficient references and the unit records
/// are per-tile state that resets at tile boundaries
/// (libaom decodeframe.c:2784-2786). Wiener coefficients are coded with
/// signed reference-recentered subexponential symbols; SGR carries an
/// equiprobable four-bit parameter version plus two projections. The
/// chroma five-tap case infers tap 0 on both axes and reads no bits.
///
/// Entropy ownership uses the unshifted global unit grid from
/// `Av1RestorationLayout`; the superres corner mapping preserves the
/// rational denominator-8 form until the final ceiling division.

///|
/// One decoded restoration unit. Wiener keeps the three coded taps per
/// axis in read order (vertical first, then horizontal); SGR keeps the
/// parameter version `ep` and the two coded projections.
pub(all) enum Av1RestorationUnit {
  Av1RestorationUnitNone
  Av1RestorationUnitWiener(Array[Int], Array[Int])
  Av1RestorationUnitSgrproj(Int, Array[Int])
}

///|
/// One unit's decoded parameters with its global grid identity. Records
/// are collected per tile and assembled into a frame unit map that must
/// distinguish decoded-None units from unread positions.
pub(all) struct Av1RestorationUnitRecord {
  plane : Int
  unit_row : Int
  unit_col : Int
  unit : Av1RestorationUnit
}

///|
/// Per-tile loop-restoration entropy state. The three CDF rows are
/// shared across planes (AV1 spec §5.11.57); coefficient references are
/// separate per plane and filter family and survive superblock rows.
pub(all) struct Av1RestorationTileState {
  cdf_switchable : Array[Int]
  cdf_wiener : Array[Int]
  cdf_sgr : Array[Int]
  // [plane][axis][tap], axis 0 vertical and 1 horizontal.
  ref_wiener : Array[Array[Array[Int]]]
  // [plane][projection].
  ref_sgr : Array[Array[Int]]
  units : Array[Av1RestorationUnitRecord]
}

///|
/// Wiener tap alphabet and reference table (libaom restoration.h:94-112).
/// Ranges are inclusive; k is the subexponential width.
fn av1_wiener_tap_min(tap : Int) -> Int {
  if tap == 0 {
    -5
  } else if tap == 1 {
    -23
  } else {
    -17
  }
}

///|
fn av1_wiener_tap_max(tap : Int) -> Int {
  if tap == 0 {
    10
  } else if tap == 1 {
    8
  } else {
    46
  }
}

///|
fn av1_wiener_tap_k(tap : Int) -> Int {
  if tap == 0 {
    1
  } else if tap == 1 {
    2
  } else {
    3
  }
}

///|
/// Initial Wiener reference taps (libaom restoration.h:124-132).
fn av1_wiener_tap_mid(tap : Int) -> Int {
  if tap == 0 {
    3
  } else if tap == 1 {
    -7
  } else {
    15
  }
}

///|
/// SGR projection ranges (libaom restoration.h:124-132): xqd0 spans
/// [-96,32), xqd1 spans [-32,96), references -32 and 31.
fn av1_sgr_xqd_low(index : Int) -> Int {
  if index == 0 {
    -96
  } else {
    -32
  }
}

///|
fn av1_sgr_xqd_high(index : Int) -> Int {
  if index == 0 {
    32
  } else {
    96
  }
}

///|
fn av1_sgr_xqd_mid(index : Int) -> Int {
  if index == 0 {
    -32
  } else {
    31
  }
}

///|
fn av1_clip_int(low : Int, value : Int, high : Int) -> Int {
  if value < low {
    low
  } else if value > high {
    high
  } else {
    value
  }
}

///|
/// SGR parameter-version radii: ep 0..9 use (2,1), 10..13 use (0,1)
/// and 14..15 use (2,0) (libaom restoration.c:584-595).
fn av1_sgr_radii(ep : Int) -> (Int, Int) {
  if ep < 10 {
    (2, 1)
  } else if ep < 14 {
    (0, 1)
  } else {
    (2, 0)
  }
}

///|
/// Fresh per-tile state: default forward-cumulative CDF rows and
/// coefficient references (AV1 spec §5.11.57; libaom entropymode.c).
fn av1_restoration_tile_state_new(num_planes : Int) -> Av1RestorationTileState {
  let ref_wiener : Array[Array[Array[Int]]] = []
  let ref_sgr : Array[Array[Int]] = []
  for _ in 0.. Av1RestorationUnit {
  let vertical : Array[Int] = []
  let horizontal : Array[Int] = []
  for axis in 0..<2 {
    for tap in 0..<3 {
      let value = if plane != 0 && tap == 0 {
        0
      } else {
        decoder.entropy_signed_refsubexp(
          av1_wiener_tap_min(tap),
          av1_wiener_tap_max(tap) + 1,
          av1_wiener_tap_k(tap),
          tile_state.ref_wiener[plane][axis][tap],
        )
      }
      tile_state.ref_wiener[plane][axis][tap] = value
      if axis == 0 {
        vertical.push(value)
      } else {
        horizontal.push(value)
      }
    }
  }
  Av1RestorationUnitWiener(vertical, horizontal)
}

///|
/// Read one SGR unit: an equiprobable four-bit parameter version, then
/// the two projections. A zero radius on either side removes that
/// projection from the bitstream and fixes its coded value; the other
/// side is derived without entropy (libaom decodeframe.c:1664-1700).
fn av1_restoration_read_sgr(
  decoder : Av1SymbolDecoder,
  tile_state : Av1RestorationTileState,
  plane : Int,
) -> Av1RestorationUnit {
  let ep = decoder.entropy_literal(4)
  let (r0, r1) = av1_sgr_radii(ep)
  let xqd : Array[Int] = [0, 0]
  if r0 == 0 {
    xqd[1] = decoder.entropy_signed_refsubexp(
      av1_sgr_xqd_low(1),
      av1_sgr_xqd_high(1),
      4,
      tile_state.ref_sgr[plane][1],
    )
  } else if r1 == 0 {
    xqd[0] = decoder.entropy_signed_refsubexp(
      av1_sgr_xqd_low(0),
      av1_sgr_xqd_high(0),
      4,
      tile_state.ref_sgr[plane][0],
    )
    xqd[1] = av1_clip_int(
      av1_sgr_xqd_low(1),
      128 - xqd[0],
      av1_sgr_xqd_high(1) - 1,
    )
  } else {
    xqd[0] = decoder.entropy_signed_refsubexp(
      av1_sgr_xqd_low(0),
      av1_sgr_xqd_high(0),
      4,
      tile_state.ref_sgr[plane][0],
    )
    xqd[1] = decoder.entropy_signed_refsubexp(
      av1_sgr_xqd_low(1),
      av1_sgr_xqd_high(1),
      4,
      tile_state.ref_sgr[plane][1],
    )
  }
  tile_state.ref_sgr[plane][0] = xqd[0]
  tile_state.ref_sgr[plane][1] = xqd[1]
  Av1RestorationUnitSgrproj(ep, xqd)
}

///|
/// Read one unit's mode symbol from the frame-mode CDF row and its
/// filter coefficients. Mode None reads no coefficients and changes no
/// reference. Returns the decoded unit; the caller appends the record.
fn av1_restoration_read_unit(
  decoder : Av1SymbolDecoder,
  tile_state : Av1RestorationTileState,
  plane : Int,
  frame_mode : Av1RestorationType,
) -> Av1RestorationUnit {
  match frame_mode {
    Av1RestorationWiener => {
      let selected = decoder.symbol(tile_state.cdf_wiener)
      if selected == 0 {
        Av1RestorationUnitNone
      } else {
        av1_restoration_read_wiener(decoder, tile_state, plane)
      }
    }
    Av1RestorationSgrproj => {
      let selected = decoder.symbol(tile_state.cdf_sgr)
      if selected == 0 {
        Av1RestorationUnitNone
      } else {
        av1_restoration_read_sgr(decoder, tile_state, plane)
      }
    }
    Av1RestorationSwitchable =>
      match decoder.symbol(tile_state.cdf_switchable) {
        0 => Av1RestorationUnitNone
        1 => av1_restoration_read_wiener(decoder, tile_state, plane)
        _ => av1_restoration_read_sgr(decoder, tile_state, plane)
      }
    Av1RestorationNone => Av1RestorationUnitNone
  }
}

///|
/// Global unit-grid rectangle overlapped by one root superblock, per
/// plane. The root MI rectangle is `[mc0,mc1) x [mr0,mr1)` including its
/// full nominal superblock size at the frame edge; superres preserves
/// the rational `D/8` ratio as `num/den` until the final ceiling
/// division (libaom decodeframe.c:1702-1744). Only the end bounds are
/// clamped to the unit counts; an empty interval consumes nothing.
fn av1_restoration_sb_unit_range(
  layout : Av1RestorationLayout,
  plane : Int,
  mc0 : Int,
  mc1 : Int,
  mr0 : Int,
  mr1 : Int,
  subsampling_x : Bool,
  subsampling_y : Bool,
) -> (Int, Int, Int, Int) {
  let shift_x = if plane != 0 && subsampling_x { 1 } else { 0 }
  let shift_y = if plane != 0 && subsampling_y { 1 } else { 0 }
  let mx = 4 >> shift_x
  let my = 4 >> shift_y
  let unit = layout.config.unit_sizes[plane]
  let (num_x, den_x) = if layout.superres_denom != 8 {
    (mx * layout.superres_denom, unit * 8)
  } else {
    (mx, unit)
  }
  let c0 = (mc0 * num_x + den_x - 1) / den_x
  let c1 = {
    let raw = (mc1 * num_x + den_x - 1) / den_x
    if raw < layout.unit_cols[plane] {
      raw
    } else {
      layout.unit_cols[plane]
    }
  }
  let r0 = (mr0 * my + unit - 1) / unit
  let r1 = {
    let raw = (mr1 * my + unit - 1) / unit
    if raw < layout.unit_rows[plane] {
      raw
    } else {
      layout.unit_rows[plane]
    }
  }
  (c0, c1, r0, r1)
}

///|
/// Read the loop-restoration units whose nominal top-left corners fall
/// inside one root superblock, in plane then unit-row then unit-column
/// order. `mc0`/`mr0` are the SB's global coded MI coordinates. An SB
/// may contain zero, one or several unit origins per plane.
fn av1_restoration_read_sb(
  decoder : Av1SymbolDecoder,
  tile_state : Av1RestorationTileState,
  layout : Av1RestorationLayout,
  mc0 : Int,
  mr0 : Int,
  sb_size : Int,
  subsampling_x? : Bool = false,
  subsampling_y? : Bool = false,
) -> Unit {
  let mc1 = mc0 + sb_size / 4
  let mr1 = mr0 + sb_size / 4
  for plane in 0..