// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Conservative attribution from pinned Binaryen version_130 counterpart:
// Copyright 2023 WebAssembly Community Group participants
// Counterpart: src/passes/NoInline.cpp (5d704ad5); origin remains unconfirmed.
// 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 no_inline_no_full_annotation_name() -> String {
  "starshine.no-full-inline"
}

///|
fn no_inline_no_partial_annotation_name() -> String {
  "starshine.no-partial-inline"
}

///|
pub fn no_inline_summary() -> String {
  "Mark named functions as unavailable for full and partial inlining."
}

///|
pub fn no_full_inline_summary() -> String {
  "Mark named functions as unavailable for full inlining."
}

///|
pub fn no_partial_inline_summary() -> String {
  "Mark named functions as unavailable for partial inlining."
}

///|
pub fn no_inline_pass_pattern(flag : String) -> (String, String)? {
  match no_inline_pass_pattern_base(flag) {
    Some(name) => {
      let pattern = flag.sub(start=name.length() + 1).to_owned()
      if pattern.length() > 0 {
        Some((name, pattern))
      } else {
        None
      }
    }
    None => None
  }
}

///|
fn no_inline_pass_pattern_base(flag : String) -> String? {
  if no_inline_pass_pattern_has_base(flag, "no-inline") {
    return Some("no-inline")
  }
  if no_inline_pass_pattern_has_base(flag, "no-full-inline") {
    return Some("no-full-inline")
  }
  if no_inline_pass_pattern_has_base(flag, "no-partial-inline") {
    return Some("no-partial-inline")
  }
  None
}

///|
fn no_inline_pass_pattern_has_base(flag : String, name : String) -> Bool {
  flag.length() > name.length() &&
  flag[name.length()] == '=' &&
  flag.has_prefix(name)
}

///|
fn no_inline_imported_func_count(mod_ : @lib.Module) -> Int32 {
  let mut count = 0
  match mod_.import_sec {
    Some(@lib.ImportSec(imports)) =>
      for import_ in imports {
        match import_ {
          @lib.Import(_, _, @lib.FuncExternType(_)) => count += 1
          _ => ()
        }
      }
    None => ()
  }
  count
}

///|
fn no_inline_defined_func_count(mod_ : @lib.Module) -> Int32 {
  match mod_.func_sec {
    Some(@lib.FuncSec(type_idxs)) => type_idxs.length()
    None => 0
  }
}

///|
fn no_inline_name_to_string(name : @lib.Name) -> String {
  let @lib.Name(text) = name
  text.to_owned()
}

///|
fn no_inline_func_names(mod_ : @lib.Module, total : Int32) -> Array[String?] {
  let names : Array[String?] = Array::make(total, None)
  match mod_.name_sec {
    Some(name_sec) =>
      match name_sec.func_names {
        Some(@lib.NameMap(entries)) =>
          for entry in entries {
            let @lib.NameAssoc(raw_idx, name) = entry
            let idx = raw_idx.reinterpret_as_int()
            if idx >= 0 && idx < names.length() {
              names[idx] = Some(no_inline_name_to_string(name))
            }
          }
        None => ()
      }
    None => ()
  }
  names
}

///|
priv struct NoInlineWildcardProgram {
  pattern_length : Int32
  word_count : Int32
  literal_masks : Array[UInt64]
  question_masks : Array[UInt64]
  star_masks : Array[UInt64]
  fallback_pattern : String?
}

///|
fn no_inline_wildcard_set_bit(words : Array[UInt64], index : Int32) -> Unit {
  let word_idx = index / 64
  words[word_idx] = words[word_idx] | (1UL << (index % 64))
}

///|
fn no_inline_wildcard_compile(pattern : String) -> NoInlineWildcardProgram {
  // Collapsing adjacent stars makes every epsilon closure one step while
  // preserving glob semantics.
  let mut pattern_length = 0
  let mut previous_star = false
  let mut needs_fallback = false
  for pattern_idx = 0
      pattern_idx < pattern.length()
      pattern_idx = pattern_idx + 1 {
    let is_star = pattern[pattern_idx] == '*'
    needs_fallback = needs_fallback ||
      (
        !is_star &&
        pattern[pattern_idx] != '?' &&
        pattern[pattern_idx].to_int() > 255
      )
    if !is_star || !previous_star {
      pattern_length += 1
    }
    previous_star = is_star
  }
  let word_count = (pattern_length + 64) / 64
  let literal_masks = Array::make(256 * word_count, 0UL)
  let question_masks = Array::make(word_count, 0UL)
  let star_masks = Array::make(word_count, 0UL)
  let mut normalized_idx = 0
  previous_star = false
  for pattern_idx = 0
      pattern_idx < pattern.length()
      pattern_idx = pattern_idx + 1 {
    let pattern_byte = pattern[pattern_idx]
    let is_star = pattern_byte == '*'
    if !is_star || !previous_star {
      let word_idx = normalized_idx / 64
      let bit = 1UL << (normalized_idx % 64)
      if is_star {
        star_masks[word_idx] = star_masks[word_idx] | bit
      } else if pattern_byte == '?' {
        question_masks[word_idx] = question_masks[word_idx] | bit
      } else if pattern_byte.to_int() <= 255 {
        let offset = pattern_byte.to_int() * word_count + word_idx
        literal_masks[offset] = literal_masks[offset] | bit
      }
      normalized_idx += 1
    }
    previous_star = is_star
  }
  {
    pattern_length,
    word_count,
    literal_masks,
    question_masks,
    star_masks,
    fallback_pattern: if needs_fallback {
      Some(pattern)
    } else {
      None
    },
  }
}

