// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.

///|
// The shared DFE remapper predates several proposal instructions. Admit only
// instructions whose type references it scans and rewrites, including children.
// Scalar i32.add carries no type index and is preserved unchanged.
fn precompute_type_cleanup_instrs_supported(
  instrs : Array[@lib.Instruction],
) -> Bool {
  for instr in instrs {
    match instr {
      @lib.Block(_, _) | @lib.Loop(_, _) | @lib.If(_, _, _) => {
        let mut supported = true
        ignore(
          pass_lower_map_children(instr, fn(body) {
            if !precompute_type_cleanup_instrs_supported(body) {
              supported = false
            }
            body
          }),
        )
        if !supported {
          return false
        }
      }
      @lib.Unreachable
      | @lib.Nop
      | @lib.Drop
      | @lib.Return
      | @lib.Br(_)
      | @lib.BrIf(_)
      | @lib.BrTable(_, _)
      | @lib.Call(_)
      | @lib.CallIndirect(_, _)
      | @lib.CallRef(_)
      | @lib.ReturnCall(_)
      | @lib.ReturnCallIndirect(_, _)
      | @lib.ReturnCallRef(_)
      | @lib.LocalGet(_)
      | @lib.LocalSet(_)
      | @lib.LocalTee(_)
      | @lib.GlobalGet(_)
      | @lib.GlobalSet(_)
      | @lib.I32Add
      | @lib.I32Const(_)
      | @lib.I64Const(_)
      | @lib.F32Const(_)
      | @lib.F64Const(_)
      | @lib.Select(_)
      | @lib.RefNull(_)
      | @lib.RefFunc(_)
      | @lib.RefIsNull
      | @lib.RefEq
      | @lib.RefAsNonNull
      | @lib.RefI31
      | @lib.RefTest(_, _, _)
      | @lib.RefCast(_, _, _)
      | @lib.StructNew(_)
      | @lib.StructNewDefault(_)
      | @lib.StructGet(_, _)
      | @lib.StructGetS(_, _)
      | @lib.StructGetU(_, _)
      | @lib.StructSet(_, _)
      | @lib.ArrayNew(_)
      | @lib.ArrayNewDefault(_)
      | @lib.ArrayNewFixed(_, _)
      | @lib.ArrayGet(_)
      | @lib.ArrayGetS(_)
      | @lib.ArrayGetU(_)
      | @lib.ArraySet(_)
      | @lib.ArrayLen => ()
      _ => return false
    }
  }
  true
}

///|
fn precompute_type_cleanup_supported(mod_ : @lib.Module) -> Bool {
  // Ordinary custom sections are opaque here. They may encode type indices or
  // section-relative offsets (for example branch hints, DWARF, or reloc.CODE),
  // and the simple-type remapper cannot update those payloads.
  if !mod_.custom_secs.is_empty() {
    return false
  }
  let types = match mod_.type_sec {
    Some(@lib.TypeSec(types)) => types
    None => return false
  }
  let mut has_gc = false
  for rec_type in types {
    match rec_type {
      @lib.SingleRecType(subtype) => {
        if subtype.metadata().is_shared() ||
          !subtype.super_types().is_empty() ||
          subtype.describes_type() is Some(_) ||
          subtype.descriptor_type() is Some(_) {
          return false
        }
        match subtype.get_comptype() {
          @lib.FuncCompType(params, results) =>
            if dfe_val_types_mention_type_idxs(params) ||
              dfe_val_types_mention_type_idxs(results) {
              return false
            }
          @lib.ArrayCompType(field) => {
            if dfe_field_type_mentions_type_idxs(field) {
              return false
            }
            has_gc = true
          }
          @lib.StructCompType(fields) => {
            if dfe_field_types_mention_type_idxs(fields) {
              return false
            }
            has_gc = true
          }
          _ => return false
        }
      }
      @lib.GroupRecType(_) => return false
    }
  }
  if !has_gc {
    return false
  }
  pass_type_cleanup_reference_surfaces_supported(mod_)
}

///|
// Run only after writeback repair: pruning changes every surviving type index,
// including functions outside the touched set. Byte equality avoids NaN != NaN.
fn precompute_cleanup_unused_gc_types(
  original : @lib.Module,
  mod_ : @lib.Module,
) -> @lib.Module {
  if !precompute_type_cleanup_supported(mod_) {
    return mod_
  }
  let old_code = match original.code_sec {
    Some(code) => @binary.encode_module(@lib.Module::new().with_code_sec(code))
    None => return mod_
  }
  let new_code = match mod_.code_sec {
    Some(code) => @binary.encode_module(@lib.Module::new().with_code_sec(code))
    None => return mod_
  }
  match (old_code, new_code) {
    (Ok(old_bytes), Ok(new_bytes)) if old_bytes != new_bytes => ()
    _ => return mod_
  }
  let pruned = dfe_prune_unused_simple_types(mod_)
  match (@binary.encode_module(mod_), @binary.encode_module(pruned)) {
    (Ok(before), Ok(after)) if after.length() < before.length() =>
      if @validate.validate_module(pruned) is Ok(_) {
        return pruned
      }
    _ => ()
  }
  mod_
}