///|
/// Generates a custom fiducial marker dictionary using a greedy packing search algorithm.
/// Guarantees that the minimum Hamming distance across all 4 rotations between any two markers is >= min_distance.
pub fn generate_custom_dictionary(
  name : String,
  grid_size : Int,
  num_markers : Int,
  min_hamming_distance : Int,
  seed? : Int = 42,
) -> MarkerDictionary {
  let payload_bits = grid_size * grid_size
  let max_val = 1L << payload_bits
  let codewords : Array[Int64] = []

  let mut rng_state = seed.to_int64()
  let next_rand = fn() -> Int64 {
    rng_state = rng_state * 6364136223846793005L + 1442695040888963407L
    if rng_state < 0L {
      -rng_state
    } else {
      rng_state
    }
  }

  let is_valid_codeword = fn(candidate : Int64) -> Bool {
    // Avoid completely all-0 or all-1 patterns
    if candidate == 0L || candidate == max_val - 1L {
      return false
    }

    // Check self-rotational symmetry: distance between candidate and its rotations must be >= min_distance
    let mut rot = candidate
    for _r in 1..<4 {
      rot = rotate_bits_90(rot, grid_size)
      if compute_hamming_distance(candidate, rot) < min_hamming_distance {
        return false
      }
    }

    // Check cross-codeword distance against all existing codewords in all 4 rotations
    for c in codewords {
      let mut c_rot = c
      for _r in 0..<4 {
        if compute_hamming_distance(candidate, c_rot) < min_hamming_distance {
          return false
        }
        c_rot = rotate_bits_90(c_rot, grid_size)
      }
    }

    true
  }

  let max_trials = 100000
  let mut trials = 0

  while codewords.length() < num_markers && trials < max_trials {
    trials += 1
    let rand_bits = next_rand() % max_val
    if is_valid_codeword(rand_bits) {
      codewords.push(rand_bits)
    }
  }

  let max_correction = (min_hamming_distance - 1) / 2
  {
    name,
    grid_size,
    payload_bits,
    max_correction_bits: max_correction,
    codewords,
  }
}