/// The temporal motion-vector projection arithmetic (AV1 §7.9.3).
///
/// Every input is now sourced: the reciprocal table comes from the
/// specification (`av1spec08.md:872-877`, identical to go-av1's `divMult`), the
/// distance bound from go-av1's `decode/motionfield.go` and
/// `decode/compound.go:248`, and `Round2Signed` is the sign-symmetric rounding
/// this repository already uses and independently verifies for the subpel
/// kernel (`av1_mc_round2_signed`). See HANDOFF.md addenda 25 and 30 for how
/// each was obtained and why none of them is guessed.

///|
/// `MAX_FRAME_DISTANCE`. Bounds both the numerator and the denominator of a
/// projection, and indexes `av1_mv_div_mult`, which has 32 entries — so the
/// bound must be at most 31, and go-av1's value is exactly that.
pub let av1_mv_max_frame_distance : Int = 31

///|
/// `MAX_OFFSET_WIDTH` / `MAX_OFFSET_HEIGHT`, the half-width of the window
/// `get_block_position` accepts around a projected 8x8 location.
pub let av1_mv_max_offset_width : Int = 8

///|
pub let av1_mv_max_offset_height : Int = 0

///|
/// `MFMV_STACK_SIZE - 2`: how many reference frames beyond LAST the projection
/// is allowed to consume.
pub let av1_mv_stack_stamp : Int = 3 - 2

///|
/// The motion-field magnitude limit: a saved vector larger than this in either
/// axis is not recorded as a temporal candidate.
pub let av1_mv_refmvs_limit : Int = (1 << 12) - 1

///|
/// The invalid motion vector that fills an un-projected `MotionFieldMvs` cell.
pub let av1_mv_invalid : Int = -(1 << 15)

///|
/// `get_mv_projection( mv, numerator, denominator )` for one component: scale a
/// saved motion vector from the frame distance it was measured over to another.
///
/// A zero denominator maps to `Div_Mult[0]`, which is zero, so a division by
/// zero projects to a zero vector rather than failing.
pub fn av1_mv_projection_component(
  component : Int,
  numerator : Int,
  denominator : Int,
) -> Int {
  let clipped_denominator = if denominator < av1_mv_max_frame_distance {
    denominator
  } else {
    av1_mv_max_frame_distance
  }
  let low = -av1_mv_max_frame_distance
  let clipped_numerator = if numerator < low {
    low
  } else if numerator > av1_mv_max_frame_distance {
    av1_mv_max_frame_distance
  } else {
    numerator
  }
  let scaled = av1_mc_round2_signed(
    component * clipped_numerator * av1_mv_div_mult[clipped_denominator],
    14,
  )
  let bound = (1 << 14) - 1
  if scaled < -bound {
    -bound
  } else if scaled > bound {
    bound
  } else {
    scaled
  }
}

///|
/// `get_mv_projection` for both components at once.
pub fn av1_mv_projection(
  mv_row : Int,
  mv_col : Int,
  numerator : Int,
  denominator : Int,
) -> (Int, Int) {
  (
    av1_mv_projection_component(mv_row, numerator, denominator),
    av1_mv_projection_component(mv_col, numerator, denominator),
  )
}

///|
/// `get_block_position` (AV1 §7.9.4) for one axis: project an 8x8 grid
/// coordinate by a motion vector and report whether the result stays inside the
/// window the specification allows around the un-projected position.
///
/// `grid` is the coordinate in 8x8 units, `delta` the projected motion vector's
/// component in 1/8-pel, `dst_sign` the direction multiplier, `limit8` the
/// frame's extent in 8x8 units, and `max_offset8` how far from the original
/// eight-unit neighbourhood a projection may land.
fn av1_mv_block_position_axis(
  grid : Int,
  delta : Int,
  dst_sign : Int,
  limit8 : Int,
  max_offset8 : Int,
) -> (Int, Bool) {
  let base8 = grid >> 3 << 3
  let shifted = if delta >= 0 { delta >> 6 } else { -(-delta >> 6) }
  let moved = grid + dst_sign * shifted
  let valid = moved >= 0 &&
    moved < limit8 &&
    moved >= base8 - max_offset8 &&
    moved < base8 + 8 + max_offset8
  (moved, valid)
}

///|
/// `get_block_position` for both axes. The width limit uses
/// `av1_mv_max_offset_width` and the height limit `av1_mv_max_offset_height`,
/// which are deliberately asymmetric in the specification.
pub fn av1_mv_block_position(
  x8 : Int,
  y8 : Int,
  proj_mv_row : Int,
  proj_mv_col : Int,
  dst_sign : Int,
  mi_cols : Int,
  mi_rows : Int,
) -> (Int, Int, Bool) {
  let (pos_y8, ok_y) = av1_mv_block_position_axis(
    y8,
    proj_mv_row,
    dst_sign,
    mi_rows >> 1,
    av1_mv_max_offset_height,
  )
  let (pos_x8, ok_x) = av1_mv_block_position_axis(
    x8,
    proj_mv_col,
    dst_sign,
    mi_cols >> 1,
    av1_mv_max_offset_width,
  )
  (pos_x8, pos_y8, ok_y && ok_x)
}