///|
fn pkl_value_int(value : Value) -> Int64? {
  match value {
    IntValue(inner) => Some(inner)
    _ => None
  }
}

///|
fn pkl_constraint_predicate_accepts_float(
  predicate : ConstraintIntPredicate,
  value : Double,
) -> Bool {
  // PKL-112: thresholds are encoded as Double, so Float / Int values
  // can compare against them uniformly without precision loss.
  match predicate {
    IsBetween(lower, upper) => value >= lower && value <= upper
    IsPositive => value >= 0.0
    IsGreaterThan(threshold) => value > threshold
    IsLessThan(threshold) => value < threshold
    NotIsBetween(lower, upper) => !(value >= lower && value <= upper)
    NotIsPositive => !(value >= 0.0)
    NotIsGreaterThan(threshold) => !(value > threshold)
    NotIsLessThan(threshold) => !(value < threshold)
    CustomGreaterThan(_, threshold) => value > threshold
    CustomLessThan(_, threshold) => value < threshold
    CustomGreaterOrEqual(_, threshold) => value >= threshold
    CustomLessOrEqual(_, threshold) => value <= threshold
    NotCustomGreaterThan(_, threshold) => !(value > threshold)
    NotCustomLessThan(_, threshold) => !(value < threshold)
    NotCustomGreaterOrEqual(_, threshold) => !(value >= threshold)
    NotCustomLessOrEqual(_, threshold) => !(value <= threshold)
    ThisCompare(op, threshold, this_on_left) =>
      pkl_constraint_compare_eval(value, op, threshold, this_on_left)
    NotThisCompare(op, threshold, this_on_left) =>
      !pkl_constraint_compare_eval(value, op, threshold, this_on_left)
  }
}

///|
/// PKL-148b: run a `this  N` (or `N  this`) bare comparison
/// against a candidate value. Used by ThisCompare / NotThisCompare;
/// kept separate so the predicate evaluator stays branch-flat.
fn pkl_constraint_compare_eval(
  value : Double,
  op : ConstraintCompareOp,
  threshold : Double,
  this_on_left : Bool,
) -> Bool {
  let (left, right) = if this_on_left {
    (value, threshold)
  } else {
    (threshold, value)
  }
  match op {
    CmpGreaterThan => left > right
    CmpGreaterOrEqual => left >= right
    CmpLessThan => left < right
    CmpLessOrEqual => left <= right
    CmpEqual => left == right
    CmpNotEqual => left != right
  }
}

///|
fn pkl_constrained_float_rejection_message_from_source(
  _display_name : String,
  source_name : String,
  value : Double,
) -> String? {
  for predicate in pkl_constrained_int_predicates(source_name) {
    if !pkl_constraint_predicate_accepts_float(predicate, value) {
      return Some(
        // PKL-148: align with Apple Pkl's exact diagnostic wording so
        // snippetTest fixtures that capture this string via
        // `test.catch(...)` match byte-for-byte.
        "Type constraint `\{pkl_constraint_name(predicate)}` violated. Value: \{value}",
      )
    }
  }
  None
}

///|
fn pkl_user_defined_constrained_type_annotation_value_rejection_message_from_source(
  display_name : String,
  source_name : String,
  value : Value,
  declarations : Array[Declaration],
) -> String? {
  match
    pkl_user_defined_constrained_type_source_name(source_name, declarations) {
    Some(resolved_source_name) =>
      match pkl_value_int(value) {
        Some(inner) =>
          pkl_user_defined_constrained_int_rejection_message_from_source(
            display_name, resolved_source_name, inner, declarations,
          )
        None => None
      }
    None => None
  }
}

///|
fn pkl_user_defined_constrained_type_annotation_value_rejection_message(
  type_name : String?,
  value : Value,
  declarations : Array[Declaration],
) -> String? {
  match type_name {
    Some(display_name) =>
      match
        pkl_user_defined_constrained_type_source_name(
          display_name, declarations,
        ) {
        Some(source_name) =>
          match pkl_value_int(value) {
            Some(inner) =>
              pkl_user_defined_constrained_int_rejection_message_from_source(
                display_name, source_name, inner, declarations,
              )
            None => None
          }
        None => None
      }
    None => None
  }
}

