/// Exact AV1 inverse DCT integer transform (AOMedia AV1 §7.13.2.1–3).
///
/// The implementation follows the normative B/H butterfly schedule.  The
/// constants are Cos128_Lookup from the AV1 specification (Q12). Products
/// retain exact integer precision and use the specified Round2 rule.
/// This is deliberately a butterfly implementation, rather than a floating
/// point/cosine matrix approximation.  Reference schedule: AOMediaCodec/av1-
/// spec §7.13.2.3 and libaom `av1/common/av1_inv_txfm1d.c` at commit
/// 8e7b6a567df174d795479b92b4ac766d271add73 (AOMedia googlesource).

///|
pub(all) enum Av1TxType {
  Av1TxDctDct
  Av1TxAdstDct
  Av1TxDctAdst
  Av1TxAdstAdst
  Av1TxFlipadstDct
  Av1TxDctFlipadst
  Av1TxFlipadstFlipadst
  Av1TxAdstFlipadst
  Av1TxFlipadstAdst
  Av1TxIdentityIdentity
  Av1TxIdentityDct
  Av1TxDctIdentity
  Av1TxVerticalDct
  Av1TxHorizontalDct
  Av1TxVerticalAdst
  Av1TxHorizontalAdst
  Av1TxVerticalFlipadst
  Av1TxHorizontalFlipadst
}

///|
/// 2D assembly for IdentityIdentity.  Identity is a scaled 1D transform in
/// AV1; returning the coefficient plane unchanged is therefore incorrect.
fn av1_inverse_identity_identity_2d(
  input : Array[Int],
  width : Int,
  height : Int,
  bit_depth : Int,
) -> Array[Int]? {
  av1_inverse_identity_dct_2d(
    input,
    width,
    height,
    false,
    bit_depth~,
    row_identity=true,
    col_identity=true,
  )
}

///|
/// Spec inverse ADST4 (7.13.2.6) with the SINPI constants.
fn av1_inv_adst4_1d(in0 : Int, in1 : Int, in2 : Int, in3 : Int) -> Array[Int] {
  let s0 = 1321L * in0.to_int64()
  let s1 = 2482L * in0.to_int64()
  let s2 = 3344L * in1.to_int64()
  let s3 = 3803L * in2.to_int64()
  let s4 = 1321L * in2.to_int64()
  let s5 = 2482L * in3.to_int64()
  let s6 = 3803L * in3.to_int64()
  let a7 = in0.to_int64() - in2.to_int64()
  let b7 = a7 + in3.to_int64()
  let s0b = s0 + s3 + s5
  let s1b = s1 - s4 - s6
  let s3b = s2
  let s2b = 3344L * b7
  [
    av1_round2_wide(s0b + s3b, 12),
    av1_round2_wide(s1b + s3b, 12),
    av1_round2_wide(s2b, 12),
    av1_round2_wide(s0b + s1b - s3b, 12),
  ]
}

///|
/// Spec inverse ADST8/ADST16 (7.13.2.7-8): input permutation, B/H butterflies
/// with Round2(x, 12), output permutation with sign flips. Scale-preserving.
fn av1_inverse_adst_1d(
  input : Array[Int],
  n : Int,
  clamp_bits : Int,
) -> Array[Int]? {
  let count = 1 << n
  if (n != 3 && n != 4) || input.length() != count {
    return None
  }
  let t = input.copy()
  let copy = t.copy()
  for i in 0..> 3) & 1
    let b = ((i >> 2) & 1) ^ ((i >> 3) & 1)
    let c = ((i >> 1) & 1) ^ ((i >> 2) & 1)
    let d = (i & 1) ^ ((i >> 1) & 1)
    let idx = ((d << 3) | (c << 2) | (b << 1) | a) >> (4 - bits)
    out[i] = if i % 2 == 1 { -t[idx] } else { t[idx] }
  }
  Some(out)
}

