///|
priv enum ConditionValue {
  JsonValue(Json)
}

///|
priv struct IfConditionContext {
  step_outputs : Map[String, Map[String, String]]
  step_outcomes : Map[String, String]
  step_conclusions : Map[String, String]
  workspace_root : String
  env_context : Map[String, String]
  job_context : Map[String, String]
  vars_context : Map[String, String]
  secrets_context : Map[String, String]
  github_context : Map[String, String]
  runner_context : Map[String, String]
  needs_outputs : Map[String, Map[String, String]]
  needs_results : Map[String, String]
  success_value : Bool
  failure_value : Bool
  cancelled_value : Bool
}

///|
fn new_if_condition_context(
  step_outputs? : Map[String, Map[String, String]] = {},
  step_outcomes? : Map[String, String] = {},
  step_conclusions? : Map[String, String] = {},
  workspace_root? : String = "",
  env_context? : Map[String, String] = {},
  job_context? : Map[String, String] = {},
  vars_context? : Map[String, String] = {},
  secrets_context? : Map[String, String] = {},
  github_context? : Map[String, String] = {},
  runner_context? : Map[String, String] = {},
  needs_outputs? : Map[String, Map[String, String]] = {},
  needs_results? : Map[String, String] = {},
  success_value? : Bool = true,
  failure_value? : Bool = false,
  cancelled_value? : Bool = false,
) -> IfConditionContext {
  {
    step_outputs,
    step_outcomes,
    step_conclusions,
    workspace_root,
    env_context,
    job_context,
    vars_context,
    secrets_context,
    github_context,
    runner_context,
    needs_outputs,
    needs_results,
    success_value,
    failure_value,
    cancelled_value,
  }
}

///|
fn if_text_slice(text : String, start : Int, end_ : Int) -> String {
  String::unsafe_substring(text, start~, end=end_)
}

///|
fn find_if_substring(text : String, pattern : String, start : Int) -> Int? {
  if pattern.length() == 0 {
    return Some(start)
  }
  let mut idx = start
  while idx + pattern.length() <= text.length() {
    let mut matched = true
    let mut offset = 0
    while offset < pattern.length() {
      if text.unsafe_get(idx + offset) != pattern.unsafe_get(offset) {
        matched = false
        break
      }
      offset += 1
    }
    if matched {
      return Some(idx)
    }
    idx += 1
  }
  None
}

///|
fn unwrap_if_expression(text : String) -> String {
  let trimmed = text.trim(chars=" \t\n\r").to_owned()
  if trimmed.has_prefix("${{") &&
    trimmed.has_suffix("}}") &&
    trimmed.length() >= 5 {
    if_text_slice(trimmed, 3, trimmed.length() - 2)
    .trim(chars=" \t\n\r")
    .to_owned()
  } else {
    trimmed
  }
}

///|
fn if_expression_has_outer_parens(text : String) -> Bool {
  guard text.length() >= 2 &&
    text.unsafe_get(0) == '(' &&
    text.unsafe_get(text.length() - 1) == ')' else {
    return false
  }
  let mut depth = 0
  let mut in_single = false
  let mut in_double = false
  let mut idx = 0
  while idx < text.length() {
    let ch = text.unsafe_get(idx)
    if ch == '\'' && !in_double {
      in_single = !in_single
      idx += 1
      continue
    }
    if ch == '"' && !in_single {
      in_double = !in_double
      idx += 1
      continue
    }
    if !in_single && !in_double {
      if ch == '(' {
        depth += 1
      } else if ch == ')' {
        depth -= 1
        if depth == 0 && idx != text.length() - 1 {
          return false
        }
      }
    }
    idx += 1
  }
  depth == 0
}

///|
fn strip_if_outer_parens(text : String) -> String {
  let mut current = unwrap_if_expression(text)
  while if_expression_has_outer_parens(current) {
    current = if_text_slice(current, 1, current.length() - 1)
      .trim(chars=" \t\n\r")
      .to_owned()
  }
  current
}