///|
fn pkl_constrained_type_annotation_value_is_valid(
  type_name : String?,
  value : Value,
  diagnostics : Array[Diagnostic],
) -> Bool {
  match
    pkl_constrained_type_annotation_value_rejection_message(type_name, value) {
    Some(message) => {
      diagnostics.push(diag(message))
      false
    }
    None => true
  }
}

///|
fn pkl_constrained_type_annotation_value_rejection_message(
  type_name : String?,
  value : Value,
) -> String? {
  match type_name {
    Some(name) =>
      pkl_constrained_type_annotation_value_rejection_message_from_source(
        name, name, value,
      )
    None => None
  }
}

///|
fn pkl_constrained_type_annotation_value_rejection_message_from_source(
  display_name : String,
  source_name : String,
  value : Value,
) -> String? {
  // PKL-148ae: `String(!isEmpty)?` / `Int(isPositive)?` etc. are
  // nullable-wrapped constrained types. NullValue trivially satisfies
  // the `?`; for non-null values, strip the trailing `?` and recurse
  // through the same cascade so the inner constraint cascade actually
  // runs. Without the unwrap, `pkl_constrained_type_base_name` returns
  // None (because the trailing `?` breaks the `has_suffix(")")` check
  // it needs) and the predicate dispatch silently returns None.
  if source_name.has_suffix("?") {
    match value {
      NullValue => return None
      _ => {
        let inner = String::unsafe_substring(
          source_name,
          start=0,
          end=source_name.length() - 1,
        )
        let inner_display = if display_name.has_suffix("?") {
          String::unsafe_substring(
            display_name,
            start=0,
            end=display_name.length() - 1,
          )
        } else {
          display_name
        }
        return pkl_constrained_type_annotation_value_rejection_message_from_source(
          inner_display, inner, value,
        )
      }
    }
  }
  match pkl_builtin_type_alias_target(source_name) {
    Some(target) =>
      return pkl_constrained_type_annotation_value_rejection_message_from_source(
        display_name, target, value,
      )
    None => ()
  }
  if value is NullValue {
    match pkl_constrained_any_not_null_constraint_name(source_name) {
      Some(name) =>
        return Some("Type constraint `\{name}` violated. Value: null")
      None => ()
    }
  }
  match pkl_value_int(value) {
    Some(inner) =>
      return pkl_constrained_int_rejection_message_from_source(
        display_name, source_name, inner,
      )
    None => ()
  }
  // PKL-092: Float values dispatch through the same predicate set as
  // Int. The Float branch comes after the Int branch so an `IntValue`
  // stays in the Int-error format; only true `FloatValue`s reach here.
  match value {
    FloatValue(inner) =>
      return pkl_constrained_float_rejection_message_from_source(
        display_name, source_name, inner,
      )
    _ => ()
  }
  match pkl_value_string(value) {
    Some(inner) =>
      return pkl_constrained_string_rejection_message_from_source(
        display_name, source_name, inner,
      )
    None => ()
  }
  // PKL-148b: `Listing(...)` host constraints fire against the
  // whole Listing (e.g. `Listing(!isEmpty)`) before delegating
  // element-side cascade to the existing collection walker.
  match value {
    ListingValue(elements)
    | DefaultedListingValue(_, elements, _)
    | ListValue(elements) =>
      match
        pkl_constrained_listing_rejection_message_from_source(
          source_name, elements,
        ) {
        Some(message) => return Some(message)
        None => ()
      }
    _ => ()
  }
  pkl_collection_element_rejection_message_from_source(
    display_name, source_name, value,
  )
}

///|
/// PKL-148b: evaluate the `Listing()` host constraint
/// against the actual Listing value. Today the supported predicates are
/// the bare `isEmpty` / `!isEmpty` zero-arg form; richer ones land as
/// follow-up.
fn pkl_constrained_listing_rejection_message_from_source(
  source_name : String,
  elements : Array[Value],
) -> String? {
  // Only fire when the host name is `Listing(<...>)` — the inner element
  // type doesn't affect the host predicate.
  match pkl_constrained_type_base_name(source_name) {
    Some(base) =>
      if !(base == "Listing" || base.has_prefix("Listing<")) {
        return None
      }
    None => return None
  }
  let text = match pkl_constrained_type_constraint_text(source_name) {
    Some(t) => pkl_constraint_trim(t)
    None => return None
  }
  let parts = pkl_split_constraint_arguments(text)
  for part in parts {
    let trimmed = pkl_constraint_trim(part)
    let negated = trimmed.has_prefix("!")
    let bare = if negated {
      pkl_constraint_trim(
        String::unsafe_substring(trimmed, start=1, end=trimmed.length()),
      )
    } else {
      trimmed
    }
    if bare == "isEmpty" {
      let is_empty = elements.length() == 0
      let violated = if negated { is_empty } else { !is_empty }
      if violated {
        // Render the rejected Listing using PCF inline form so the
        // user sees what was rejected.
        let buf = StringBuilder::new()
        render_listing_for_diag(elements, buf)
        return Some(
          "Type constraint `\{trimmed}` violated. Value: \{buf.to_string()}",
        )
      }
    }
  }
  None
}

