// 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.

///|
fn pass_encoding_cleanup_has_removable_nop(
  body : Array[@lib.Instruction],
) -> Bool {
  for instr in body {
    match instr {
      @lib.Nop => return true
      @lib.Block(_, @lib.Expr(child))
      | @lib.Loop(_, @lib.Expr(child))
      | @lib.TryTable(_, _, @lib.Expr(child)) =>
        if pass_encoding_cleanup_has_removable_nop(child) {
          return true
        }
      @lib.If(_, yes, no) => {
        if pass_encoding_cleanup_has_removable_nop(yes) {
          return true
        }
        match no {
          Some(child) =>
            if pass_encoding_cleanup_has_removable_nop(child) {
              return true
            }
          None => ()
        }
      }
      _ => ()
    }
  }
  false
}

///|
// Run after writeback repair, while restored functions still use their original
// type and local indices. Reuse the label-name-aware control cleanup and the
// validated byte-saving type and local remappers.
fn pass_cleanup_control_and_type_encoding(
  mod_ : @lib.Module,
  touched~ : Array[Bool]? = None,
  nop_rewrite_attempts~ : Array[Int32]? = None,
  flatten_attempts~ : Array[Int32]? = None,
  perf~ : HotPerfSession? = None,
) -> @lib.Module {
  if pass_module_has_offset_sensitive_metadata(mod_) {
    return mod_
  }
  let nop_start = perf_start_timer_opt(
    perf, "detail:simplify-locals:encoding-nops",
  )
  let without_nops = match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) => {
      let mut changed = false
      let rewritten = funcs.mapi(fn(index, func) {
        if touched is Some(selected) &&
          (index >= selected.length() || !selected[index]) {
          return func
        }
        let @lib.Func(locals, @lib.Expr(body)) = func
        if !pass_encoding_cleanup_has_removable_nop(body) {
          return func
        }
        match nop_rewrite_attempts {
          Some(attempts) => attempts[0] += 1
          None => ()
        }
        let (body, body_changed) = dae_remove_nops_instrs(body)
        if !body_changed {
          return func
        }
        changed = true
        @lib.Func::new(locals, @lib.Expr::new(body))
      })
      if changed {
        mod_.with_code_sec(@lib.CodeSec::new(rewritten))
      } else {
        mod_
      }
    }
    None => mod_
  }
  perf_stop_timer_opt(perf, "detail:simplify-locals:encoding-nops", nop_start)
  let control_start = perf_start_timer_opt(
    perf, "detail:simplify-locals:encoding-control",
  )
  let cleaned = oi_cleanup_control(without_nops, touched~, flatten_attempts~)
  perf_stop_timer_opt(
    perf, "detail:simplify-locals:encoding-control", control_start,
  )
  let type_start = perf_start_timer_opt(
    perf, "detail:simplify-locals:encoding-type",
  )
  let result = if pass_encoding_cleanup_may_remap_types(cleaned, touched) {
    dfe_cleanup_simple_type_encoding(cleaned, perf~)
  } else {
    cleaned
  }
  perf_stop_timer_opt(perf, "detail:simplify-locals:encoding-type", type_start)
  result
}

///|
// Inlining restores original bodies outside the selected functions. Their
// embedded type indices must keep the original module-wide meaning.
fn pass_encoding_cleanup_may_remap_types(
  mod_ : @lib.Module,
  touched : Array[Bool]?,
) -> Bool {
  match touched {
    None => true
    Some(selected) =>
      match mod_.code_sec {
        Some(@lib.CodeSec(funcs)) => {
          for index = 0; index < funcs.length(); index = index + 1 {
            if index >= selected.length() || !selected[index] {
              return false
            }
          }
          true
        }
        None => true
      }
  }
}