///|
/// 2D inverse transform assembly for the ADST family (AV1 spec 7.13.3):
/// rows scaled by Round2(T, rowShift) and clamped, then columns scaled by
/// Round2(T, 4). The 1D passes are scale-preserving; this assembly provides
/// the size normalisation that the 1D functions intentionally omit.
fn av1_inverse_adst_2d(
  input : Array[Int],
  width : Int,
  height : Int,
  row_adst : Bool,
  col_adst : Bool,
  row_flip : Bool,
  col_flip : Bool,
  bit_depth? : Int = 8,
) -> Array[Int]? {
  av1_inverse_separable(
    input,
    width,
    height,
    bit_depth,
    if row_adst {
      1
    } else {
      0
    },
    if col_adst {
      1
    } else {
      0
    },
    row_flip,
    col_flip,
  )
}

///|
/// Spec inverse identity 1D scaling (7.13.2.11 through 7.13.2.14): the
/// identity transform is NOT a passthrough; it carries the size-dependent
/// sqrt2/2/2sqrt2/4 normalisation.
fn av1_inverse_identity_1d(input : Array[Int], n : Int) -> Array[Int]? {
  let count = 1 << n
  if input.length() != count {
    return None
  }
  let out : Array[Int] = []
  if n == 2 {
    for i in 0.. Array[Int]? {
  let row_kind = if row_identity || cols_dct { 2 } else { 0 }
  let col_kind = if col_identity || !cols_dct { 2 } else { 0 }
  av1_inverse_separable(
    input, width, height, bit_depth, row_kind, col_kind, false, false,
  )
}

///|
/// Dispatch a coded transform type. DCT_DCT and lossless WHT are implemented;
/// other AV1 transform types remain explicit unsupported boundaries.
pub fn av1_inverse_transform_2d(
  tx_type : Av1TxType,
  dequant : Array[Int],
  width : Int,
  height : Int,
  bit_depth : Int,
  lossless? : Bool = false,
) -> Array[Int]? {
  match tx_type {
    Av1TxDctDct =>
      av1_inverse_dct_2d(dequant, width, height, bit_depth, lossless~)
    // AV1 names the vertical (column) transform before the horizontal one.
    Av1TxAdstDct =>
      av1_inverse_adst_2d(
        dequant,
        width,
        height,
        false,
        true,
        false,
        false,
        bit_depth~,
      )
    Av1TxDctAdst =>
      av1_inverse_adst_2d(
        dequant,
        width,
        height,
        true,
        false,
        false,
        false,
        bit_depth~,
      )
    Av1TxAdstAdst =>
      av1_inverse_adst_2d(
        dequant,
        width,
        height,
        true,
        true,
        false,
        false,
        bit_depth~,
      )
    Av1TxFlipadstDct =>
      av1_inverse_adst_2d(
        dequant,
        width,
        height,
        false,
        true,
        false,
        true,
        bit_depth~,
      )
    Av1TxDctFlipadst =>
      av1_inverse_adst_2d(
        dequant,
        width,
        height,
        true,
        false,
        true,
        false,
        bit_depth~,
      )
    Av1TxFlipadstFlipadst =>
      av1_inverse_adst_2d(
        dequant,
        width,
        height,
        true,
        true,
        true,
        true,
        bit_depth~,
      )
    Av1TxAdstFlipadst =>
      av1_inverse_adst_2d(
        dequant,
        width,
        height,
        true,
        true,
        true,
        false,
        bit_depth~,
      )
    Av1TxFlipadstAdst =>
      av1_inverse_adst_2d(
        dequant,
        width,
        height,
        true,
        true,
        false,
        true,
        bit_depth~,
      )
    Av1TxIdentityIdentity =>
      av1_inverse_identity_identity_2d(dequant, width, height, bit_depth)
    // `cols_dct=true` means rows are identity and columns are DCT.
    Av1TxIdentityDct =>
      av1_inverse_identity_dct_2d(dequant, width, height, true, bit_depth~)
    Av1TxDctIdentity =>
      av1_inverse_identity_dct_2d(dequant, width, height, false, bit_depth~)
    Av1TxVerticalDct =>
      av1_inverse_identity_dct_2d(dequant, width, height, true, bit_depth~)
    Av1TxHorizontalDct =>
      av1_inverse_identity_dct_2d(dequant, width, height, false, bit_depth~)
    Av1TxVerticalAdst =>
      av1_inverse_separable(
        dequant, width, height, bit_depth, 2, 1, false, false,
      )
    Av1TxHorizontalAdst =>
      av1_inverse_separable(
        dequant, width, height, bit_depth, 1, 2, false, false,
      )
    Av1TxVerticalFlipadst =>
      av1_inverse_separable(
        dequant, width, height, bit_depth, 2, 1, false, true,
      )
    Av1TxHorizontalFlipadst =>
      av1_inverse_separable(
        dequant, width, height, bit_depth, 1, 2, true, false,
      )
  }
}

///|
test "transform dispatch preserves DCT_DCT and rejects unimplemented ADST" {
  let input = Array::make(16, 0)
  assert_true(av1_inverse_transform_2d(Av1TxDctDct, input, 4, 4, 8) is Some(_))
  assert_true(av1_inverse_transform_2d(Av1TxAdstDct, input, 4, 4, 8) is Some(_))
  assert_true(
    av1_inverse_transform_2d(Av1TxFlipadstDct, input, 4, 4, 8) is Some(_),
  )
  assert_true(
    av1_inverse_transform_2d(Av1TxAdstAdst, Array::make(64, 0), 8, 8, 8)
    is Some(_),
  )
  assert_true(
    av1_inverse_transform_2d(Av1TxIdentityIdentity, Array::make(32, 0), 4, 8, 8)
    is Some(_),
  )
  assert_true(
    av1_inverse_transform_2d(Av1TxDctAdst, Array::make(32, 0), 4, 8, 8)
    is Some(_),
  )
  assert_true(
    av1_inverse_transform_2d(Av1TxAdstDct, Array::make(32, 0), 8, 4, 8)
    is Some(_),
  )
  assert_true(
    av1_inverse_transform_2d(Av1TxVerticalDct, Array::make(32, 0), 4, 8, 8)
    is Some(_),
  )
  assert_true(
    av1_inverse_transform_2d(Av1TxHorizontalAdst, Array::make(32, 0), 8, 4, 8)
    is Some(_),
  )
}

///|
/// AV1's intra transform set is DCT-only for 32x32 and larger blocks
/// (spec get_tx_set, §7.13.1).  ADST is defined only up to 16 points;
/// keep the unsupported 32-point boundary explicit so callers do not treat
/// a non-normative extension as a valid AV1 decode path.
test "32 point ADST is rejected by the AV1 transform-size boundary" {
  let coeffs : Array[Int] = Array::make(32 * 32, 0)
  assert_true(av1_inverse_transform_2d(Av1TxAdstDct, coeffs, 32, 32, 8) is None)
  assert_true(av1_inverse_transform_2d(Av1TxDctAdst, coeffs, 32, 32, 8) is None)
  assert_true(
    av1_inverse_transform_2d(Av1TxAdstAdst, coeffs, 32, 32, 8) is None,
  )
  assert_true(
    av1_inverse_transform_2d(Av1TxFlipadstDct, coeffs, 32, 32, 8) is None,
  )
}

///|
/// Nonzero regression coverage for axis-specific FLIPADST dispatch.  A
/// flip must be applied inside the corresponding 1D transform axis; flipping
/// a completed 2D DCT image produces a different result for non-symmetric
/// coefficients.
test "flipadst dispatch applies the flip on the selected axis" {
  let input : Array[Int] = [
    768, -512, 256, 0, 1024, 1280, -256, 512, 0, 256, 512, -768, 1536, 0, -512, 256,
  ]
  for
    pair in [
      (Av1TxAdstDct, Av1TxFlipadstDct, true),
      (Av1TxDctAdst, Av1TxDctFlipadst, false),
      (Av1TxVerticalAdst, Av1TxVerticalFlipadst, true),
      (Av1TxHorizontalAdst, Av1TxHorizontalFlipadst, false),
      (Av1TxAdstAdst, Av1TxFlipadstAdst, true),
      (Av1TxAdstAdst, Av1TxAdstFlipadst, false),
    ] {
    let (base_type, flip_type, vertical) = pair
    let base = av1_inverse_transform_2d(base_type, input, 4, 4, 8).unwrap()
    let flipped = av1_inverse_transform_2d(flip_type, input, 4, 4, 8).unwrap()
    for y in 0..<4 {
      for x in 0..<4 {
        let index = if vertical { (3 - y) * 4 + x } else { y * 4 + 3 - x }
        assert_eq(flipped[y * 4 + x], base[index])
      }
    }
  }
}

///|
test "identity transform applies normative 1D scaling and validates shapes" {
  let scaled = av1_inverse_identity_1d([2, 0, 0, 0], 2).unwrap()
  assert_eq(scaled[0], 3)
  let input : Array[Int] = Array::make(16, 0)
  input[0] = 4096
  let out = av1_inverse_transform_2d(Av1TxIdentityIdentity, input, 4, 4, 8).unwrap()
  assert_true(out[0] != input[0])
  assert_true(
    av1_inverse_transform_2d(Av1TxIdentityDct, Array::make(8, 0), 4, 4, 8)
    is None,
  )
  assert_true(
    av1_inverse_transform_2d(Av1TxDctIdentity, Array::make(8, 0), 4, 4, 8)
    is None,
  )
  assert_true(
    av1_inverse_transform_2d(Av1TxIdentityDct, Array::make(1024, 0), 32, 32, 8)
    is Some(_),
  )
  assert_true(
    av1_inverse_transform_2d(Av1TxDctIdentity, Array::make(1024, 0), 32, 32, 8)
    is Some(_),
  )
  assert_true(
    av1_inverse_transform_2d(Av1TxIdentityIdentity, Array::make(16, 0), 4, 4, 7)
    is None,
  )
}

///|
test "identity mixed transform dispatch selects the correct axis" {
  let input : Array[Int] = Array::make(16, 0)
  input[1] = 100
  let expected_idct = av1_inverse_identity_dct_2d(
    input,
    4,
    4,
    true,
    bit_depth=8,
  ).unwrap()
  let expected_dctid = av1_inverse_identity_dct_2d(
    input,
    4,
    4,
    false,
    bit_depth=8,
  ).unwrap()
  assert_eq(
    av1_inverse_transform_2d(Av1TxIdentityDct, input, 4, 4, 8).unwrap(),
    expected_idct,
  )
  assert_eq(
    av1_inverse_transform_2d(Av1TxDctIdentity, input, 4, 4, 8).unwrap(),
    expected_dctid,
  )
}

///|
test "32 point ADST remains an explicit unsupported boundary" {
  assert_true(av1_inverse_adst_1d(Array::make(32, 0), 5, 16) is None)
}

///|
let av1_cos128_lookup : Array[Int] = [
  4096, 4095, 4091, 4085, 4076, 4065, 4052, 4036, 4017, 3996, 3973, 3948, 3920, 3889,
  3857, 3822, 3784, 3745, 3703, 3659, 3612, 3564, 3513, 3461, 3406, 3349, 3290, 3229,
  3166, 3102, 3035, 2967, 2896, 2824, 2751, 2675, 2598, 2520, 2440, 2359, 2276, 2191,
  2106, 2019, 1931, 1842, 1751, 1660, 1567, 1474, 1380, 1285, 1189, 1092, 995, 897,
  799, 700, 601, 501, 401, 301, 201, 101, 0,
]

///|
/// AV1 cos128(angle), with angles reduced modulo 256 (Q12).
fn av1_cos128(angle : Int) -> Int {
  let a = angle & 255
  if a <= 64 {
    av1_cos128_lookup[a]
  } else if a <= 128 {
    -av1_cos128_lookup[128 - a]
  } else if a <= 192 {
    -av1_cos128_lookup[a - 128]
  } else {
    av1_cos128_lookup[256 - a]
  }
}

///|
fn av1_sin128(angle : Int) -> Int {
  av1_cos128(angle - 64)
}

///|
/// AV1 Round2(x,n): add half an ulp and arithmetic shift right.
fn av1_round2(x : Int, n : Int) -> Int {
  if n == 0 {
    x
  } else if n > 0 && n < 32 {
    // x = q*2^n + r, with q an arithmetic shift and 0 <= r < 2^n.
    // The highest discarded bit is exactly the carry from adding half an
    // ulp. This also avoids overflow when x is close to Int::max_value.
    (x >> n) + ((x >> (n - 1)) & 1)
  } else {
    ((x.to_int64() + (1L << (n - 1))) >> n).to_int()
  }
}

///|
/// JS represents Int64 with BigInt. Number arithmetic is exact for the
/// bounded integer intermediates below; native and wasm keep their Int64 path.
#cfg(target="js")
let av1_transform_exact_double : Bool = true

///|
#cfg(not(target="js"))
let av1_transform_exact_double : Bool = false

///|
fn av1_round2_wide(x : Int64, n : Int) -> Int {
  if n == 0 {
    x.to_int()
  } else {
    ((x + (1L << (n - 1))) >> n).to_int()
  }
}

///|
fn av1_clip(lo : Int, hi : Int, x : Int) -> Int {
  if x < lo {
    lo
  } else if x > hi {
    hi
  } else {
    x
  }
}

///|
fn av1_brev(bits : Int, value : Int) -> Int {
  let mut out = 0
  for i in 0..> i) & 1) << (bits - 1 - i))
  }
  out
}