///|
fn render_listing_for_diag(
  elements : Array[Value],
  buf : StringBuilder,
) -> Unit {
  if elements.length() == 0 {
    buf.write_string("new Listing {}")
  } else {
    buf.write_string("new Listing { ... }")
  }
}

///|
/// If `source_name` looks like `Listing` / `Mapping`, walk the
/// matching value variant and apply the same constrained-type cascade
/// to each element. Non-collection sources fall through to `None`,
/// preserving the existing dispatch behaviour for scalars.
fn pkl_collection_element_rejection_message_from_source(
  display_name : String,
  source_name : String,
  value : Value,
) -> String? {
  // PKL-148ae: strip the outer `()` from the source name
  // before extracting the generic argument. `List(!isEmpty)`
  // has a top-level constraint that hides the `<...>` from `generic_argument_text`'s
  // `has_suffix(">")` check — `pkl_constrained_type_base_name` returns
  // `List` which is then directly walkable.
  let base_source = match pkl_constrained_type_base_name(source_name) {
    Some(b) => b
    None => source_name
  }
  // PKL-148ae: `List` joins `Listing` for element-side cascade,
  // matching Apple Pkl's symmetric treatment — `(List)`
  // arg / return constraints walk each element through the same
  // predicate set.
  let listing_or_list = match generic_argument_text(base_source, "Listing") {
    Some(t) => Some(t)
    None => generic_argument_text(base_source, "List")
  }
  match listing_or_list {
    Some(element_type) =>
      match value {
        ListingValue(elements)
        | DefaultedListingValue(_, elements, _)
        | ListValue(elements)
        | SetValue(elements) =>
          for element in elements {
            match
              pkl_constrained_type_annotation_value_rejection_message_from_source(
                element_type, element_type, element,
              ) {
              Some(message) => return Some(message)
              None => ()
            }
          } nobreak {
            return None
          }
        _ => return None
      }
    None => ()
  }
  // PKL-148ae: `Map` joins `Mapping` for entry-side cascade.
  let mapping_or_map = match generic_argument_text(base_source, "Mapping") {
    Some(t) => Some(t)
    None => generic_argument_text(base_source, "Map")
  }
  match mapping_or_map {
    Some(inner_text) => {
      let parts = split_top_level_generic_arguments(inner_text)
      if parts.length() != 2 {
        return None
      }
      let key_type = parts[0]
      let value_type = parts[1]
      let walk_entries = fn(entries : Array[ValueEntry]) -> String? {
        for entry in entries {
          match
            pkl_constrained_type_annotation_value_rejection_message_from_source(
              key_type,
              key_type,
              entry.key,
            ) {
            Some(message) => return Some(message)
            None => ()
          }
          match
            pkl_constrained_type_annotation_value_rejection_message_from_source(
              value_type,
              value_type,
              entry.value,
            ) {
            Some(message) => return Some(message)
            None => ()
          }
        } nobreak {
          None
        }
      }
      match value {
        MappingValue(entries)
        | DefaultedMappingValue(_, entries, _)
        | MapValue(entries) =>
          match walk_entries(entries) {
            Some(message) => return Some(message)
            None => return None
          }
        _ => return None
      }
    }
    None => ()
  }
  let _ = display_name
  None
}

///|
priv enum ConstraintStringLengthOp {
  StringLengthGreaterThan
  StringLengthGreaterOrEqual
  StringLengthLessThan
  StringLengthLessOrEqual
  StringLengthEqual
  StringLengthNotEqual
}

