// 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 oi_publish_fresh(instr : @lib.Instruction) -> Bool {
  match instr {
    @lib.StructNew(_)
    | @lib.StructNewDefault(_)
    | @lib.ArrayNew(_)
    | @lib.ArrayNewDefault(_)
    | @lib.ArrayNewFixed(_, _)
    | @lib.ArrayNewData(_, _)
    | @lib.ArrayNewElem(_, _) => true
    _ => false
  }
}

///|
fn oi_publish_local_unshared(
  local_idx : @lib.LocalIdx,
  params : Array[@lib.ValType],
  subtypes : Array[@lib.SubType],
) -> Bool {
  let @lib.LocalIdx(raw) = local_idx
  let index = raw.to_int()
  if index < 0 || index >= params.length() {
    return false
  }
  match params[index] {
    @lib.RefTypeValType(
      @lib.HeapTypeRefType(_, _, @lib.HeapType(@lib.TypeIdx(type_raw)))
    ) => {
      let type_index = type_raw.to_int()
      type_index >= 0 &&
      type_index < subtypes.length() &&
      !subtypes[type_index].metadata().is_shared()
    }
    _ => false
  }
}

///|
fn oi_publish_rewrite_instrs(
  instrs : Array[@lib.Instruction],
  params : Array[@lib.ValType],
  subtypes : Array[@lib.SubType],
  changed : Array[Bool],
) -> Array[@lib.Instruction] {
  let out : Array[@lib.Instruction] = []
  for instr in instrs {
    match instr {
      @lib.Block(block_type, @lib.Expr(body)) =>
        out.push(
          @lib.Instruction::block(
            block_type,
            @lib.Expr::new(
              oi_publish_rewrite_instrs(body, params, subtypes, changed),
            ),
          ),
        )
      @lib.Loop(block_type, @lib.Expr(body)) =>
        out.push(
          @lib.Instruction::loop_(
            block_type,
            @lib.Expr::new(
              oi_publish_rewrite_instrs(body, params, subtypes, changed),
            ),
          ),
        )
      @lib.If(block_type, then_body, else_body) => {
        let next_then = oi_publish_rewrite_instrs(
          then_body, params, subtypes, changed,
        )
        let next_else = match else_body {
          Some(body) =>
            Some(oi_publish_rewrite_instrs(body, params, subtypes, changed))
          None => None
        }
        out.push(@lib.Instruction::if_(block_type, next_then, next_else))
      }
      @lib.Publish => {
        let n = out.length()
        if n == 0 {
          out.push(instr)
          continue
        }
        let last = out[n - 1]
        if oi_publish_fresh(last) || last is @lib.Publish {
          changed[0] = true
        } else if last is @lib.RefAsNonNull &&
          n >= 2 &&
          oi_publish_fresh(out[n - 2]) {
          ignore(out.pop())
          changed[0] = true
        } else if last is @lib.LocalGet(local_idx) &&
          oi_publish_local_unshared(local_idx, params, subtypes) {
          changed[0] = true
        } else {
          match last {
            @lib.Block(_, @lib.Expr(body)) if !body.is_empty() => {
              let final_instr = body[body.length() - 1]
              if (oi_publish_fresh(final_instr) || final_instr is @lib.Publish) &&
                body.all(fn(item) {
                  item is @lib.Nop ||
                  item is @lib.LocalGet(_) ||
                  item is @lib.I32Const(_) ||
                  oi_publish_fresh(item) ||
                  item is @lib.Publish
                }) {
                ignore(out.pop())
                for item in body {
                  if !(item is @lib.Nop) {
                    out.push(item)
                  }
                }
                changed[0] = true
              } else {
                out.push(instr)
              }
            }
            _ => out.push(instr)
          }
        }
      }
      _ => out.push(instr)
    }
  }
  out
}

///|
fn oi_publish_rewrite_func(
  mod_ : @lib.Module,
  func_idx : @lib.FuncIdx,
  func : @lib.Func,
) -> @lib.Func? {
  let call_types = msf_build_call_type_cache(mod_)
  let @lib.FuncIdx(raw) = func_idx
  let index = raw.to_int()
  if index < 0 || index >= call_types.length() {
    return None
  }
  let type_idx = match call_types[index] {
    Some(type_idx) => type_idx
    None => return None
  }
  let signatures = msf_build_type_signature_cache(mod_)
  let params = match msf_cached_signature(signatures, type_idx) {
    Some(@lib.FuncType(params, _)) => params
    None => return None
  }
  let subtypes = cl_module_subtypes(mod_)
  let @lib.Func(locals, @lib.Expr(body)) = func
  let changed = [false]
  let next = oi_publish_rewrite_instrs(body, params, subtypes, changed)
  if changed[0] {
    Some(@lib.Func::new(locals, @lib.Expr::new(next)))
  } else {
    None
  }
}