///|
/// Compiles a pattern known to be valid (aborts otherwise).
pub fn re(pattern : String, flags? : Int = 0) -> @regex.Regex {
  @regex.compile(pattern, flags~) catch {
    e => abort("invalid builtin regex \{pattern.escape()}: \{Repr(e)}")
  }
}

///|
/// Python's `bool(re.search(pattern, text, flags))`, for `analyse_text`.
pub fn search(pattern : @regex.Regex, text : String) -> Bool {
  (pattern.search(text) catch { _ => None }) is Some(_)
}

///|
/// Python's `bool(re.match(pattern, text))`.
pub fn matches(pattern : @regex.Regex, text : String) -> Bool {
  (pattern.match_at(text, 0) catch { _ => None }) is Some(_)
}

///|
let split_path_re : @regex.Regex = re("[/\\\\ ]")

///|
let doctype_lookup_re : @regex.Regex = re(
  (
    #|
    #|    ]*>
  ),
  flags=@regex.DOTALL | @regex.MULTILINE | @regex.VERBOSE,
)

///|
let tag_re : @regex.Regex = re(
  "<([a-zA-Z][a-zA-Z0-9._:-]*)(\\s[^>]*)?>.*?",
  flags=@regex.IGNORECASE | @regex.DOTALL | @regex.MULTILINE,
)

///|
let xml_decl_re : @regex.Regex = re(
  "\\s*<\\?xml[^>]*\\?>",
  flags=@regex.IGNORECASE,
)

///|
/// Python's `shebang_matches`: whether the last component of the shebang
/// line (ignoring `-flags`) fully matches `regex` (case-insensitively).
pub fn shebang_matches(text : String, regex : String) -> Bool {
  let first_line = match text.find("\n") {
    Some(i) => text.unsafe_substring(start=0, end=i).to_lower()
    None => text.to_lower()
  }
  if !first_line.has_prefix("#!") {
    return false
  }
  let rest = first_line.unsafe_substring(start=2, end=first_line.length())
  let parts = (split_path_re.split(rest.trim().to_owned()) catch { _ => [] })
    .filter_map(x => x)
    .filter(x => x != "" && !x.has_prefix("-"))
  guard parts.last() is Some(found) else { return false }
  let full = re(
    "^" + regex + "(\\.(exe|cmd|bat|bin))?$",
    flags=@regex.IGNORECASE,
  )
  search(full, found)
}

///|
/// Python's `doctype_matches`.
pub fn doctype_matches(text : String, regex : String) -> Bool {
  match (doctype_lookup_re.search(text) catch { _ => None }) {
    None => false
    Some(m) => {
      let doctype = m.group(1).unwrap_or("").trim().to_owned()
      matches(re(regex, flags=@regex.IGNORECASE), doctype)
    }
  }
}

///|
/// Python's `html_doctype_matches`.
pub fn html_doctype_matches(text : String) -> Bool {
  doctype_matches(text, "html")
}

///|
/// Python's `looks_like_xml`.
pub fn looks_like_xml(text : String) -> Bool {
  if matches(xml_decl_re, text) {
    return true
  }
  if search(doctype_lookup_re, text) {
    return true
  }
  let head = if text.length() > 1000 {
    text.unsafe_substring(start=0, end=1000)
  } else {
    text
  }
  search(tag_re, head)
}

///|
/// Python's `make_analysator` wrapper: clamps to `[0, 1]`.
pub fn clamp_score(x : Double) -> Double {
  if x.is_nan() || x <= 0.0 {
    0.0
  } else if x >= 1.0 {
    1.0
  } else {
    x
  }
}