///|
priv enum ConstraintStringPredicate {
  StringLengthIsBetween(Int, Int)
  StringLengthIsPositive
  StringLengthIsOdd
  StringLengthIsGreaterThan(Int)
  StringLengthIsLessThan(Int)
  StringLengthCompare(ConstraintStringLengthOp, Int)
  StringMatchesRegex(String)
  StringEqualsCapitalize
  StringIsEmpty
  StringStartsWith(String)
  StringEndsWith(String)
  StringContains(String)
  NotStringLengthIsBetween(Int, Int)
  NotStringLengthIsPositive
  NotStringLengthIsOdd
  NotStringLengthIsGreaterThan(Int)
  NotStringLengthIsLessThan(Int)
  NotStringLengthCompare(ConstraintStringLengthOp, Int)
  NotStringMatchesRegex(String)
  NotStringEqualsCapitalize
  NotStringIsEmpty
  NotStringStartsWith(String)
  NotStringEndsWith(String)
  NotStringContains(String)
}

///|
fn pkl_value_string(value : Value) -> String? {
  match value {
    StringValue(inner) => Some(inner)
    _ => None
  }
}

///|
fn pkl_constraint_trim(text : String) -> String {
  let mut start = 0
  let mut end = text.length()
  while start < end {
    let c = text[start].to_int().unsafe_to_char()
    if c == ' ' || c == '\t' {
      start += 1
    } else {
      break
    }
  }
  while end > start {
    let c = text[end - 1].to_int().unsafe_to_char()
    if c == ' ' || c == '\t' {
      end -= 1
    } else {
      break
    }
  }
  String::unsafe_substring(text, start~, end~)
}

///|
fn pkl_strip_default_type_marker(text : String) -> String {
  let trimmed = pkl_constraint_trim(text)
  if trimmed.has_prefix("*") {
    pkl_constraint_trim(
      String::unsafe_substring(trimmed, start=1, end=trimmed.length()),
    )
  } else {
    trimmed
  }
}

///|
fn pkl_constraint_split_at_operator(
  text : String,
  op : String,
) -> (String, String)? {
  match text.find(op) {
    Some(index) =>
      Some(
        (
          String::unsafe_substring(text, start=0, end=index),
          String::unsafe_substring(
            text,
            start=index + op.length(),
            end=text.length(),
          ),
        ),
      )
    None => None
  }
}

///|
fn pkl_string_length_lhs_matches(text : String) -> Bool {
  let trimmed = pkl_constraint_trim(text)
  trimmed == "length" || trimmed == "this.length"
}

///|
/// Parse a `length OP N` comparison fragment. Operators are tried in
/// length-descending order so `>=` is recognized before `>`.
fn pkl_string_length_comparison_predicate(
  text : String,
) -> ConstraintStringPredicate? {
  let ops : Array[(String, ConstraintStringLengthOp)] = [
    (">=", StringLengthGreaterOrEqual),
    ("<=", StringLengthLessOrEqual),
    ("==", StringLengthEqual),
    ("!=", StringLengthNotEqual),
    (">", StringLengthGreaterThan),
    ("<", StringLengthLessThan),
  ]
  for entry in ops {
    let (op_text, op) = entry
    match pkl_constraint_split_at_operator(text, op_text) {
      Some((lhs, rhs)) =>
        if pkl_string_length_lhs_matches(lhs) {
          match pkl_parse_constraint_int_text(pkl_constraint_trim(rhs)) {
            Some(value) => return Some(StringLengthCompare(op, value))
            None => ()
          }
        }
      None => ()
    }
  }
  None
}

///|
fn pkl_string_length_dot_method_predicate(
  text : String,
) -> ConstraintStringPredicate? {
  let prefix = if text.has_prefix("length.") {
    Some("length.".length())
  } else if text.has_prefix("this.length.") {
    Some("this.length.".length())
  } else {
    None
  }
  match prefix {
    Some(start) => {
      let suffix = String::unsafe_substring(text, start~, end=text.length())
      // Reuse the existing Int predicate parser against the `.length`
      // suffix so length.isBetween / length.isGreaterThan / length.isLessThan
      // share the numeric grammar. PKL-112 lifted the threshold encoding
      // to Double — `length` is always Int so we truncate the threshold
      // back to Int here. Non-integer thresholds against `length` get
      // routed back through this branch only when the upstream parser
      // chose to accept them, which is the same behaviour Apple Pkl
      // exhibits (truncation toward zero).
      match pkl_int_constraint_predicate(suffix) {
        Some(IsBetween(lower, upper)) =>
          Some(StringLengthIsBetween(lower.to_int(), upper.to_int()))
        Some(IsPositive) => Some(StringLengthIsPositive)
        Some(IsGreaterThan(n)) => Some(StringLengthIsGreaterThan(n.to_int()))
        Some(IsLessThan(n)) => Some(StringLengthIsLessThan(n.to_int()))
        Some(NotIsBetween(lower, upper)) =>
          Some(NotStringLengthIsBetween(lower.to_int(), upper.to_int()))
        Some(NotIsPositive) => Some(NotStringLengthIsPositive)
        Some(NotIsGreaterThan(n)) =>
          Some(NotStringLengthIsGreaterThan(n.to_int()))
        Some(NotIsLessThan(n)) => Some(NotStringLengthIsLessThan(n.to_int()))
        _ => None
      }
    }
    None => None
  }
}

