///|
priv struct BoundedPattern {
  regex : @string.Regex
  weight : Int32
} derive(Debug)

///|
fn BoundedPattern::execute(self : BoundedPattern, text : String) -> Bool {
  self.regex.execute(text) is Some(_)
}

///|
/// Bound the expanded expression before the core parser/compiler can recurse or
/// copy quantified subexpressions. Counts include every alternative and class.
fn pattern_cost(
  pattern : String,
  id : @smithy.ShapeId,
) -> Int32 raise PreparationError {
  let unsupported = fn() {
    UnsupportedConstraint(
      id, "I expected a pattern with at most 32 nested groups and 1024 expanded expression units.",
    )
  }
  let groups : Array[(Int32, Int32)] = []
  let mut total = 0
  let mut last = 0
  let mut i = 0
  while i < pattern.length() {
    let c = pattern.code_unit_at(i).to_int()
    if c == 40 {
      guard groups.length() < 32 else { raise unsupported() }
      groups.push((total, last))
      total = 0
      last = 0
      i += if i + 2 < pattern.length() &&
        pattern.code_unit_at(i + 1) == 63 &&
        pattern.code_unit_at(i + 2) == 58 {
        3
      } else {
        1
      }
      continue
    }
    if c == 42 || c == 43 || c == 63 || c == 123 {
      guard last > 0 else { raise unsupported() }
      let mut copies = 2
      if c == 123 {
        i += 1
        let start = i
        let mut lower = 0
        while i < pattern.length() &&
              pattern.code_unit_at(i) >= 48 &&
              pattern.code_unit_at(i) <= 57 {
          lower = lower * 10 + pattern.code_unit_at(i).to_int() - 48
          guard lower <= 1024 else { raise unsupported() }
          i += 1
        }
        guard i > start else { raise unsupported() }
        copies = lower
        if i < pattern.length() && pattern.code_unit_at(i) == 44 {
          i += 1
          let upper_start = i
          let mut upper = 0
          while i < pattern.length() &&
                pattern.code_unit_at(i) >= 48 &&
                pattern.code_unit_at(i) <= 57 {
            upper = upper * 10 + pattern.code_unit_at(i).to_int() - 48
            guard upper <= 1024 else { raise unsupported() }
            i += 1
          }
          copies = if i == upper_start { lower + 1 } else { upper }
          guard copies >= lower else { raise unsupported() }
        }
        guard i < pattern.length() && pattern.code_unit_at(i) == 125 else {
          raise unsupported()
        }
      }
      let expanded = last * copies + 2
      total = total - last + expanded
      last = expanded
      guard total <= 1024 else { raise unsupported() }
      i += 1
      if i < pattern.length() && pattern.code_unit_at(i) == 63 {
        i += 1
      }
      continue
    }
    let atom = if c == 41 {
      guard groups.pop() is Some((parent, _)) else { raise unsupported() }
      let size = total + 1
      total = parent
      size
    } else if c == 92 {
      i += 1
      guard i < pattern.length() else { raise unsupported() }
      if pattern.code_unit_at(i) == 117 {
        i += 1
        if i < pattern.length() && pattern.code_unit_at(i) == 123 {
          while i < pattern.length() && pattern.code_unit_at(i) != 125 {
            i += 1
          }
        } else {
          i += 3
        }
        guard i < pattern.length() else { raise unsupported() }
      }
      1
    } else if c == 91 {
      let start = i
      i += 1
      while i < pattern.length() && pattern.code_unit_at(i) != 93 {
        if pattern.code_unit_at(i) == 92 {
          i += 1
        }
        i += 1
      }
      guard i < pattern.length() else { raise unsupported() }
      i - start + 1
    } else {
      1
    }
    total += atom
    last = if c == 124 { 0 } else { atom }
    guard total <= 1024 else { raise unsupported() }
    i += 1
  }
  guard groups.is_empty() else { raise unsupported() }
  // UTF-16 lowering and Thompson instructions can expand a source atom.
  Int32::max(1, total) * 8
}

///|
/// Compile the portable pattern profile. Unknown constructs are never ignored.
fn compile_pattern(
  pattern : String,
  id : @smithy.ShapeId,
) -> BoundedPattern raise PreparationError {
  let unsupported = fn() {
    UnsupportedConstraint(
      id, "I could not compile the pattern in the portable regular-expression profile.",
    )
  }
  guard pattern.length() <= 4096 else { raise unsupported() }
  let out = StringBuilder()
  let mut in_class = false
  let mut i = 0
  while i < pattern.length() {
    let c = pattern.code_unit_at(i).to_int()
    if c == 92 {
      i += 1
      guard i < pattern.length() else { raise unsupported() }
      let escape = pattern.code_unit_at(i).to_int()
      let expansion = match escape {
        100 => Some("0-9")
        119 => Some("A-Za-z0-9_")
        115 =>
          Some(
            "\\t\\n\\v\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff",
          )
        _ => None
      }
      if expansion is Some(expansion) {
        out.write_string(
          if in_class {
            expansion
          } else {
            "[" + expansion + "]"
          },
        )
      } else if escape == 68 || escape == 87 || escape == 83 {
        guard !in_class else { raise unsupported() }
        out.write_string(
          match escape {
            68 => "[^0-9]"
            87 => "[^A-Za-z0-9_]"
            _ =>
              "[^\\t\\n\\v\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]"
          },
        )
      } else if escape == 98 && in_class {
        out.write_string("\\u0008")
      } else {
        guard [
          92, 46, 94, 36, 124, 63, 42, 43, 40, 41, 91, 93, 123, 125, 45, 47, 110,
          114, 116, 102, 118, 98, 66, 117,
        ].contains(escape) else {
          raise unsupported()
        }
        out.write_string("\\")
        out.write_string(pattern[i:i + 1].to_owned())
      }
    } else if c == 91 {
      guard !in_class else { raise unsupported() }
      in_class = true
      out.write_string("[")
    } else if c == 93 {
      in_class = false
      out.write_string("]")
    } else if c == 46 && !in_class {
      out.write_string("[^\\n\\r\\u2028\\u2029]")
    } else {
      if c == 40 &&
        !in_class &&
        i + 1 < pattern.length() &&
        pattern.code_unit_at(i + 1) == 63 {
        guard i + 2 < pattern.length() && pattern.code_unit_at(i + 2) == 58 else {
          raise unsupported()
        }
      }
      if c == 40 &&
        !in_class &&
        !(i + 1 < pattern.length() && pattern.code_unit_at(i + 1) == 63) {
        out.write_string("(?:")
        i += 1
        continue
      }
      let end = if c >= 0xd800 && c <= 0xdbff && i + 1 < pattern.length() {
        i + 2
      } else {
        i + 1
      }
      out.write_string(pattern[i:end].to_owned())
      i = end
      continue
    }
    i += 1
  }
  let translated = out.to_string()
  let weight = pattern_cost(translated, id)
  let regex = @string.Regex::Regex(translated) catch {
    _ => raise unsupported()
  }
  // Core compiles its automaton lazily; do that once, only after the cost guard.
  ignore(regex.execute(""))
  { regex, weight, }
}