///|
fn gen_valid_heap2local_owner_updates_config() -> GenValidConfig {
  let base = gen_valid_heap2local_profile_config(Heap2LocalStructProfile)
  GenValidConfig::{
    ..base,
    max_type_defs: 2,
    max_params: 3,
    max_results: 3,
    max_locals: 1,
    max_body_statements: 4,
    features: GenValidFeatureToggles::{
      ..base.features,
      allow_heap2local_body_generator: false,
    },
  }
}

///|
fn gen_valid_heap2local_owner_append_read(
  body : Array[Instruction],
  field : UInt32,
  delta : Int32,
) -> Unit {
  body.push(Instruction::local_get(LocalIdx::new(3U)))
  body.push(Instruction::struct_get(TypeIdx::new(0U), U32::new(field)))
  if delta != 0 {
    body.push(Instruction::i32_const(I32(delta)))
    body.push(Instruction::i32_add())
  }
}

///|
fn gen_valid_heap2local_owner_arm(
  seed : UInt64,
  epoch : Int32,
  branch : Int32,
) -> Array[Instruction] {
  let body : Array[Instruction] = []
  let base = 10 + ((seed >> 8) & 255UL).to_int()
  // Each condition changes an exported field. Keep the numbers small so
  // numeric observations do not depend on overflow or host conversions.
  let delta = 1 + branch * (1 + ((seed >> 16) & 15UL).to_int())
  if epoch == 0 {
    for field = 0; field < 3; field = field + 1 {
      body.push(Instruction::i32_const(I32(base + field * 100 + delta)))
    }
  } else if epoch == 1 {
    match seed % 3UL {
      0UL => {
        // Swapped fields must both read the owner from the preceding epoch.
        gen_valid_heap2local_owner_append_read(body, 1U, delta)
        gen_valid_heap2local_owner_append_read(body, 0U, 0)
        gen_valid_heap2local_owner_append_read(body, 2U, 0)
      }
      1UL => {
        // A three-field cycle requires staging all operands before publication.
        gen_valid_heap2local_owner_append_read(body, 1U, delta)
        gen_valid_heap2local_owner_append_read(body, 2U, 0)
        gen_valid_heap2local_owner_append_read(body, 0U, 0)
      }
      _ => {
        // The new first field must not replace the old field while the later
        // operands still read it. This is the read-after-write regression.
        body.push(Instruction::i32_const(I32(base + 500 + delta)))
        gen_valid_heap2local_owner_append_read(body, 0U, 0)
        gen_valid_heap2local_owner_append_read(body, 1U, 0)
      }
    }
  } else {
    gen_valid_heap2local_owner_append_read(body, 2U, delta)
    gen_valid_heap2local_owner_append_read(body, 0U, 0)
    gen_valid_heap2local_owner_append_read(body, 1U, 0)
  }
  body.push(Instruction::struct_new(TypeIdx::new(0U)))
  body
}

///|
fn gen_valid_heap2local_owner_updates_module(seed : UInt64) -> Module {
  let owner_ref = ValType::ref_type(
    RefType::new(true, HeapType::new(TypeIdx::new(0U))),
  )
  let fresh_ref = ValType::ref_type(
    RefType::new(false, HeapType::new(TypeIdx::new(0U))),
  )
  let body : Array[Instruction] = []
  for epoch = 0; epoch < 3; epoch = epoch + 1 {
    body.push(Instruction::local_get(LocalIdx::new(epoch.to_uint())))
    body.push(
      Instruction::if_(
        BlockType::val_type(fresh_ref),
        gen_valid_heap2local_owner_arm(seed, epoch, 0),
        Some(gen_valid_heap2local_owner_arm(seed, epoch, 1)),
      ),
    )
    body.push(Instruction::local_set(LocalIdx::new(3U)))
  }
  for field = 0U; field < 3U; field = field + 1U {
    gen_valid_heap2local_owner_append_read(body, field, 0)
  }
  Module::new()
  .with_type_sec(
    TypeSec::new([
      RecType::new(
        SubType::comp_type(
          CompType::struct_([
            FieldType::new(StorageType::val_type(ValType::i32()), Const),
            FieldType::new(StorageType::val_type(ValType::i32()), Const),
            FieldType::new(StorageType::val_type(ValType::i32()), Const),
          ]),
        ),
      ),
      RecType::new(
        SubType::comp_type(
          CompType::func([ValType::i32(), ValType::i32(), ValType::i32()], [
            ValType::i32(),
            ValType::i32(),
            ValType::i32(),
          ]),
        ),
      ),
    ]),
  )
  .with_func_sec(FuncSec::new([TypeIdx::new(1U)]))
  .with_export_sec(
    ExportSec::new([
      Export::new(Name::from_string("run"), ExternIdx::func(FuncIdx::new(0U))),
    ]),
  )
  .with_code_sec(
    CodeSec::new([Func::new(Locals::single(1U, owner_ref), Expr::new(body))]),
  )
}