///|
fn no_inline_wildcard_match_fallback(
  pattern : String,
  text : String,
  visits : Array[Int32],
) -> Bool {
  let width = text.length() + 1
  let mut previous = Array::make(width, false)
  let mut current = Array::make(width, false)
  previous[0] = true
  for pattern_idx = 0
      pattern_idx < pattern.length()
      pattern_idx = pattern_idx + 1 {
    current[0] = pattern[pattern_idx] == '*' && previous[0]
    for text_idx = 1; text_idx <= text.length(); text_idx = text_idx + 1 {
      visits[0] += 1
      current[text_idx] = if pattern[pattern_idx] == '*' {
        previous[text_idx] || current[text_idx - 1]
      } else {
        (
          pattern[pattern_idx] == '?' ||
          pattern[pattern_idx] == text[text_idx - 1]
        ) &&
        previous[text_idx - 1]
      }
    }
    let old = previous
    previous = current
    current = old
  }
  previous[text.length()]
}

///|
fn no_inline_wildcard_match_compiled(
  program : NoInlineWildcardProgram,
  text : String,
  visits : Array[Int32],
  first : Array[UInt64],
  second : Array[UInt64],
) -> Bool {
  match program.fallback_pattern {
    Some(pattern) =>
      return no_inline_wildcard_match_fallback(pattern, text, visits)
    None => ()
  }
  for word_idx = 0; word_idx < program.word_count; word_idx = word_idx + 1 {
    first[word_idx] = 0UL
    second[word_idx] = 0UL
  }
  no_inline_wildcard_set_bit(first, 0)
  if (program.star_masks[0] & 1UL) != 0UL {
    no_inline_wildcard_set_bit(first, 1)
  }
  let mut current = first
  let mut next = second
  for text_idx = 0; text_idx < text.length(); text_idx = text_idx + 1 {
    // Advance every reachable NFA state in machine-word batches. Star states
    // consume in place; literal and question states advance by one bit.
    let text_unit = text[text_idx].to_int()
    let literal_base = if text_unit <= 255 {
      text_unit * program.word_count
    } else {
      -1
    }
    let mut carry = 0UL
    for word_idx = 0; word_idx < program.word_count; word_idx = word_idx + 1 {
      visits[0] += 1
      let matched = current[word_idx] &
        (
          program.question_masks[word_idx] |
          (if literal_base >= 0 {
            program.literal_masks[literal_base + word_idx]
          } else {
            0UL
          })
        )
      next[word_idx] = (current[word_idx] & program.star_masks[word_idx]) |
        (matched << 1) |
        carry
      carry = matched >> 63
    }
    carry = 0UL
    let mut any_reachable = false
    for word_idx = 0; word_idx < program.word_count; word_idx = word_idx + 1 {
      let stars = next[word_idx] & program.star_masks[word_idx]
      next[word_idx] = next[word_idx] | (stars << 1) | carry
      carry = stars >> 63
      any_reachable = any_reachable || next[word_idx] != 0UL
    }
    if !any_reachable {
      return false
    }
    let old = current
    current = next
    next = old
  }
  let accept_word = program.pattern_length / 64
  let accept_bit = 1UL << (program.pattern_length % 64)
  (current[accept_word] & accept_bit) != 0UL
}

///|
fn no_inline_wildcard_match_with_visits(
  pattern : String,
  text : String,
  visits : Array[Int32],
) -> Bool {
  let program = no_inline_wildcard_compile(pattern)
  no_inline_wildcard_match_compiled(
    program,
    text,
    visits,
    Array::make(program.word_count, 0UL),
    Array::make(program.word_count, 0UL),
  )
}

///|
fn no_inline_wildcard_match(pattern : String, text : String) -> Bool {
  no_inline_wildcard_match_with_visits(pattern, text, [0])
}

///|
fn no_inline_annotation_present(
  annotations : Array[@lib.FuncAnnotation],
  name : String,
) -> Bool {
  for annotation in annotations {
    if annotation.name == name {
      return true
    }
  }
  false
}

///|
fn no_inline_existing_policy_map(
  mod_ : @lib.Module,
  total : Int32,
) -> Array[Array[@lib.FuncAnnotation]] {
  let by_func : Array[Array[@lib.FuncAnnotation]] = []
  let mut idx = 0
  while idx < total {
    by_func.push([])
    idx += 1
  }
  match mod_.func_annotation_sec {
    Some(@lib.FuncAnnotationSec(entries)) =>
      for entry in entries {
        let @lib.FuncAnnotationAssoc(raw_idx, annotations) = entry
        let slot = raw_idx.reinterpret_as_int()
        if slot >= 0 && slot < by_func.length() {
          for annotation in annotations {
            if no_inline_policy_annotation_name(annotation) &&
              !no_inline_annotation_present(by_func[slot], annotation.name) {
              by_func[slot].push(annotation)
            }
          }
        }
      }
    None => ()
  }
  by_func
}

