///|
fn gen_valid_constraint_family(profile : GenValidProfile) -> Int32 {
  match profile {
    ConstraintIntegerProfile => 0
    ConstraintJoinsProfile => 1
    ConstraintLoopsProfile => 2
    ConstraintTeesProfile => 3
    ConstraintWraparoundProfile => 4
    ConstraintI64Profile => 5
    ConstraintLogicalProfile => 6
    ConstraintReferencesProfile => 7
    ConstraintUnreachableProfile => 8
    ConstraintRelationsProfile => 9
    ConstraintEffectsProfile => 10
    ConstraintFloatBoundaryProfile => 11
    _ => 0
  }
}

///|
fn gen_valid_constraint_profile_config(
  profile : GenValidProfile,
) -> GenValidConfig {
  let base = GenValidConfig::binaryen_oracle_coverage_forced_default()
  GenValidConfig::{
    ..base,
    max_type_defs: 4,
    max_imports: 0,
    max_defined_funcs: if profile == ConstraintEffectsProfile {
      3
    } else {
      2
    },
    max_tables: 0,
    max_mems: 0,
    max_globals: 1,
    max_tags: if profile == ConstraintEffectsProfile {
      1
    } else {
      0
    },
    max_elems: 0,
    max_datas: 0,
    max_params: 2,
    max_results: 1,
    max_locals: 4,
    max_body_statements: 440 + gen_valid_constraint_family(profile),
    max_body_depth: 12,
  }
}

///|
fn gen_valid_is_constraint_profile_config(config : GenValidConfig) -> Bool {
  [
    ConstraintIntegerProfile,
    ConstraintJoinsProfile,
    ConstraintLoopsProfile,
    ConstraintTeesProfile,
    ConstraintWraparoundProfile,
    ConstraintI64Profile,
    ConstraintLogicalProfile,
    ConstraintReferencesProfile,
    ConstraintUnreachableProfile,
    ConstraintRelationsProfile,
    ConstraintEffectsProfile,
    ConstraintFloatBoundaryProfile,
  ].any(fn(profile) { config == gen_valid_constraint_profile_config(profile) })
}