///|
fn find_top_level_if_operator(text : String, op : String) -> Int? {
  guard op.length() > 0 else { return None }
  let mut depth = 0
  let mut in_single = false
  let mut in_double = false
  let mut idx = 0
  while idx + op.length() <= text.length() {
    let ch = text.unsafe_get(idx)
    if ch == '\'' && !in_double {
      in_single = !in_single
      idx += 1
      continue
    }
    if ch == '"' && !in_single {
      in_double = !in_double
      idx += 1
      continue
    }
    if !in_single && !in_double {
      if ch == '(' {
        depth += 1
        idx += 1
        continue
      }
      if ch == ')' {
        depth -= 1
        idx += 1
        continue
      }
      if depth == 0 {
        let mut matched = true
        let mut offset = 0
        while offset < op.length() {
          if text.unsafe_get(idx + offset) != op.unsafe_get(offset) {
            matched = false
            break
          }
          offset += 1
        }
        if matched {
          return Some(idx)
        }
      }
    }
    idx += 1
  }
  None
}

///|
fn if_bool_literal(text : String) -> Bool? {
  let trimmed = strip_if_outer_parens(text)
  if trimmed == "true" {
    Some(true)
  } else if trimmed == "false" {
    Some(false)
  } else {
    None
  }
}

///|
fn if_quoted_string(text : String) -> String? {
  let trimmed = strip_if_outer_parens(text)
  if trimmed.length() >= 2 &&
    (
      (
        trimmed.unsafe_get(0) == '\'' &&
        trimmed.unsafe_get(trimmed.length() - 1) == '\''
      ) ||
      (
        trimmed.unsafe_get(0) == '"' &&
        trimmed.unsafe_get(trimmed.length() - 1) == '"'
      )
    ) {
    Some(if_text_slice(trimmed, 1, trimmed.length() - 1))
  } else {
    None
  }
}

///|
fn if_step_output_name(expression : String) -> (String, String)? {
  let trimmed = strip_if_outer_parens(expression)
  guard trimmed.has_prefix("steps.") && trimmed.length() > 6 else {
    return None
  }
  guard find_if_substring(trimmed, ".outputs.", 6) is Some(outputs_idx) else {
    return None
  }
  let step_id = if_text_slice(trimmed, 6, outputs_idx)
  let output_name = if_text_slice(trimmed, outputs_idx + 9, trimmed.length())
  if step_id.length() == 0 || output_name.length() == 0 {
    None
  } else {
    Some((step_id, output_name))
  }
}

///|
fn if_step_status_name(expression : String, suffix : String) -> String? {
  let trimmed = strip_if_outer_parens(expression)
  guard trimmed.has_prefix("steps.") && trimmed.has_suffix(suffix) else {
    return None
  }
  let step_id = if_text_slice(trimmed, 6, trimmed.length() - suffix.length())
  if step_id.length() == 0 {
    None
  } else {
    Some(step_id)
  }
}

///|
fn if_needs_output_name(expression : String) -> (String, String)? {
  let trimmed = strip_if_outer_parens(expression)
  guard trimmed.has_prefix("needs.") && trimmed.length() > 6 else {
    return None
  }
  guard find_if_substring(trimmed, ".outputs.", 6) is Some(outputs_idx) else {
    return None
  }
  let job_id = if_text_slice(trimmed, 6, outputs_idx)
  let output_name = if_text_slice(trimmed, outputs_idx + 9, trimmed.length())
  if job_id.length() == 0 || output_name.length() == 0 {
    None
  } else {
    Some((job_id, output_name))
  }
}

///|
fn if_needs_result_name(expression : String) -> String? {
  let trimmed = strip_if_outer_parens(expression)
  guard trimmed.has_prefix("needs.") && trimmed.has_suffix(".result") else {
    return None
  }
  let job_id = if_text_slice(trimmed, 6, trimmed.length() - 7)
  if job_id.length() == 0 {
    None
  } else {
    Some(job_id)
  }
}

///|
fn if_env_name(expression : String) -> String? {
  let trimmed = strip_if_outer_parens(expression)
  if trimmed.has_prefix("env.") && trimmed.length() > 4 {
    Some(if_text_slice(trimmed, 4, trimmed.length()))
  } else {
    None
  }
}

///|
fn if_vars_name(expression : String) -> String? {
  let trimmed = strip_if_outer_parens(expression)
  if trimmed.has_prefix("vars.") && trimmed.length() > 5 {
    Some(if_text_slice(trimmed, 5, trimmed.length()))
  } else {
    None
  }
}

///|
fn if_job_name(expression : String) -> String? {
  let trimmed = strip_if_outer_parens(expression)
  if trimmed.has_prefix("job.") && trimmed.length() > 4 {
    Some(if_text_slice(trimmed, 4, trimmed.length()))
  } else {
    None
  }
}