///|
fn av1_bfly(t : Array[Int], a : Int, b : Int, angle : Int, flip : Int) -> Unit {
  let ca = av1_cos128(angle)
  let sa = av1_sin128(angle)
  let ta = t[a]
  let tb = t[b]
  if av1_transform_exact_double &&
    ta >= -536870912 &&
    ta < 536870912 &&
    tb >= -536870912 &&
    tb < 536870912 {
    // |operand| <= 2^29 and |Q12 coefficient| <= 2^12: sums of products
    // plus 2048 stay below 2^43, exactly representable in Double (2^53).
    // The rounded result fits Int32. Wider public inputs retain Int64's
    // narrowing semantics below. floor, not truncation, matches signed >>.
    let x = ta.to_double() * ca.to_double() - tb.to_double() * sa.to_double()
    let y = ta.to_double() * sa.to_double() + tb.to_double() * ca.to_double()
    let xx = ((x + 2048.0) / 4096.0).floor().to_int()
    let yy = ((y + 2048.0) / 4096.0).floor().to_int()
    if flip == 1 {
      t[a] = yy
      t[b] = xx
    } else {
      t[a] = xx
      t[b] = yy
    }
    return
  }
  // libaom's half_btf widens products to int64 before Round2.  Keep the
  // public coefficient API as Int while matching that overflow-safe path.
  let x : Int64 = ta.to_int64() * ca.to_int64() - tb.to_int64() * sa.to_int64()
  let y : Int64 = ta.to_int64() * sa.to_int64() + tb.to_int64() * ca.to_int64()
  let xx = ((x + 2048L) >> 12).to_int()
  let yy = ((y + 2048L) >> 12).to_int()
  if flip == 1 {
    t[a] = yy
    t[b] = xx
  } else {
    t[a] = xx
    t[b] = yy
  }
}