///|
fn gen_valid_constraint_profile_module(ctx : GenValidContext) -> Module {
  let family = ctx.config.max_body_statements - 440
  let seed = if ctx.use_named_streams {
    ctx.root_seed
  } else {
    ctx.rnd.next_uint64()
  }
  if family == 2 && (seed & 6UL) == 4UL {
    return gen_valid_constraint_parameter_loop_module(seed)
  }
  if family == 2 && (seed & 2UL) != 0UL {
    return gen_valid_constraint_tuple_loop_module(seed)
  }
  if family == 10 {
    if seed % 8UL == 5UL {
      return gen_valid_constraint_throwing_stack_module(seed)
    }
    if seed % 4UL == 3UL {
      return gen_valid_constraint_copied_control_module(seed)
    }
    if seed % 3UL == 1UL {
      return gen_valid_constraint_handler_module(seed)
    }
    if seed % 3UL == 2UL {
      return gen_valid_constraint_pending_call_module(seed)
    }
  }
  let wide = family == 5 ||
    ((seed & 8UL) != 0UL && family != 6 && family != 7 && family != 11)
  let bits = if wide { 64 } else { 32 }
  let ty = if wide { ValType::i64() } else { ValType::i32() }
  let bool_ty = ValType::i32()
  let limits = [
    0UL, 1UL, 0x7fffffffUL, 0x80000000UL, 0xffffffffUL, 0x100000000UL, 0x7fffffffffffffffUL,
    0x8000000000000000UL, 0xffffffffffffffffUL,
  ]
  let value = limits[(seed % limits.length().to_uint64()).to_int()]
  let constant = fn(value : UInt64) {
    if wide {
      Instruction::i64_const(I64(value.reinterpret_as_int64()))
    } else {
      Instruction::i32_const(I32(value.to_uint().reinterpret_as_int()))
    }
  }
  let get = fn(index : UInt32) { Instruction::local_get(LocalIdx::new(index)) }
  let eq = if wide { Instruction::i64_eq() } else { Instruction::i32_eq() }
  let ne = if wide { Instruction::i64_ne() } else { Instruction::i32_ne() }
  let lt_s = if wide {
    Instruction::i64_lt_s()
  } else {
    Instruction::i32_lt_s()
  }
  let lt_u = if wide {
    Instruction::i64_lt_u()
  } else {
    Instruction::i32_lt_u()
  }
  let gt_s = if wide {
    Instruction::i64_gt_s()
  } else {
    Instruction::i32_gt_s()
  }
  let ge_u = if wide {
    Instruction::i64_ge_u()
  } else {
    Instruction::i32_ge_u()
  }
  let add = if wide { Instruction::i64_add() } else { Instruction::i32_add() }
  let eqz = if wide { Instruction::i64_eqz() } else { Instruction::i32_eqz() }
  let result_if = fn(yes : Array[Instruction], no : Array[Instruction]) {
    Instruction::if_(BlockType::val_type(bool_ty), yes, Some(no))
  }
  let zero = Instruction::i32_const(I32(0))
  let one = Instruction::i32_const(I32(1))
  let mut params = [ty, ty]
  let mut locals : Array[ValType] = [ty]
  let mut module_ = Module::new()
  let body : Array[Instruction] = match family {
    1 =>
      [
        get(0U),
        eqz,
        Instruction::if_(
          BlockType::void_(),
          [constant(3UL), Instruction::local_set(LocalIdx::new(2U))],
          Some([constant(5UL), Instruction::local_set(LocalIdx::new(2U))]),
        ),
        get(2U),
        constant(0UL),
        ne,
      ]
    2 => {
      // Bounded loops retain a live guard and expose a fact on their exit.
      let bound = 1UL + seed % 31UL
      [
        Instruction::block(
          BlockType::void_(),
          Expr::new([
            Instruction::loop_(
              BlockType::void_(),
              Expr::new([
                get(2U),
                constant(bound),
                ge_u,
                Instruction::br_if(LabelIdx::new(1U)),
                get(2U),
                constant(1UL),
                add,
                Instruction::local_set(LocalIdx::new(2U)),
                Instruction::br(LabelIdx::new(0U)),
              ]),
            ),
          ]),
        ),
        get(2U),
        constant(bound),
        ge_u,
      ]
    }
    3 =>
      [
        get(0U),
        constant(value),
        Instruction::local_tee(LocalIdx::new(0U)),
        eq,
        result_if([get(0U), constant(value), eq], [get(0U), constant(value), eq]),
      ]
    4 => {
      let maximum = if bits == 64 { 0xffffffffffffffffUL } else { 0xffffffffUL }
      [
        get(0U),
        constant(maximum),
        eq,
        result_if(
          [
            get(0U),
            constant(1UL),
            add,
            Instruction::local_set(LocalIdx::new(0U)),
            get(0U),
            eqz,
          ],
          [zero],
        ),
      ]
    }
    5 =>
      [
        get(0U),
        constant(0x100000000UL + seed % 1024UL),
        eq,
        result_if([get(0U), constant(0xffffffffUL), Instruction::i64_gt_u()], [
          zero,
        ]),
      ]
    6 =>
      [
        get(0U),
        constant(10UL),
        ge_u,
        get(0U),
        constant(20UL),
        Instruction::i32_le_u(),
        if (seed & 1UL) == 0UL {
          Instruction::i32_and()
        } else {
          Instruction::i32_or()
        },
        Instruction::i32_eqz(),
        Instruction::i32_eqz(),
        result_if([get(0U), constant(0UL), ne], [get(0U), constant(30UL), lt_u]),
      ]
    7 => {
      params = [ValType::ref_type(RefType::abs(AbsHeapType::extern_()))]
      locals = []
      [
        get(0U),
        Instruction::ref_is_null(),
        result_if([get(0U), Instruction::ref_is_null()], [
          get(0U),
          Instruction::ref_is_null(),
        ]),
      ]
    }
    8 =>
      [
        get(0U),
        eqz,
        Instruction::if_(
          BlockType::void_(),
          [one, Instruction::return_()],
          Some([constant(value), Instruction::local_set(LocalIdx::new(2U))]),
        ),
        get(2U),
        constant(value),
        eq,
      ]
    9 =>
      [
        get(0U),
        get(1U),
        lt_s,
        result_if([get(1U), get(0U), gt_s], [get(0U), get(1U), lt_u]),
      ]
    10 => {
      module_ = module_.with_global_sec(
        GlobalSec::new([
          Global::new(GlobalType::new(bool_ty, true), Expr::new([zero])),
        ]),
      )
      [
        constant(value),
        Instruction::local_tee(LocalIdx::new(0U)),
        constant(value),
        eq,
        result_if(
          [
            one,
            Instruction::global_set(GlobalIdx::new(0U)),
            get(0U),
            constant(value),
            eq,
          ],
          [zero],
        ),
      ]
    }
    11 => {
      params = [ValType::f64()]
      locals = []
      [
        get(0U),
        Instruction::f64_const(F64(1.0)),
        Instruction::f64_ne(),
        result_if([get(0U), get(0U), Instruction::f64_eq()], [one]),
      ]
    }
    _ =>
      // Half the cases expose a provable equality; half retain the signed /
      // unsigned distinction at negative and sign-boundary bit patterns.
      [
        get(0U),
        constant(value),
        if (seed & 1UL) == 0UL {
          eq
        } else {
          lt_s
        },
        result_if(
          [
            get(0U),
            constant(value),
            if (seed & 1UL) == 0UL {
              ne
            } else {
              lt_u
            },
          ],
          [one],
        ),
      ]
  }
  module_
  .with_type_sec(
    TypeSec::new([
      RecType::new(SubType::comp_type(CompType::func(params, [bool_ty]))),
    ]),
  )
  .with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
  .with_code_sec(
    CodeSec::new([Func::new(Locals::from_types(locals), Expr::new(body))]),
  )
  .with_export_sec(
    ExportSec::new([
      Export::new(Name::from_string("run"), ExternIdx::func(FuncIdx::new(0U))),
    ]),
  )
}