///|
fn if_secrets_name(expression : String) -> String? {
  let trimmed = strip_if_outer_parens(expression)
  if trimmed.has_prefix("secrets.") && trimmed.length() > 8 {
    Some(if_text_slice(trimmed, 8, trimmed.length()))
  } else {
    None
  }
}

///|
fn if_github_name(expression : String) -> String? {
  let trimmed = strip_if_outer_parens(expression)
  if trimmed.has_prefix("github.") && trimmed.length() > 7 {
    Some(if_text_slice(trimmed, 7, trimmed.length()))
  } else {
    None
  }
}

///|
fn if_runner_name(expression : String) -> String? {
  let trimmed = strip_if_outer_parens(expression)
  if trimmed.has_prefix("runner.") && trimmed.length() > 7 {
    Some(if_text_slice(trimmed, 7, trimmed.length()))
  } else {
    None
  }
}

///|
fn split_top_level_if_arguments(text : String) -> Array[String]? {
  let args : Array[String] = []
  let mut depth = 0
  let mut in_single = false
  let mut in_double = false
  let mut start = 0
  let mut idx = 0
  while idx < text.length() {
    let ch = text.unsafe_get(idx)
    if ch == '\'' && !in_double {
      in_single = !in_single
      idx += 1
      continue
    }
    if ch == '"' && !in_single {
      in_double = !in_double
      idx += 1
      continue
    }
    if !in_single && !in_double {
      if ch == '(' {
        depth += 1
      } else if ch == ')' {
        if depth == 0 {
          return None
        }
        depth -= 1
      } else if ch == ',' && depth == 0 {
        args.push(
          if_text_slice(text, start, idx).trim(chars=" \t\n\r").to_owned(),
        )
        start = idx + 1
      }
    }
    idx += 1
  }
  if depth != 0 || in_single || in_double {
    return None
  }
  args.push(
    if_text_slice(text, start, text.length()).trim(chars=" \t\n\r").to_owned(),
  )
  Some(args)
}

///|
fn if_function_arguments(
  expression : String,
  function_name : String,
  arity : Int,
) -> Array[String]? {
  let trimmed = strip_if_outer_parens(expression)
  let lower = trimmed.to_lower()
  let prefix = function_name.to_lower() + "("
  guard lower.has_prefix(prefix) && trimmed.has_suffix(")") else { return None }
  let inner = if_text_slice(trimmed, prefix.length(), trimmed.length() - 1)
  guard split_top_level_if_arguments(inner) is Some(args) else { return None }
  if args.length() != arity {
    None
  } else {
    Some(args)
  }
}

///|
fn[V] if_map_get_case_insensitive(values : Map[String, V], key : String) -> V? {
  match values.get(key) {
    Some(value) => Some(value)
    None => {
      let needle = key.to_lower()
      for existing_key, value in values {
        if existing_key.to_lower() == needle {
          return Some(value)
        }
      }
      None
    }
  }
}

///|
fn resolve_if_step_output(
  step_outputs : Map[String, Map[String, String]],
  step_id : String,
  output_name : String,
) -> String {
  match if_map_get_case_insensitive(step_outputs, step_id) {
    Some(outputs) =>
      if_map_get_case_insensitive(outputs, output_name).unwrap_or("")
    None => ""
  }
}

///|
fn resolve_if_step_status(
  values : Map[String, String],
  step_id : String,
) -> String {
  if_map_get_case_insensitive(values, step_id).unwrap_or("")
}

///|
fn resolve_if_needs_output(
  needs_outputs : Map[String, Map[String, String]],
  job_id : String,
  output_name : String,
) -> String {
  match if_map_get_case_insensitive(needs_outputs, job_id) {
    Some(outputs) =>
      if_map_get_case_insensitive(outputs, output_name).unwrap_or("")
    None => ""
  }
}

///|
fn resolve_if_needs_result(
  needs_results : Map[String, String],
  job_id : String,
) -> String {
  if_map_get_case_insensitive(needs_results, job_id).unwrap_or("")
}

///|
fn condition_expression_has_status_check(text : String) -> Bool {
  let trimmed = unwrap_if_expression(text)
  trimmed.contains("success()") ||
  trimmed.contains("always()") ||
  trimmed.contains("failure()") ||
  trimmed.contains("cancelled()")
}

