///|

///|
fn parse_bool_text(text : String) -> Bool? {
  let trimmed = trim_trailing_semicolons(text)
  if trimmed == "true" {
    return Some(true)
  }
  if trimmed == "false" {
    return Some(false)
  }
  None
}

///|
fn check_if_operator_scope(
  shader_defs : Map[String, ShaderDefValue],
  expression : String,
  scope : Scope?,
  offset : Int,
  is_else : Bool,
) -> (Bool, String?) raise PreprocessError {
  guard parse_wgsl_shader_def_comparison(expression) is Some(comparison) else {
    return (false, None)
  }
  let def = comparison.def_name()
  if scope is None {
    return (true, Some(def))
  }
  let condition = eval_if_condition(
    shader_defs,
    def,
    comparison.operator(),
    comparison.value_text(),
    offset,
  )
  guard scope is Some(scope_state) else { abort("unreachable missing scope") }
  scope_state.branch(is_else, condition, offset)
  (true, Some(def))
}

///|
fn compare_bool_values(
  lhs : Bool,
  rhs : Bool,
  operator : WgslShaderDefOperator,
  offset : Int,
) -> Bool raise PreprocessError {
  match operator {
    Equal => lhs == rhs
    NotEqual => lhs != rhs
    Greater => lhs > rhs
    GreaterEqual => lhs >= rhs
    Less => lhs < rhs
    LessEqual => lhs <= rhs
    Unknown(text) => raise UnknownShaderDefOperator(text, offset)
  }
}

///|
fn compare_int_values(
  lhs : Int,
  rhs : Int,
  operator : WgslShaderDefOperator,
  offset : Int,
) -> Bool raise PreprocessError {
  match operator {
    Equal => lhs == rhs
    NotEqual => lhs != rhs
    Greater => lhs > rhs
    GreaterEqual => lhs >= rhs
    Less => lhs < rhs
    LessEqual => lhs <= rhs
    Unknown(text) => raise UnknownShaderDefOperator(text, offset)
  }
}

///|
fn compare_uint_values(
  lhs : UInt,
  rhs : UInt,
  operator : WgslShaderDefOperator,
  offset : Int,
) -> Bool raise PreprocessError {
  match operator {
    Equal => lhs == rhs
    NotEqual => lhs != rhs
    Greater => lhs > rhs
    GreaterEqual => lhs >= rhs
    Less => lhs < rhs
    LessEqual => lhs <= rhs
    Unknown(text) => raise UnknownShaderDefOperator(text, offset)
  }
}

///|
fn eval_if_condition(
  shader_defs : Map[String, ShaderDefValue],
  def : String,
  operator : WgslShaderDefOperator,
  value : String,
  offset : Int,
) -> Bool raise PreprocessError {
  guard shader_defs.get(def) is Some(def_value) else {
    raise UnknownShaderDef(def, offset)
  }
  match def_value {
    Bool(bool_value) =>
      match parse_bool_text(value) {
        Some(expected) =>
          compare_bool_values(bool_value, expected, operator, offset)
        None =>
          raise InvalidShaderDefComparisonValue(def, value, "bool", offset)
      }
    Int(int_value) => {
      let parsed = @string.parse_int(value[:], base=10) catch {
        _ => raise InvalidShaderDefComparisonValue(def, value, "int", offset)
      }
      compare_int_values(int_value, parsed, operator, offset)
    }
    UInt(uint_value) => {
      let parsed = @string.parse_uint(value[:], base=10) catch {
        _ => raise InvalidShaderDefComparisonValue(def, value, "uint", offset)
      }
      compare_uint_values(uint_value, parsed, operator, offset)
    }
    Raw(_) =>
      raise InvalidShaderDefComparisonValue(def, value, "bool/int/uint", offset)
  }
}

///|
fn replace_shader_defs_in_line(
  line : String,
  shader_defs : Map[String, ShaderDefValue],
) -> String {
  let references = parse_wgsl_shader_def_references(line)
  let mut output = line
  for reference in references {
    if !reference.is_delimited() &&
      shader_defs.get(reference.name()) is Some(value) {
      output = replace_first_shader_def_reference(
        output,
        false,
        value.value_as_string(),
      )
    }
  }
  for reference in references {
    if reference.is_delimited() &&
      shader_defs.get(reference.name()) is Some(value) {
      output = replace_first_shader_def_reference(
        output,
        true,
        value.value_as_string(),
      )
    }
  }
  output
}

///|
fn replace_first_shader_def_reference(
  line : String,
  delimited : Bool,
  replacement : String,
) -> String {
  for reference in parse_wgsl_shader_def_references(line) {
    if reference.is_delimited() == delimited {
      return line[0:reference.start()].to_owned() +
        replacement +
        line[reference.end():line.length()].to_owned()
    }
  }
  line
}

///|