///|
fn pkl_string_this_equals_capitalize_predicate(
  text : String,
) -> ConstraintStringPredicate? {
  match pkl_constraint_split_at_operator(text, "==") {
    Some((lhs, rhs)) => {
      let left = pkl_constraint_trim(lhs)
      let right = pkl_constraint_trim(rhs)
      if left == "this" && right == "capitalize()" {
        Some(StringEqualsCapitalize)
      } else if left == "capitalize()" && right == "this" {
        Some(StringEqualsCapitalize)
      } else {
        None
      }
    }
    None => None
  }
}

///|
fn pkl_string_matches_regex_inner(text : String) -> String? {
  let prefix = "matches(Regex(\""
  let suffix = "\"))"
  if text.has_prefix(prefix) &&
    text.has_suffix(suffix) &&
    text.length() >= prefix.length() + suffix.length() {
    Some(
      String::unsafe_substring(
        text,
        start=prefix.length(),
        end=text.length() - suffix.length(),
      ),
    )
  } else {
    None
  }
}

///|
fn pkl_string_constraint_predicate(text : String) -> ConstraintStringPredicate? {
  let trimmed = pkl_constraint_trim(text)
  if trimmed.has_prefix("!") && trimmed.length() > 1 {
    let inner = String::unsafe_substring(trimmed, start=1, end=trimmed.length())
    match pkl_string_constraint_predicate(inner) {
      Some(StringLengthIsBetween(lower, upper)) =>
        return Some(NotStringLengthIsBetween(lower, upper))
      Some(StringLengthIsPositive) => return Some(NotStringLengthIsPositive)
      Some(StringLengthIsOdd) => return Some(NotStringLengthIsOdd)
      Some(StringLengthIsGreaterThan(n)) =>
        return Some(NotStringLengthIsGreaterThan(n))
      Some(StringLengthIsLessThan(n)) =>
        return Some(NotStringLengthIsLessThan(n))
      Some(StringLengthCompare(op, n)) =>
        return Some(NotStringLengthCompare(op, n))
      Some(StringMatchesRegex(pattern)) =>
        return Some(NotStringMatchesRegex(pattern))
      Some(StringEqualsCapitalize) => return Some(NotStringEqualsCapitalize)
      Some(StringIsEmpty) => return Some(NotStringIsEmpty)
      Some(StringStartsWith(arg)) => return Some(NotStringStartsWith(arg))
      Some(StringEndsWith(arg)) => return Some(NotStringEndsWith(arg))
      Some(StringContains(arg)) => return Some(NotStringContains(arg))
      _ => return None
    }
  }
  // PKL-148ae: bare `isEmpty` / `this.isEmpty` on a String constraint
  // (Apple Pkl exposes `String.isEmpty` directly; the predicate form
  // shows up without a `length.` prefix unlike `length.isPositive`).
  if trimmed == "isEmpty" || trimmed == "this.isEmpty" {
    return Some(StringIsEmpty)
  }
  // PKL-148ai: bare `endsWith("X")` / `startsWith("X")` / `contains("X")`
  // (also the `this.endsWith(...)` form). The constraint name renders the
  // argument verbatim including the quotes — gold diagnostic shape is
  // `Type constraint `endsWith("A")` violated. Value: "noegiP"`.
  for prefix_with_paren in ["endsWith(", "this.endsWith("] {
    if trimmed.has_prefix(prefix_with_paren) && trimmed.has_suffix(")") {
      let inner = String::unsafe_substring(
        trimmed,
        start=prefix_with_paren.length(),
        end=trimmed.length() - 1,
      )
      let arg = pkl_constraint_trim(inner)
      if arg.length() >= 2 && arg.has_prefix("\"") && arg.has_suffix("\"") {
        return Some(
          StringEndsWith(
            String::unsafe_substring(arg, start=1, end=arg.length() - 1),
          ),
        )
      }
    }
  }
  for prefix_with_paren in ["startsWith(", "this.startsWith("] {
    if trimmed.has_prefix(prefix_with_paren) && trimmed.has_suffix(")") {
      let inner = String::unsafe_substring(
        trimmed,
        start=prefix_with_paren.length(),
        end=trimmed.length() - 1,
      )
      let arg = pkl_constraint_trim(inner)
      if arg.length() >= 2 && arg.has_prefix("\"") && arg.has_suffix("\"") {
        return Some(
          StringStartsWith(
            String::unsafe_substring(arg, start=1, end=arg.length() - 1),
          ),
        )
      }
    }
  }
  for prefix_with_paren in ["contains(", "this.contains("] {
    if trimmed.has_prefix(prefix_with_paren) && trimmed.has_suffix(")") {
      let inner = String::unsafe_substring(
        trimmed,
        start=prefix_with_paren.length(),
        end=trimmed.length() - 1,
      )
      let arg = pkl_constraint_trim(inner)
      if arg.length() >= 2 && arg.has_prefix("\"") && arg.has_suffix("\"") {
        return Some(
          StringContains(
            String::unsafe_substring(arg, start=1, end=arg.length() - 1),
          ),
        )
      }
    }
  }
  match pkl_string_length_dot_method_predicate(trimmed) {
    Some(predicate) => return Some(predicate)
    None => ()
  }
  if trimmed == "length.isOdd" || trimmed == "this.length.isOdd" {
    return Some(StringLengthIsOdd)
  }
  match pkl_string_length_comparison_predicate(trimmed) {
    Some(predicate) => return Some(predicate)
    None => ()
  }
  match pkl_string_this_equals_capitalize_predicate(trimmed) {
    Some(predicate) => return Some(predicate)
    None => ()
  }
  match pkl_string_matches_regex_inner(trimmed) {
    Some(pattern) => return Some(StringMatchesRegex(pattern))
    None => ()
  }
  None
}