///|
fn gen_valid_constraint_handler_module(seed : UInt64) -> Module {
  let i32 = ValType::i32()
  let before = (seed.to_uint() ^ 0x12345678U).reinterpret_as_int()
  let initial = Instruction::i32_const(I32(before))
  let after = Instruction::i32_const(I32(before ^ 1))
  let tag = TagIdx::new(0U)
  let thrower = [
    Instruction::local_get(LocalIdx::new(0U)),
    Instruction::if_(BlockType::void_(), [Instruction::throw_(tag)], None),
  ]
  let body = [
    initial,
    Instruction::local_set(LocalIdx::new(1U)),
    Instruction::block(
      BlockType::void_(),
      Expr::new([
        Instruction::try_table(
          BlockType::val_type(i32),
          [Catch::all(LabelIdx::new(0U))],
          Expr::new([
            Instruction::local_get(LocalIdx::new(0U)),
            Instruction::call(FuncIdx::new(0U)),
            Instruction::i32_const(I32(9)),
          ]),
        ),
        after,
        Instruction::local_tee(LocalIdx::new(1U)),
        Instruction::i32_add(),
        Instruction::drop(),
      ]),
    ),
    Instruction::local_get(LocalIdx::new(1U)),
    initial,
    Instruction::i32_eq(),
  ]
  Module::new()
  .with_type_sec(
    TypeSec::new([
      RecType::new(SubType::comp_type(CompType::func([i32], []))),
      RecType::new(SubType::comp_type(CompType::func([i32], [i32]))),
      RecType::new(SubType::comp_type(CompType::func([], []))),
    ]),
  )
  .with_func_sec(FuncSec::new([TypeIdx::new(0U), TypeIdx::new(1U)]))
  .with_tag_sec(TagSec::new([TagType::new(TypeIdx::new(2U))]))
  .with_code_sec(
    CodeSec::new([
      Func::new(Locals::from_types([]), Expr::new(thrower)),
      Func::new(Locals::from_types([i32]), Expr::new(body)),
    ]),
  )
  .with_export_sec(
    ExportSec::new([
      Export::new(Name::from_string("run"), ExternIdx::func(FuncIdx::new(1U))),
    ]),
  )
}