///|
fn av1_had(t : Array[Int], a0 : Int, b0 : Int, flip : Int, bits : Int) -> Unit {
  let a = if flip == 1 { b0 } else { a0 }
  let b = if flip == 1 { a0 } else { b0 }
  if av1_transform_exact_double && bits >= 1 && bits <= 31 {
    // A sum/difference of Int32 operands is at most 2^32 in magnitude.
    // Clamp to the stage's Int32 range before converting back to Int.
    let lo = (-(1 << (bits - 1))).to_double()
    let hi = ((1 << (bits - 1)) - 1).to_double()
    let x = t[a].to_double()
    let y = t[b].to_double()
    let sum = x + y
    let diff = x - y
    t[a] = (if sum < lo { lo } else if sum > hi { hi } else { sum }).to_int()
    t[b] = (if diff < lo { lo } else if diff > hi { hi } else { diff }).to_int()
    return
  }
  let lo : Int64 = -(1L << (bits - 1))
  let hi : Int64 = (1L << (bits - 1)) - 1L
  let x = t[a].to_int64()
  let y = t[b].to_int64()
  let sum = if x + y < lo { lo } else if x + y > hi { hi } else { x + y }
  let diff = if x - y < lo { lo } else if x - y > hi { hi } else { x - y }
  t[a] = sum.to_int()
  t[b] = diff.to_int()
}