///|
fn if_expression_function_supported(text : String) -> Bool {
  match if_function_arguments(text, "contains", 2) {
    Some(args) =>
      return if_condition_supported(args[0]) && if_condition_supported(args[1])
    None => ()
  }
  match if_function_arguments(text, "startsWith", 2) {
    Some(args) =>
      return if_condition_supported(args[0]) && if_condition_supported(args[1])
    None => ()
  }
  match if_function_arguments(text, "endsWith", 2) {
    Some(args) =>
      return if_condition_supported(args[0]) && if_condition_supported(args[1])
    None => ()
  }
  match if_function_arguments(text, "fromJSON", 1) {
    Some(args) => return if_condition_supported(args[0])
    None => ()
  }
  match if_function_arguments(text, "toJSON", 1) {
    Some(args) => return if_condition_supported(args[0])
    None => ()
  }
  if if_hash_files_function_supported(text) {
    return true
  }
  match if_function_arguments_at_least(text, "format", 1) {
    Some(args) => {
      for arg in args {
        if !if_condition_supported(arg) {
          return false
        }
      }
      return true
    }
    None => ()
  }
  match if_function_arguments_at_least(text, "join", 1) {
    Some(args) => {
      for arg in args {
        if !if_condition_supported(arg) {
          return false
        }
      }
      return true
    }
    None => ()
  }
  match if_function_arguments_at_least(text, "case", 1) {
    Some(args) => {
      for arg in args {
        if !if_condition_supported(arg) {
          return false
        }
      }
      return true
    }
    None => ()
  }
  false
}

///|
fn if_number_literal(text : String) -> Bool {
  let trimmed = text.trim(chars=" \t\n\r").to_owned()
  if trimmed.length() == 0 {
    return false
  }
  let mut i = 0
  if trimmed.unsafe_get(i).to_int() == '-'.to_int() ||
    trimmed.unsafe_get(i).to_int() == '+'.to_int() {
    i += 1
  }
  if i >= trimmed.length() {
    return false
  }
  let mut has_digit = false
  let mut has_dot = false
  while i < trimmed.length() {
    let ch = trimmed.unsafe_get(i).to_int()
    if ch >= '0'.to_int() && ch <= '9'.to_int() {
      has_digit = true
    } else if ch == '.'.to_int() && !has_dot {
      has_dot = true
    } else {
      return false
    }
    i += 1
  }
  has_digit
}

///|
fn if_condition_atom_supported(text : String) -> Bool {
  if if_bool_literal(text) is Some(_) {
    return true
  }
  if if_quoted_string(text) is Some(_) {
    return true
  }
  if if_number_literal(text) {
    return true
  }
  if if_expression_function_supported(text) {
    return true
  }
  let trimmed = strip_if_outer_parens(text)
  if trimmed == "success()" ||
    trimmed == "always()" ||
    trimmed == "failure()" ||
    trimmed == "cancelled()" {
    return true
  }
  if if_step_output_name(trimmed) is Some(_) {
    return true
  }
  if if_step_status_name(trimmed, ".outcome") is Some(_) {
    return true
  }
  if if_step_status_name(trimmed, ".conclusion") is Some(_) {
    return true
  }
  if if_needs_output_name(trimmed) is Some(_) {
    return true
  }
  if if_needs_result_name(trimmed) is Some(_) {
    return true
  }
  if if_env_name(trimmed) is Some(_) {
    return true
  }
  if if_job_name(trimmed) is Some(_) {
    return true
  }
  if if_vars_name(trimmed) is Some(_) {
    return true
  }
  if if_secrets_name(trimmed) is Some(_) {
    return true
  }
  if if_github_name(trimmed) is Some(_) {
    return true
  }
  if if_runner_name(trimmed) is Some(_) {
    return true
  }
  false
}