///|
fn pkl_constrained_string_predicates(
  type_name : String,
) -> Array[ConstraintStringPredicate] {
  let predicates : Array[ConstraintStringPredicate] = []
  match pkl_constrained_type_base_name(type_name) {
    Some("String") =>
      match pkl_constrained_type_constraint_text(type_name) {
        Some(text) => {
          let parts = pkl_split_constraint_arguments(text)
          for part in parts {
            match pkl_string_constraint_predicate(part) {
              Some(predicate) => predicates.push(predicate)
              None => ()
            }
          }
        }
        None => ()
      }
    _ => ()
  }
  predicates
}

///|
fn pkl_string_length_op_text(op : ConstraintStringLengthOp) -> String {
  match op {
    StringLengthGreaterThan => ">"
    StringLengthGreaterOrEqual => ">="
    StringLengthLessThan => "<"
    StringLengthLessOrEqual => "<="
    StringLengthEqual => "=="
    StringLengthNotEqual => "!="
  }
}

///|
fn pkl_string_constraint_name(predicate : ConstraintStringPredicate) -> String {
  // PKL-148: include the argument list verbatim so the diagnostic
  // wording matches Apple Pkl exactly (e.g. `length.isBetween(10, 20)`
  // rather than the bare `length.isBetween`).
  match predicate {
    StringLengthIsBetween(lo, hi) => "length.isBetween(\{lo}, \{hi})"
    StringLengthIsPositive => "length.isPositive"
    StringLengthIsOdd => "length.isOdd"
    StringLengthIsGreaterThan(t) => "length.isGreaterThan(\{t})"
    StringLengthIsLessThan(t) => "length.isLessThan(\{t})"
    StringLengthCompare(op, t) => "length \{pkl_string_length_op_text(op)} \{t}"
    StringMatchesRegex(pattern) => "matches(Regex(\"\{pattern}\"))"
    StringEqualsCapitalize => "this == capitalize()"
    StringIsEmpty => "isEmpty"
    StringStartsWith(arg) => "startsWith(\"\{arg}\")"
    StringEndsWith(arg) => "endsWith(\"\{arg}\")"
    StringContains(arg) => "contains(\"\{arg}\")"
    NotStringLengthIsBetween(lo, hi) => "!length.isBetween(\{lo}, \{hi})"
    NotStringLengthIsPositive => "!length.isPositive"
    NotStringLengthIsOdd => "!length.isOdd"
    NotStringLengthIsGreaterThan(t) => "!length.isGreaterThan(\{t})"
    NotStringLengthIsLessThan(t) => "!length.isLessThan(\{t})"
    NotStringLengthCompare(op, t) =>
      "!(length \{pkl_string_length_op_text(op)} \{t})"
    NotStringMatchesRegex(pattern) => "!matches(Regex(\"\{pattern}\"))"
    NotStringEqualsCapitalize => "!(this == capitalize())"
    NotStringIsEmpty => "!isEmpty"
    NotStringStartsWith(arg) => "!startsWith(\"\{arg}\")"
    NotStringEndsWith(arg) => "!endsWith(\"\{arg}\")"
    NotStringContains(arg) => "!contains(\"\{arg}\")"
  }
}