///|
/// In-place inverse DCT schedule for n=2..6 (length 4,8,16,32,64).
fn av1_inverse_dct_in_place(t : Array[Int], n : Int, clamp_bits : Int) -> Unit {
  let copy = t.copy()
  let count = 1 << n
  for i in 0..= 5 {
    for i in 0..<8 {
      av1_bfly(t, 16 + i, 31 - i, 6 + (av1_brev(3, 7 - i) << 3), 0)
    }
  }
  if n == 6 {
    for i in 0..<16 {
      av1_had(t, 32 + i * 2, 33 + i * 2, i & 1, clamp_bits)
    }
  }
  if n >= 4 {
    for i in 0..<4 {
      av1_bfly(t, 8 + i, 15 - i, 12 + (av1_brev(2, 3 - i) << 4), 0)
    }
  }
  if n >= 5 {
    for i in 0..<8 {
      av1_had(t, 16 + 2 * i, 17 + 2 * i, i & 1, clamp_bits)
    }
  }
  if n == 6 {
    for i in 0..<4 {
      for j in 0..<2 {
        av1_bfly(
          t,
          62 - i * 4 - j,
          33 + i * 4 + j,
          60 - 16 * av1_brev(2, i) + 64 * j,
          1,
        )
      }
    }
  }
  if n >= 3 {
    for i in 0..<2 {
      av1_bfly(t, 4 + i, 7 - i, 56 - 32 * i, 0)
    }
  }
  if n >= 4 {
    for i in 0..<4 {
      av1_had(t, 8 + 2 * i, 9 + 2 * i, i & 1, clamp_bits)
    }
  }
  if n >= 5 {
    for i in 0..<2 {
      for j in 0..<2 {
        av1_bfly(
          t,
          30 - 4 * i - j,
          17 + 4 * i + j,
          24 + (j << 6) + ((1 - i) << 5),
          1,
        )
      }
    }
  }
  if n == 6 {
    for i in 0..<8 {
      for j in 0..<2 {
        av1_had(t, 32 + i * 4 + j, 35 + i * 4 - j, i & 1, clamp_bits)
      }
    }
  }
  for i in 0..<2 {
    av1_bfly(t, 2 * i, 2 * i + 1, 32 + 16 * i, 1 - i)
  }
  if n >= 3 {
    for i in 0..<2 {
      av1_had(t, 4 + 2 * i, 5 + 2 * i, i, clamp_bits)
    }
  }
  if n >= 4 {
    for i in 0..<2 {
      av1_bfly(t, 14 - i, 9 + i, 48 + 64 * i, 1)
    }
  }
  if n >= 5 {
    for i in 0..<4 {
      for j in 0..<2 {
        av1_had(t, 16 + 4 * i + j, 19 + 4 * i - j, i & 1, clamp_bits)
      }
    }
  }
  if n == 6 {
    for i in 0..<2 {
      for j in 0..<4 {
        av1_bfly(
          t,
          61 - i * 8 - j,
          34 + i * 8 + j,
          56 - i * 32 + (j >> 1) * 64,
          1,
        )
      }
    }
  }
  for i in 0..<2 {
    av1_had(t, i, 3 - i, 0, clamp_bits)
  }
  if n >= 3 {
    av1_bfly(t, 6, 5, 32, 1)
  }
  if n >= 4 {
    for i in 0..<2 {
      for j in 0..<2 {
        av1_had(t, 8 + 4 * i + j, 11 + 4 * i - j, i, clamp_bits)
      }
    }
  }
  if n >= 5 {
    for i in 0..<4 {
      av1_bfly(t, 29 - i, 18 + i, 48 + (i >> 1) * 64, 1)
    }
  }
  if n == 6 {
    for i in 0..<4 {
      for j in 0..<4 {
        av1_had(t, 32 + 8 * i + j, 39 + 8 * i - j, i & 1, clamp_bits)
      }
    }
  }
  if n >= 3 {
    for i in 0..<4 {
      av1_had(t, i, 7 - i, 0, clamp_bits)
    }
  }
  if n >= 4 {
    for i in 0..<2 {
      av1_bfly(t, 13 - i, 10 + i, 32, 1)
    }
  }
  if n >= 5 {
    for i in 0..<2 {
      for j in 0..<4 {
        av1_had(t, 16 + i * 8 + j, 23 + i * 8 - j, i, clamp_bits)
      }
    }
  }
  if n == 6 {
    for i in 0..<8 {
      let angle = if i >= 4 { 112 } else { 48 }
      av1_bfly(t, 59 - i, 36 + i, angle, 1)
    }
  }
  if n >= 4 {
    for i in 0..<8 {
      av1_had(t, i, 15 - i, 0, clamp_bits)
    }
  }
  if n >= 5 {
    for i in 0..<4 {
      av1_bfly(t, 27 - i, 20 + i, 32, 1)
    }
  }
  if n == 6 {
    for i in 0..<8 {
      av1_had(t, 32 + i, 47 - i, 0, clamp_bits)
      av1_had(t, 48 + i, 63 - i, 1, clamp_bits)
    }
  }
  if n >= 5 {
    for i in 0..<16 {
      av1_had(t, i, 31 - i, 0, clamp_bits)
    }
  }
  if n == 6 {
    for i in 0..<8 {
      av1_bfly(t, 55 - i, 40 + i, 32, 1)
    }
    for i in 0..<32 {
      av1_had(t, i, 63 - i, 0, clamp_bits)
    }
  }
}

