/// Eight-tap subpel motion compensation (AV1 §7.11.3.4).
///
/// The kernel reads a stored reference plane with edge clamping rather than a
/// border extension: the specification's extension is itself a replication, and
/// the block-stage MV clamping keeps every access within reach, so clamping at
/// read time produces identical samples without widening any allocation.
///
/// Reference scaling is carried by the `xScale`/`yScale` terms and degenerates
/// exactly to a shift of `1 << 14` when the reference and the current frame
/// share dimensions.

///|
/// §7.11.3.4 fixed-point widths: reference scale, 1/16-pel block position and
/// the 1/1024-pel accumulator used for the two-pass walk.
let av1_ref_scale_shift : Int = 14

///|
let av1_subpel_bits : Int = 4

///|
let av1_scale_subpel_bits : Int = 10

///|
/// A walk position carries 1/16-pel in its low bits once the reference scale is
/// factored out.
let av1_subpel_phase_shift : Int = av1_scale_subpel_bits - av1_subpel_bits

///|
/// `EIGHTTAP_SMOOTH`, `EIGHTTAP`, `EIGHTTAP_SHARP`, `BILINEAR` and the two
/// four-tap substitutes that narrow blocks are forced onto.
let av1_interp_eighttap : Int = 0

///|
let av1_interp_eighttap_smooth : Int = 1

///|
let av1_interp_eighttap_sharp : Int = 2

///|
let av1_interp_fourtap : Int = 4

///|
let av1_interp_fourtap_smooth : Int = 5

///|
/// `SEL_SWITCHABLE`: the block codes its own filter, so the frame-level value
/// never reaches the kernel.
let av1_interp_switchable : Int = 4

///|
fn av1_mc_round2(value : Int, shift : Int) -> Int {
  (value + (1 << (shift - 1))) >> shift
}

///|
/// Sign-symmetric rounding: half-way values move away from zero.
fn av1_mc_round2_signed(value : Int, shift : Int) -> Int {
  if value >= 0 {
    av1_mc_round2(value, shift)
  } else {
    -av1_mc_round2(-value, shift)
  }
}

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

///|
/// Blocks of four samples or fewer substitute a shorter kernel, because the
/// eight-tap support would reach further than the block is wide (AV1 §7.11.3.4).
fn av1_adjust_filter(interp : Int, size : Int) -> Int {
  if size > 4 {
    interp
  } else if interp == av1_interp_eighttap || interp == av1_interp_eighttap_sharp {
    av1_interp_fourtap
  } else if interp == av1_interp_eighttap_smooth {
    av1_interp_fourtap_smooth
  } else {
    interp
  }
}

///|
/// Everything the kernel needs about one block's prediction. `x`/`y` are the
/// block origin in the target plane's own units, and the motion vector is in
/// eighth-pel of the *reference* frame.
pub(all) struct Av1MotionRequest {
  reference : Av1RefFrame
  /// Current frame's entropy-coded dimensions; the scale ratios are formed
  /// against these.
  frame_width : Int
  frame_height : Int
  plane : Int
  x : Int
  y : Int
  width : Int
  height : Int
  mv_row : Int
  mv_col : Int
  /// Per-direction filter selection for the block being predicted. For OBMC the
  /// caller must pass the *neighbour's* filters, not the current block's.
  filter_vertical : Int
  filter_horizontal : Int
  compound : Bool
}

///|
/// Intermediate rounding exponents. Single-reference prediction keeps more
/// precision on the second pass because the result is written straight out;
/// a compound blend gives two of those bits back to the averaging stage.
fn av1_inter_rounds(bit_depth : Int, compound : Bool) -> (Int, Int) {
  let mut round0 = 3
  let mut round1 = if compound { 7 } else { 11 }
  if bit_depth == 12 {
    round0 = round0 + 2
    if !compound {
      round1 = round1 - 2
    }
  }
  (round0, round1)
}

///|
/// Stride of one plane inside a stored reference frame.
fn av1_ref_plane_stride(reference : Av1RefFrame, plane : Int) -> Int {
  if plane == 0 {
    reference.coded_width
  } else {
    (reference.coded_width + reference.sub_x) >> reference.sub_x
  }
}