///|
fn if_condition_supported(text : String) -> Bool {
  let trimmed = strip_if_outer_parens(text)
  if trimmed.length() == 0 {
    return true
  }
  match find_top_level_if_operator(trimmed, "||") {
    Some(idx) =>
      return if_condition_supported(if_text_slice(trimmed, 0, idx)) &&
        if_condition_supported(
          if_text_slice(trimmed, idx + 2, trimmed.length()),
        )
    None => ()
  }
  match find_top_level_if_operator(trimmed, "&&") {
    Some(idx) =>
      return if_condition_supported(if_text_slice(trimmed, 0, idx)) &&
        if_condition_supported(
          if_text_slice(trimmed, idx + 2, trimmed.length()),
        )
    None => ()
  }
  match find_top_level_if_operator(trimmed, "!=") {
    Some(idx) =>
      return if_condition_supported(if_text_slice(trimmed, 0, idx)) &&
        if_condition_supported(
          if_text_slice(trimmed, idx + 2, trimmed.length()),
        )
    None => ()
  }
  match find_top_level_if_operator(trimmed, "==") {
    Some(idx) =>
      return if_condition_supported(if_text_slice(trimmed, 0, idx)) &&
        if_condition_supported(
          if_text_slice(trimmed, idx + 2, trimmed.length()),
        )
    None => ()
  }
  match find_top_level_if_operator(trimmed, ">=") {
    Some(idx) =>
      return if_condition_supported(if_text_slice(trimmed, 0, idx)) &&
        if_condition_supported(
          if_text_slice(trimmed, idx + 2, trimmed.length()),
        )
    None => ()
  }
  match find_top_level_if_operator(trimmed, "<=") {
    Some(idx) =>
      return if_condition_supported(if_text_slice(trimmed, 0, idx)) &&
        if_condition_supported(
          if_text_slice(trimmed, idx + 2, trimmed.length()),
        )
    None => ()
  }
  match find_top_level_if_operator(trimmed, ">") {
    Some(idx) =>
      return if_condition_supported(if_text_slice(trimmed, 0, idx)) &&
        if_condition_supported(
          if_text_slice(trimmed, idx + 1, trimmed.length()),
        )
    None => ()
  }
  match find_top_level_if_operator(trimmed, "<") {
    Some(idx) =>
      return if_condition_supported(if_text_slice(trimmed, 0, idx)) &&
        if_condition_supported(
          if_text_slice(trimmed, idx + 1, trimmed.length()),
        )
    None => ()
  }
  if trimmed.has_prefix("!") {
    return if_condition_supported(if_text_slice(trimmed, 1, trimmed.length()))
  }
  if_condition_atom_supported(trimmed)
}

///|
fn if_condition_value_truthy(value : ConditionValue) -> Bool {
  match value {
    JsonValue(json) =>
      match json {
        False => false
        Null => false
        String(text) => text.length() > 0
        Number(value, repr=_) => value != 0.0
        _ => true
      }
  }
}

///|
fn if_condition_value_eq(left : ConditionValue, right : ConditionValue) -> Bool {
  match left {
    JsonValue(left_json) =>
      match right {
        JsonValue(right_json) => {
          if Json::equal(left_json, right_json) {
            return true
          }
          match left_json {
            Array(_) => false
            Object(_) => false
            _ =>
              match right_json {
                Array(_) => false
                Object(_) => false
                _ =>
                  if_condition_value_to_string(left) ==
                  if_condition_value_to_string(right)
              }
          }
        }
      }
  }
}

///|
fn if_condition_value_to_string(value : ConditionValue) -> String {
  match value {
    JsonValue(json) =>
      match json {
        True => "true"
        False => "false"
        Null => ""
        String(text) => text
        Number(value, repr~) =>
          match repr {
            Some(text) => text
            None => value.to_string()
          }
        Array(_) => Json::stringify(json)
        Object(_) => Json::stringify(json)
      }
  }
}

///|
fn if_condition_value_json(value : ConditionValue) -> Json {
  match value {
    JsonValue(json) => json
  }
}

///|
fn if_condition_value_from_json(json : Json) -> ConditionValue {
  JsonValue(json)
}