///|
/// Exact AV1 inverse DCT. `n` is log2(length), and `clamp_bits` is the
/// intermediate range `r` from §7.13.2.3 (normally BitDepth+8 or +6).
pub fn av1_inverse_dct(
  input : Array[Int],
  n : Int,
  clamp_bits : Int,
) -> Array[Int]? {
  if n < 2 ||
    n > 6 ||
    input.length() != 1 << n ||
    clamp_bits < 2 ||
    clamp_bits > 32 {
    return None
  }
  let out = input.copy()
  av1_inverse_dct_in_place(out, n, clamp_bits)
  Some(out)
}

///|
/// Exact DC-only specialization. It uses the same normative butterfly path,
/// making the result bit-identical to a block with all AC coefficients zero.
pub fn av1_inverse_dct_dc(dc : Int, n : Int, clamp_bits : Int) -> Array[Int]? {
  if n < 2 || n > 6 {
    return None
  }
  let input : Array[Int] = Array::make(1 << n, 0)
  input[0] = dc
  av1_inverse_dct(input, n, clamp_bits)
}

///|
fn av1_tx_row_shift(width : Int, height : Int) -> Int? {
  if width == height {
    if width == 4 {
      Some(0)
    } else if width == 8 {
      Some(1)
    } else if width == 16 {
      Some(2)
    } else if width == 32 || width == 64 {
      Some(2)
    } else {
      None
    }
  } else if (width == 4 && height == 8) || (width == 8 && height == 4) {
    Some(0)
  } else if (width == 8 && height == 16) ||
    (width == 16 && height == 8) ||
    (width == 16 && height == 32) ||
    (width == 32 && height == 16) ||
    (width == 32 && height == 64) ||
    (width == 64 && height == 32) {
    Some(1)
  } else if (width == 4 && height == 16) || (width == 16 && height == 4) {
    Some(1)
  } else if (width == 8 && height == 32) ||
    (width == 32 && height == 8) ||
    (width == 16 && height == 64) ||
    (width == 64 && height == 16) {
    Some(2)
  } else {
    None
  }
}