///|
/// Two-pass 8-tap interpolation of one reference list for one block, returned
/// row-major and unclipped: a compound blend or residual add decides where the
/// sample range is applied (AV1 §7.11.3.4).
fn av1_motion_compensate(request : Av1MotionRequest) -> Array[Int] {
  let reference = request.reference
  let (sub_x, sub_y) = if request.plane == 0 {
    (0, 0)
  } else {
    (reference.sub_x, reference.sub_y)
  }
  let (round0, round1) = av1_inter_rounds(reference.bit_depth, request.compound)
  let samples = reference.planes[request.plane]
  let stride = av1_ref_plane_stride(reference, request.plane)
  // Clamp against the visible reference extent, not the MI-padded allocation:
  // padding beyond the frame is undefined and must never be sampled.
  let last_x = ((reference.width + sub_x) >> sub_x) - 1
  let last_y = ((reference.height + sub_y) >> sub_y) - 1
  let half_sample = 1 << (av1_subpel_bits - 1)
  // A motion vector is eighth-pel; the walk is in 1/16-pel, hence the doubling.
  // Doubling first also makes the subsampling shift exact.
  let origin_x = (request.x << av1_subpel_bits) +
    ((2 * request.mv_col) >> sub_x) +
    half_sample
  let origin_y = (request.y << av1_subpel_bits) +
    ((2 * request.mv_row) >> sub_y) +
    half_sample
  let x_scale = (
      (reference.width << av1_ref_scale_shift) + request.frame_width / 2
    ) /
    request.frame_width
  let y_scale = (
      (reference.height << av1_ref_scale_shift) + request.frame_height / 2
    ) /
    request.frame_height
  let base_x = origin_x * x_scale - (half_sample << av1_ref_scale_shift)
  let base_y = origin_y * y_scale - (half_sample << av1_ref_scale_shift)
  let offset = (1 << (av1_scale_subpel_bits - av1_subpel_bits)) / 2
  let start_x = av1_mc_round2_signed(
      base_x,
      av1_ref_scale_shift + av1_subpel_bits - av1_scale_subpel_bits,
    ) +
    offset
  let start_y = av1_mc_round2_signed(
      base_y,
      av1_ref_scale_shift + av1_subpel_bits - av1_scale_subpel_bits,
    ) +
    offset
  let step_x = av1_mc_round2_signed(
    x_scale,
    av1_ref_scale_shift - av1_scale_subpel_bits,
  )
  let step_y = av1_mc_round2_signed(
    y_scale,
    av1_ref_scale_shift - av1_scale_subpel_bits,
  )
  let filter_h = av1_adjust_filter(request.filter_horizontal, request.width)
  let filter_v = av1_adjust_filter(request.filter_vertical, request.height)
  // Pass one filters horizontally over the rows the vertical pass will need,
  // which is why the intermediate buffer carries eight extra rows.
  let intermediate_height = (
      ((request.height - 1) * step_y + ((1 << av1_scale_subpel_bits) - 1)) >>
      av1_scale_subpel_bits
    ) +
    8
  let intermediate : Array[Int] = Array::make(
    intermediate_height * request.width,
    0,
  )
  for row in 0..> av1_scale_subpel_bits) + row - 3,
    )
    for column in 0..> av1_subpel_phase_shift) & 15
      let taps = av1_subpel_filters[filter_h][phase]
      let mut sum = 0
      for tap in 0..<8 {
        let source_x = av1_mc_clip(
          0,
          last_x,
          (position >> av1_scale_subpel_bits) + tap - 3,
        )
        sum = sum + taps[tap] * samples[source_y * stride + source_x]
      }
      intermediate[row * request.width + column] = av1_mc_round2(sum, round0)
    }
  }
  // Pass two filters vertically out of the intermediate buffer. Only the
  // fractional part of the start position survives.
  let prediction : Array[Int] = Array::make(request.width * request.height, 0)
  for row in 0..> av1_subpel_phase_shift) & 15
    let taps = av1_subpel_filters[filter_v][phase]
    let base = position >> av1_scale_subpel_bits
    for column in 0.. Unit {
  if plane_stride <= 0 || width <= 0 || height <= 0 {
    return
  }
  let plane_height = plane.length() / plane_stride
  let first_row = if origin_y < 0 { -origin_y } else { 0 }
  let first_column = if origin_x < 0 { -origin_x } else { 0 }
  let rows = if height < plane_height - origin_y {
    height
  } else {
    plane_height - origin_y
  }
  let columns = if width < plane_stride - origin_x {
    width
  } else {
    plane_stride - origin_x
  }
  for row in first_row..