///|
fn if_expression_function_value(
  text : String,
  ctx : IfConditionContext,
) -> ConditionValue? {
  match if_function_arguments(text, "contains", 2) {
    Some(args) => {
      let haystack = if_condition_atom_value(args[0], ctx)
      let needle = if_condition_atom_value(args[1], ctx)
      let matched = match if_condition_value_json(haystack) {
        Array(items) => {
          let mut found = false
          for item in items {
            if if_condition_value_eq(if_condition_value_from_json(item), needle) {
              found = true
              break
            }
          }
          found
        }
        _ =>
          if_condition_value_to_string(haystack).contains(
            if_condition_value_to_string(needle),
          )
      }
      return Some(if_condition_value_from_json(Json::boolean(matched)))
    }
    None => ()
  }
  match if_function_arguments(text, "startsWith", 2) {
    Some(args) => {
      let text_value = if_condition_value_to_string(
        if_condition_atom_value(args[0], ctx),
      )
      let prefix = if_condition_value_to_string(
        if_condition_atom_value(args[1], ctx),
      )
      return Some(
        if_condition_value_from_json(
          Json::boolean(text_value.has_prefix(prefix)),
        ),
      )
    }
    None => ()
  }
  match if_function_arguments(text, "endsWith", 2) {
    Some(args) => {
      let text_value = if_condition_value_to_string(
        if_condition_atom_value(args[0], ctx),
      )
      let suffix = if_condition_value_to_string(
        if_condition_atom_value(args[1], ctx),
      )
      return Some(
        if_condition_value_from_json(
          Json::boolean(text_value.has_suffix(suffix)),
        ),
      )
    }
    None => ()
  }
  match if_function_arguments(text, "fromJSON", 1) {
    Some(args) => {
      let source = if_condition_value_to_string(
        if_condition_atom_value(args[0], ctx),
      )
      let json = @json.parse(source) catch { _ => Json::null() }
      return Some(if_condition_value_from_json(json))
    }
    None => ()
  }
  match if_function_arguments(text, "toJSON", 1) {
    Some(args) => {
      let value = if_condition_atom_value(args[0], ctx)
      let json_text = Json::stringify(if_condition_value_json(value), indent=2)
      return Some(if_condition_value_from_json(Json::string(json_text)))
    }
    None => ()
  }
  match if_hash_files_function_value(text, ctx) {
    Some(value) => return Some(value)
    None => ()
  }
  match if_function_arguments_at_least(text, "format", 1) {
    Some(args) => {
      let fmt_str = if_condition_value_to_string(
        if_condition_atom_value(args[0], ctx),
      )
      let mut result = fmt_str
      let mut i = 1
      while i < args.length() {
        let placeholder = "{" + (i - 1).to_string() + "}"
        let replacement = if_condition_value_to_string(
          if_condition_atom_value(args[i], ctx),
        )
        let mut new_result = ""
        let mut search_from = 0
        while true {
          match find_if_substring(result, placeholder, search_from) {
            Some(pos) => {
              new_result = new_result +
                if_text_slice(result, search_from, pos) +
                replacement
              search_from = pos + placeholder.length()
            }
            None => {
              new_result = new_result +
                if_text_slice(result, search_from, result.length())
              break
            }
          }
        }
        result = new_result
        i += 1
      }
      return Some(if_condition_value_from_json(Json::string(result)))
    }
    None => ()
  }
  match if_function_arguments_at_least(text, "join", 1) {
    Some(args) => {
      let value = if_condition_atom_value(args[0], ctx)
      let separator = if args.length() >= 2 {
        if_condition_value_to_string(if_condition_atom_value(args[1], ctx))
      } else {
        ","
      }
      match if_condition_value_json(value) {
        Array(items) => {
          let parts : Array[String] = []
          for item in items {
            parts.push(
              if_condition_value_to_string(if_condition_value_from_json(item)),
            )
          }
          return Some(
            if_condition_value_from_json(Json::string(parts.join(separator))),
          )
        }
        _ => {
          let str_val = if_condition_value_to_string(value)
          return Some(if_condition_value_from_json(Json::string(str_val)))
        }
      }
    }
    None => ()
  }
  match if_function_arguments_at_least(text, "case", 1) {
    Some(args) => {
      let has_default = args.length() % 2 == 1
      let pair_count = args.length() / 2
      let mut i = 0
      while i < pair_count {
        let pred = if_condition_atom_value(args[i * 2], ctx)
        if if_condition_value_truthy(pred) {
          return Some(if_condition_atom_value(args[i * 2 + 1], ctx))
        }
        i += 1
      }
      if has_default {
        return Some(if_condition_atom_value(args[args.length() - 1], ctx))
      }
      return Some(if_condition_value_from_json(Json::string("")))
    }
    None => ()
  }
  None
}