///|
fn no_inline_append_policy_associations(
  mod_ : @lib.Module,
  additions : Array[@lib.FuncAnnotationAssoc],
) -> @lib.Module {
  let entries = match mod_.func_annotation_sec {
    Some(@lib.FuncAnnotationSec(entries)) => entries.copy()
    None => []
  }
  for association in additions {
    entries.push(association)
  }
  mod_.with_func_annotation_sec(@lib.FuncAnnotationSec::new(entries))
}

///|
fn no_inline_run_module_pass(
  mod_ : @lib.Module,
  pattern : String,
  mark_full~ : Bool,
  mark_partial~ : Bool,
) -> @lib.Module {
  let total = no_inline_imported_func_count(mod_) +
    no_inline_defined_func_count(mod_)
  let names = no_inline_func_names(mod_, total)
  let by_func = no_inline_existing_policy_map(mod_, total)
  let additions : Array[@lib.FuncAnnotationAssoc] = []
  let wildcard = no_inline_wildcard_compile(pattern)
  let wildcard_visits = [0]
  let wildcard_first = Array::make(wildcard.word_count, 0UL)
  let wildcard_second = Array::make(wildcard.word_count, 0UL)
  let mut changed = false
  let mut idx = 0
  while idx < total {
    // Binaryen assigns every function an internal name. Format the numeric
    // fallback only when no name-section entry exists for this function.
    let name = match names[idx] {
      Some(name) => name
      None => idx.to_string()
    }
    wildcard_visits[0] = 0
    if no_inline_wildcard_match_compiled(
        wildcard, name, wildcard_visits, wildcard_first, wildcard_second,
      ) {
      let owned : Array[@lib.FuncAnnotation] = []
      if mark_full &&
        !no_inline_annotation_present(
          by_func[idx],
          no_inline_no_full_annotation_name(),
        ) {
        let annotation = @lib.FuncAnnotation::new(
          no_inline_no_full_annotation_name(),
        )
        by_func[idx].push(annotation)
        owned.push(annotation)
        changed = true
      }
      if mark_partial &&
        !no_inline_annotation_present(
          by_func[idx],
          no_inline_no_partial_annotation_name(),
        ) {
        let annotation = @lib.FuncAnnotation::new(
          no_inline_no_partial_annotation_name(),
        )
        by_func[idx].push(annotation)
        owned.push(annotation)
        changed = true
      }
      if !owned.is_empty() {
        additions.push(
          @lib.FuncAnnotationAssoc::new(idx.reinterpret_as_uint(), owned),
        )
      }
    }
    idx += 1
  }
  if !changed {
    return mod_
  }
  no_inline_append_policy_associations(mod_, additions)
}

///|

///|
fn no_inline_policy_annotation_name(annotation : @lib.FuncAnnotation) -> Bool {
  annotation.name == no_inline_no_full_annotation_name() ||
  annotation.name == no_inline_no_partial_annotation_name()
}

///|
pub fn no_inline_copy_policy_annotations(
  mod_ : @lib.Module,
  source_idx : UInt32,
  target_idx : UInt32,
) -> @lib.Module {
  let total = no_inline_imported_func_count(mod_) +
    no_inline_defined_func_count(mod_)
  let source = source_idx.reinterpret_as_int()
  let target = target_idx.reinterpret_as_int()
  if source < 0 || source >= total || target < 0 || target >= total {
    return mod_
  }
  let by_func = no_inline_existing_policy_map(mod_, total)
  let additions : Array[@lib.FuncAnnotation] = []
  for annotation in by_func[source] {
    if no_inline_policy_annotation_name(annotation) &&
      !no_inline_annotation_present(by_func[target], annotation.name) {
      by_func[target].push(annotation)
      additions.push(annotation)
    }
  }
  if additions.is_empty() {
    return mod_
  }
  no_inline_append_policy_associations(mod_, [
    @lib.FuncAnnotationAssoc::new(target.reinterpret_as_uint(), additions),
  ])
}

///|
fn no_inline_policy_flags(mod_ : @lib.Module) -> (Array[Bool], Array[Bool]) {
  let total = no_inline_imported_func_count(mod_) +
    no_inline_defined_func_count(mod_)
  let no_full = Array::make(total, false)
  let no_partial = Array::make(total, false)
  match mod_.func_annotation_sec {
    Some(@lib.FuncAnnotationSec(entries)) =>
      for entry in entries {
        let @lib.FuncAnnotationAssoc(raw_idx, annotations) = entry
        let slot = raw_idx.reinterpret_as_int()
        if slot >= 0 && slot < total {
          for annotation in annotations {
            if annotation.name == no_inline_no_full_annotation_name() {
              no_full[slot] = true
            } else if annotation.name == no_inline_no_partial_annotation_name() {
              no_partial[slot] = true
            }
          }
        }
      }
    None => ()
  }
  (no_full, no_partial)
}