///|
fn gen_valid_constraint_pending_call_module(seed : UInt64) -> Module {
  let i32 = ValType::i32()
  let initial = (seed.to_uint() ^ 0x5342U).reinterpret_as_int()
  let number = fn(value : Int32) { Instruction::i32_const(I32(value)) }
  let call = Instruction::call(FuncIdx::new(1U))
  Module::new()
  .with_type_sec(
    TypeSec::new([
      RecType::new(SubType::comp_type(CompType::func([], [i32]))),
      RecType::new(SubType::comp_type(CompType::func([], []))),
    ]),
  )
  .with_func_sec(
    FuncSec::new([TypeIdx::new(0U), TypeIdx::new(1U), TypeIdx::new(0U)]),
  )
  .with_global_sec(
    GlobalSec::new([
      Global::new(GlobalType::new(i32, true), Expr::new([number(initial)])),
    ]),
  )
  .with_code_sec(
    CodeSec::new([
      Func::new(
        Locals::from_types([]),
        Expr::new([Instruction::global_get(GlobalIdx::new(0U))]),
      ),
      Func::new(
        Locals::from_types([]),
        Expr::new([
          number(initial ^ 1),
          Instruction::global_set(GlobalIdx::new(0U)),
        ]),
      ),
      Func::new(
        Locals::from_types([i32, i32]),
        Expr::new([
          number(0),
          Instruction::local_set(LocalIdx::new(0U)),
          Instruction::call(FuncIdx::new(0U)),
          Instruction::local_get(LocalIdx::new(0U)),
          Instruction::if_(BlockType::void_(), [call], None),
          number(1),
          Instruction::if_(BlockType::void_(), [call], None),
          Instruction::local_set(LocalIdx::new(1U)),
          Instruction::local_get(LocalIdx::new(1U)),
        ]),
      ),
    ]),
  )
  .with_export_sec(
    ExportSec::new([
      Export::new(Name::from_string("run"), ExternIdx::func(FuncIdx::new(2U))),
      Export::new(
        Name::from_string("state"),
        ExternIdx::global(GlobalIdx::new(0U)),
      ),
    ]),
  )
}

///|
fn gen_valid_constraint_tuple_loop_module(seed : UInt64) -> Module {
  let i32 = ValType::i32()
  let f64 = ValType::f64()
  let tuple = BlockType::type_idx(TypeIdx::new(0U))
  let get = fn(index : UInt32) { Instruction::local_get(LocalIdx::new(index)) }
  let c = fn(value : Int32) { Instruction::i32_const(I32(value)) }
  let bound = 1 + (seed % 7UL).to_int()
  let body = [
    Instruction::loop_(
      tuple,
      Expr::new([
        get(0U),
        Instruction::local_tee(LocalIdx::new(2U)),
        c(seed.to_int()),
        Instruction::i32_eq(),
        Instruction::if_(
          tuple,
          [get(2U), Instruction::f64_const(F64(1.25)), Instruction::return_()],
          Some([
            get(1U),
            c(bound),
            Instruction::i32_lt_u(),
            Instruction::if_(
              BlockType::val_type(i32),
              [
                get(1U),
                c(1),
                Instruction::i32_add(),
                Instruction::local_tee(LocalIdx::new(1U)),
                Instruction::drop(),
                c(1),
              ],
              Some([c(0)]),
            ),
            Instruction::if_(
              tuple,
              [Instruction::br(LabelIdx::new(2U))],
              Some([
                get(2U),
                Instruction::f64_const(F64(2.5)),
                Instruction::return_(),
              ]),
            ),
          ]),
        ),
      ]),
    ),
  ]
  Module::new()
  .with_type_sec(
    TypeSec::new([
      RecType::new(SubType::comp_type(CompType::func([], [i32, f64]))),
      RecType::new(SubType::comp_type(CompType::func([i32], [i32, f64]))),
    ]),
  )
  .with_func_sec(FuncSec::new([TypeIdx::new(1U)]))
  .with_code_sec(
    CodeSec::new([Func::new(Locals::from_types([i32, i32]), Expr::new(body))]),
  )
  .with_export_sec(
    ExportSec::new([
      Export::new(Name::from_string("run"), ExternIdx::func(FuncIdx::new(0U))),
    ]),
  )
}