///|
fn if_condition_atom_value(
  text : String,
  ctx : IfConditionContext,
) -> ConditionValue {
  match if_bool_literal(text) {
    Some(value) => return if_condition_value_from_json(Json::boolean(value))
    None => ()
  }
  match if_quoted_string(text) {
    Some(value) => return if_condition_value_from_json(Json::string(value))
    None => ()
  }
  if if_number_literal(text) {
    let trimmed = text.trim(chars=" \t\n\r").to_owned()
    return if_condition_value_from_json(Json::string(trimmed))
  }
  match if_expression_function_value(text, ctx) {
    Some(value) => return value
    None => ()
  }
  let trimmed = strip_if_outer_parens(text)
  if trimmed == "success()" {
    return if_condition_value_from_json(Json::boolean(ctx.success_value))
  }
  if trimmed == "always()" {
    return if_condition_value_from_json(Json::boolean(true))
  }
  if trimmed == "failure()" {
    return if_condition_value_from_json(Json::boolean(ctx.failure_value))
  }
  if trimmed == "cancelled()" {
    return if_condition_value_from_json(Json::boolean(ctx.cancelled_value))
  }
  match if_step_output_name(trimmed) {
    Some((step_id, output_name)) =>
      return if_condition_value_from_json(
        Json::string(
          resolve_if_step_output(ctx.step_outputs, step_id, output_name),
        ),
      )
    None => ()
  }
  match if_step_status_name(trimmed, ".outcome") {
    Some(step_id) =>
      return if_condition_value_from_json(
        Json::string(resolve_if_step_status(ctx.step_outcomes, step_id)),
      )
    None => ()
  }
  match if_step_status_name(trimmed, ".conclusion") {
    Some(step_id) =>
      return if_condition_value_from_json(
        Json::string(resolve_if_step_status(ctx.step_conclusions, step_id)),
      )
    None => ()
  }
  match if_needs_output_name(trimmed) {
    Some((job_id, output_name)) =>
      return if_condition_value_from_json(
        Json::string(
          resolve_if_needs_output(ctx.needs_outputs, job_id, output_name),
        ),
      )
    None => ()
  }
  match if_needs_result_name(trimmed) {
    Some(job_id) =>
      return if_condition_value_from_json(
        Json::string(resolve_if_needs_result(ctx.needs_results, job_id)),
      )
    None => ()
  }
  match if_env_name(trimmed) {
    Some(name) =>
      return if_condition_value_from_json(
        Json::string(
          if_map_get_case_insensitive(ctx.env_context, name).unwrap_or(""),
        ),
      )
    None => ()
  }
  match if_job_name(trimmed) {
    Some(name) =>
      return if_condition_value_from_json(
        Json::string(
          if_map_get_case_insensitive(ctx.job_context, name).unwrap_or(""),
        ),
      )
    None => ()
  }
  match if_vars_name(trimmed) {
    Some(name) =>
      return if_condition_value_from_json(
        Json::string(
          if_map_get_case_insensitive(ctx.vars_context, name).unwrap_or(""),
        ),
      )
    None => ()
  }
  match if_secrets_name(trimmed) {
    Some(name) =>
      return if_condition_value_from_json(
        Json::string(
          if_map_get_case_insensitive(ctx.secrets_context, name).unwrap_or(""),
        ),
      )
    None => ()
  }
  match if_github_name(trimmed) {
    Some(name) =>
      return if_condition_value_from_json(
        Json::string(
          if_map_get_case_insensitive(ctx.github_context, name).unwrap_or(""),
        ),
      )
    None => ()
  }
  match if_runner_name(trimmed) {
    Some(name) =>
      return if_condition_value_from_json(
        Json::string(
          if_map_get_case_insensitive(ctx.runner_context, name).unwrap_or(""),
        ),
      )
    None => ()
  }
  if_condition_value_from_json(Json::string(""))
}

///|
fn evaluate_string_function_expression(
  text : String,
  ctx : IfConditionContext,
) -> String? {
  match if_expression_function_value(text, ctx) {
    Some(value) => Some(if_condition_value_to_string(value))
    None => None
  }
}

///|
#warnings("-deprecated")
fn try_parse_int64(s : String) -> Int64? {
  let v = @strconv.parse_int64(s) catch { _ => return None }
  Some(v)
}

///|
#warnings("-deprecated")
fn try_parse_double(s : String) -> Double? {
  let v = @strconv.parse_double(s) catch { _ => return None }
  Some(v)
}

///|
fn compare_condition_values(
  left : ConditionValue,
  right : ConditionValue,
) -> Int {
  let left_str = if_condition_value_to_string(left)
  let right_str = if_condition_value_to_string(right)
  match (try_parse_int64(left_str), try_parse_int64(right_str)) {
    (Some(l), Some(r)) => if l < r { -1 } else if l > r { 1 } else { 0 }
    _ =>
      match (try_parse_double(left_str), try_parse_double(right_str)) {
        (Some(l), Some(r)) => if l < r { -1 } else if l > r { 1 } else { 0 }
        _ =>
          if left_str < right_str {
            -1
          } else if left_str > right_str {
            1
          } else {
            0
          }
      }
  }
}