///|
/// 2D DCT_DCT inverse transform (§7.13.3), row-major coefficients/output.
/// Width and height are legal AV1 transform dimensions (4,8,16,32,64).
pub fn av1_inverse_dct_2d(
  dequant : Array[Int],
  width : Int,
  height : Int,
  bit_depth : Int,
  lossless? : Bool = false,
) -> Array[Int]? {
  let row_shift = match av1_tx_row_shift(width, height) {
    Some(v) => v
    None => return None
  }
  if bit_depth < 8 || bit_depth > 16 || dequant.length() != width * height {
    return None
  }
  let logw = if width == 4 {
    2
  } else if width == 8 {
    3
  } else if width == 16 {
    4
  } else if width == 32 {
    5
  } else {
    6
  }
  let logh = if height == 4 {
    2
  } else if height == 8 {
    3
  } else if height == 16 {
    4
  } else if height == 32 {
    5
  } else {
    6
  }
  let rs = row_shift
  let cs = 4
  let row_clamp = bit_depth + 8
  let col_clamp = if bit_depth + 6 < 16 { 16 } else { bit_depth + 6 }
  let out : Array[Int] = Array::make(width * height, 0)
  if lossless {
    // AV1 lossless coding uses the 4x4 inverse WHT (§7.13.2.10), with a
    // pre-shift of two bits on rows and no shift on columns.
    if width != 4 || height != 4 {
      return None
    }
    for i in 0..<4 {
      let a = dequant[i * 4] >> 2
      let c = dequant[i * 4 + 1] >> 2
      let d = dequant[i * 4 + 2] >> 2
      let b = dequant[i * 4 + 3] >> 2
      let aa = a + c
      let dd = d - b
      let e = (aa - dd) >> 1
      let bb = e - b
      let cc = e - c
      out[i * 4] = aa - bb
      out[i * 4 + 1] = bb
      out[i * 4 + 2] = cc
      out[i * 4 + 3] = dd + cc
    }
    for j in 0..<4 {
      let a = out[j]
      let c = out[4 + j]
      let d = out[8 + j]
      let b = out[12 + j]
      let aa = a + c
      let dd = d - b
      let e = (aa - dd) >> 1
      let bb = e - b
      let cc = e - c
      out[j] = aa - bb
      out[4 + j] = bb
      out[8 + j] = cc
      out[12 + j] = dd + cc
    }
    return Some(out)
  }
  for i in 0..> 12).to_int()
      }
    }
    let transformed = match av1_inverse_dct(row, logw, row_clamp) {
      Some(v) => v
      None => return None
    }
    for j in 0.. v
      None => return None
    }
    for i in 0..