///|
fn gen_valid_constraint_parameter_loop_module(seed : UInt64) -> Module {
  let i32 = ValType::i32()
  let tuple = BlockType::type_idx(TypeIdx::new(0U))
  let get = fn(index : UInt32) { Instruction::local_get(LocalIdx::new(index)) }
  let c = fn(value : Int32) { Instruction::i32_const(I32(value)) }
  let variant = ((seed >> 3) % 4UL).to_int()
  let mut loop_body = [
    Instruction::local_tee(LocalIdx::new(1U)),
    get(0U),
    Instruction::i32_lt_u(),
    Instruction::if_(
      BlockType::void_(),
      [
        get(1U),
        c(1),
        Instruction::i32_add(),
        Instruction::br(LabelIdx::new(1U)),
      ],
      None,
    ),
  ]
  if variant == 1 {
    loop_body = [
      Instruction::local_set(LocalIdx::new(1U)),
      get(1U),
      c(1),
      Instruction::i32_add(),
      get(1U),
      get(0U),
      Instruction::i32_lt_u(),
      Instruction::br_if(LabelIdx::new(0U)),
      Instruction::drop(),
    ]
  }
  if variant == 3 {
    // Both loops deliberately share the initial producer. Only the inner
    // loop carries a changing value; its facts must shadow the outer binding.
    loop_body = [Instruction::loop_(tuple, Expr::new(loop_body))]
  }
  let body = [
    get(0U),
    c(1 + (seed % 31UL).to_int()),
    Instruction::i32_and(),
    Instruction::local_set(LocalIdx::new(0U)),
  ]
  if variant == 2 {
    body.push(
      Instruction::block(
        BlockType::val_type(i32),
        Expr::new([
          c(0),
          Instruction::loop_(
            BlockType::type_idx(TypeIdx::new(1U)),
            Expr::new([
              Instruction::local_set(LocalIdx::new(1U)),
              get(1U),
              c(1),
              Instruction::i32_add(),
              get(1U),
              get(0U),
              Instruction::i32_ge_u(),
              Instruction::br_table([LabelIdx::new(0U)], LabelIdx::new(1U)),
            ]),
          ),
        ]),
      ),
    )
    body.push(Instruction::local_set(LocalIdx::new(1U)))
  } else {
    body.push(c(0))
    body.push(Instruction::loop_(tuple, Expr::new(loop_body)))
  }
  body.push(get(1U))
  Module::new()
  .with_type_sec(
    TypeSec::new([
      RecType::new(SubType::comp_type(CompType::func([i32], []))),
      RecType::new(SubType::comp_type(CompType::func([i32], [i32]))),
    ]),
  )
  .with_func_sec(FuncSec::new([TypeIdx::new(1U)]))
  .with_code_sec(
    CodeSec::new([Func::new(Locals::from_types([i32]), Expr::new(body))]),
  )
  .with_export_sec(
    ExportSec::new([
      Export::new(Name::from_string("run"), ExternIdx::func(FuncIdx::new(0U))),
    ]),
  )
}