///|
fn pkl_string_length_op_accepts(
  op : ConstraintStringLengthOp,
  length : Int,
  threshold : Int,
) -> Bool {
  match op {
    StringLengthGreaterThan => length > threshold
    StringLengthGreaterOrEqual => length >= threshold
    StringLengthLessThan => length < threshold
    StringLengthLessOrEqual => length <= threshold
    StringLengthEqual => length == threshold
    StringLengthNotEqual => length != threshold
  }
}

///|
fn pkl_string_predicate_accepts(
  predicate : ConstraintStringPredicate,
  value : String,
) -> Bool {
  let len = value.length()
  match predicate {
    StringLengthIsBetween(lower, upper) => len >= lower && len <= upper
    StringLengthIsPositive => len > 0
    StringLengthIsOdd => len % 2 == 1
    StringLengthIsGreaterThan(threshold) => len > threshold
    StringLengthIsLessThan(threshold) => len < threshold
    StringLengthCompare(op, threshold) =>
      pkl_string_length_op_accepts(op, len, threshold)
    StringMatchesRegex(pattern) =>
      try {
        let re = @regexp.compile(pattern)
        let m = re.execute(value)
        m.matched() && m.before().length() == 0 && m.after().length() == 0
      } catch {
        // A malformed pattern means the predicate cannot accept anything;
        // surface this as a rejection via the diagnostic path so the user
        // sees the constraint name plus the bad pattern.
        _ => false
      }
    StringEqualsCapitalize => value == capitalize_first(value, true)
    NotStringLengthIsBetween(lower, upper) => !(len >= lower && len <= upper)
    NotStringLengthIsPositive => !(len > 0)
    NotStringLengthIsOdd => len % 2 == 0
    NotStringLengthIsGreaterThan(threshold) => !(len > threshold)
    NotStringLengthIsLessThan(threshold) => !(len < threshold)
    NotStringLengthCompare(op, threshold) =>
      !pkl_string_length_op_accepts(op, len, threshold)
    NotStringMatchesRegex(pattern) =>
      try {
        let re = @regexp.compile(pattern)
        let m = re.execute(value)
        !(m.matched() && m.before().length() == 0 && m.after().length() == 0)
      } catch {
        _ => true
      }
    NotStringEqualsCapitalize => value != capitalize_first(value, true)
    StringIsEmpty => len == 0
    NotStringIsEmpty => len > 0
    StringStartsWith(arg) => value.has_prefix(arg)
    StringEndsWith(arg) => value.has_suffix(arg)
    StringContains(arg) =>
      match value.find(arg) {
        Some(_) => true
        None => false
      }
    NotStringStartsWith(arg) => !value.has_prefix(arg)
    NotStringEndsWith(arg) => !value.has_suffix(arg)
    NotStringContains(arg) =>
      match value.find(arg) {
        Some(_) => false
        None => true
      }
  }
}

///|
fn pkl_constrained_string_rejection_message_from_source(
  _display_name : String,
  source_name : String,
  value : String,
) -> String? {
  for predicate in pkl_constrained_string_predicates(source_name) {
    if !pkl_string_predicate_accepts(predicate, value) {
      return Some(
        "Type constraint `\{pkl_string_constraint_name(predicate)}` violated. Value: \"\{value}\"",
      )
    }
  }
  None
}