///|
fn eval_if_condition_bool(text : String, ctx : IfConditionContext) -> Bool {
  let trimmed = strip_if_outer_parens(text)
  if trimmed.length() == 0 {
    return true
  }
  match find_top_level_if_operator(trimmed, "||") {
    Some(idx) =>
      return eval_if_condition_bool(if_text_slice(trimmed, 0, idx), ctx) ||
        eval_if_condition_bool(
          if_text_slice(trimmed, idx + 2, trimmed.length()),
          ctx,
        )
    None => ()
  }
  match find_top_level_if_operator(trimmed, "&&") {
    Some(idx) =>
      return eval_if_condition_bool(if_text_slice(trimmed, 0, idx), ctx) &&
        eval_if_condition_bool(
          if_text_slice(trimmed, idx + 2, trimmed.length()),
          ctx,
        )
    None => ()
  }
  match find_top_level_if_operator(trimmed, "!=") {
    Some(idx) =>
      return !if_condition_value_eq(
        if_condition_atom_value(if_text_slice(trimmed, 0, idx), ctx),
        if_condition_atom_value(
          if_text_slice(trimmed, idx + 2, trimmed.length()),
          ctx,
        ),
      )
    None => ()
  }
  match find_top_level_if_operator(trimmed, "==") {
    Some(idx) =>
      return if_condition_value_eq(
        if_condition_atom_value(if_text_slice(trimmed, 0, idx), ctx),
        if_condition_atom_value(
          if_text_slice(trimmed, idx + 2, trimmed.length()),
          ctx,
        ),
      )
    None => ()
  }
  match find_top_level_if_operator(trimmed, ">=") {
    Some(idx) => {
      let left = if_condition_atom_value(if_text_slice(trimmed, 0, idx), ctx)
      let right = if_condition_atom_value(
        if_text_slice(trimmed, idx + 2, trimmed.length()),
        ctx,
      )
      return compare_condition_values(left, right) >= 0
    }
    None => ()
  }
  match find_top_level_if_operator(trimmed, "<=") {
    Some(idx) => {
      let left = if_condition_atom_value(if_text_slice(trimmed, 0, idx), ctx)
      let right = if_condition_atom_value(
        if_text_slice(trimmed, idx + 2, trimmed.length()),
        ctx,
      )
      return compare_condition_values(left, right) <= 0
    }
    None => ()
  }
  match find_top_level_if_operator(trimmed, ">") {
    Some(idx) => {
      let left = if_condition_atom_value(if_text_slice(trimmed, 0, idx), ctx)
      let right = if_condition_atom_value(
        if_text_slice(trimmed, idx + 1, trimmed.length()),
        ctx,
      )
      return compare_condition_values(left, right) > 0
    }
    None => ()
  }
  match find_top_level_if_operator(trimmed, "<") {
    Some(idx) => {
      let left = if_condition_atom_value(if_text_slice(trimmed, 0, idx), ctx)
      let right = if_condition_atom_value(
        if_text_slice(trimmed, idx + 1, trimmed.length()),
        ctx,
      )
      return compare_condition_values(left, right) < 0
    }
    None => ()
  }
  if trimmed.has_prefix("!") {
    return !eval_if_condition_bool(
      if_text_slice(trimmed, 1, trimmed.length()),
      ctx,
    )
  }
  if_condition_value_truthy(if_condition_atom_value(trimmed, ctx))
}

///|
fn evaluate_if_condition(text : String, ctx : IfConditionContext) -> Bool {
  let trimmed = unwrap_if_expression(text)
  if trimmed.length() == 0 {
    return ctx.success_value
  }
  if condition_expression_has_status_check(trimmed) {
    eval_if_condition_bool(trimmed, ctx)
  } else {
    ctx.success_value && eval_if_condition_bool(trimmed, ctx)
  }
}

///|
fn evaluate_boolean_condition(text : String, ctx : IfConditionContext) -> Bool {
  let trimmed = unwrap_if_expression(text)
  if trimmed.length() == 0 {
    return false
  }
  eval_if_condition_bool(trimmed, ctx)
}