///|
fn gen_valid_constraint_copied_control_module(seed : UInt64) -> Module {
  let i32 = ValType::i32()
  let c = fn(value : Int32) { Instruction::i32_const(I32(value)) }
  let get = fn(index : UInt32) { Instruction::local_get(LocalIdx::new(index)) }
  Module::new()
  .with_type_sec(
    TypeSec::new([
      RecType::new(SubType::comp_type(CompType::func([i32, i32], [i32]))),
    ]),
  )
  .with_func_sec(FuncSec::new([TypeIdx::new(0U)]))
  .with_global_sec(
    GlobalSec::new([Global::new(GlobalType::new(i32, true), Expr::new([c(0)]))]),
  )
  .with_code_sec(
    CodeSec::new([
      Func::new(
        Locals::from_types([i32]),
        Expr::new([
          Instruction::block(
            BlockType::val_type(i32),
            Expr::new([
              get(0U),
              Instruction::if_(
                BlockType::void_(),
                [
                  Instruction::global_get(GlobalIdx::new(0U)),
                  c(1 + (seed % 255UL).to_int()),
                  Instruction::i32_add(),
                  Instruction::global_set(GlobalIdx::new(0U)),
                ],
                None,
              ),
              get(1U),
            ]),
          ),
          Instruction::local_set(LocalIdx::new(2U)),
          get(2U),
          Instruction::i32_eqz(),
        ]),
      ),
    ]),
  )
  .with_export_sec(
    ExportSec::new([
      Export::new(Name::from_string("run"), ExternIdx::func(FuncIdx::new(0U))),
      Export::new(
        Name::from_string("state"),
        ExternIdx::global(GlobalIdx::new(0U)),
      ),
    ]),
  )
}

///|
fn gen_valid_constraint_throwing_stack_module(seed : UInt64) -> Module {
  let i32 = ValType::i32()
  let before = seed.to_uint().reinterpret_as_int() ^ 0x4312
  let c = fn(value : Int32) { Instruction::i32_const(I32(value)) }
  let get = fn(slot : UInt32) { Instruction::local_get(LocalIdx::new(slot)) }
  Module::new()
  .with_type_sec(
    TypeSec::new([
      RecType::new(SubType::comp_type(CompType::func([i32, i32], [i32]))),
      RecType::new(SubType::comp_type(CompType::func([i32], [i32]))),
      RecType::new(SubType::comp_type(CompType::func([], []))),
    ]),
  )
  .with_func_sec(FuncSec::new([TypeIdx::new(0U), TypeIdx::new(1U)]))
  .with_tag_sec(TagSec::new([TagType::new(TypeIdx::new(2U))]))
  .with_code_sec(
    CodeSec::new([
      // The unused argument also exercises DAE2 signature rewriting when this
      // generated shape is passed through that direct-pass lane.
      Func::new(
        Locals::empty(),
        Expr::new([
          get(0U),
          Instruction::if_(
            BlockType::void_(),
            [Instruction::throw_(TagIdx::new(0U))],
            None,
          ),
          c(7),
        ]),
      ),
      Func::new(
        Locals::from_types([i32]),
        Expr::new([
          c(before),
          Instruction::local_set(LocalIdx::new(1U)),
          Instruction::try_(
            BlockType::val_type(i32),
            Expr::new([
              get(0U),
              c(19),
              Instruction::call(FuncIdx::new(0U)),
              get(1U),
              c(before ^ 1),
              Instruction::local_set(LocalIdx::new(1U)),
              Instruction::i32_add(),
            ]),
            [@lib.LegacyCatch::all(Expr::new([get(1U)]))],
          ),
          // This provable fact forces constraint rewriting while keeping the
          // result and handler-visible local observable.
          get(1U),
          get(1U),
          Instruction::i32_eq(),
          Instruction::i32_add(),
        ]),
      ),
    ]),
  )
  .with_export_sec(
    ExportSec::new([
      Export::new(Name::from_string("run"), ExternIdx::func(FuncIdx::new(1U))),
    ]),
  )
}