// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
fn const_eval_clamp_type(
  values : Array[ConstEvalValue],
) -> ConstEvalType raise WeslCompileError {
  let mut has_f32 = false
  let mut has_abstract_float = false
  let mut has_i32 = false
  let mut has_u32 = false
  for value in values {
    match value.value_type() {
      Void => raise Validation("cannot use void with clamp arguments")
      Bool => raise Validation("cannot use bool with clamp arguments")
      F32 => has_f32 = true
      AbstractFloat => has_abstract_float = true
      I32 => has_i32 = true
      U32 => has_u32 = true
      AbstractInt => ()
      FrexpAbstractResult | FrexpF32Result =>
        raise Validation("cannot use frexp result with clamp arguments")
      Vector(_, _) => raise Validation("cannot use vector with clamp arguments")
      Matrix(_, _, _) | Array(_, _) =>
        raise Validation("cannot use aggregate with clamp arguments")
      Struct(_) => raise Validation("cannot use struct with clamp arguments")
    }
  }
  if has_u32 && has_i32 {
    raise Validation("ambiguous clamp argument types")
  }
  if has_f32 {
    return F32
  }
  if has_abstract_float {
    return F32
  }
  if has_u32 {
    return U32
  }
  I32
}

///|
fn const_eval_numeric_join_type(
  values : Array[ConstEvalValue],
  context : String,
) -> ConstEvalType raise WeslCompileError {
  let mut has_f32 = false
  let mut has_abstract_float = false
  let mut has_i32 = false
  let mut has_u32 = false
  for value in values {
    match value.value_type() {
      Void => raise Validation("cannot use void with \{context} arguments")
      Bool => raise Validation("cannot use bool with \{context} arguments")
      F32 => has_f32 = true
      AbstractFloat => has_abstract_float = true
      I32 => has_i32 = true
      U32 => has_u32 = true
      AbstractInt => ()
      FrexpAbstractResult | FrexpF32Result =>
        raise Validation("cannot use frexp result with \{context} arguments")
      Vector(_, _) =>
        raise Validation("cannot use vector with \{context} arguments")
      Matrix(_, _, _) | Array(_, _) =>
        raise Validation("cannot use aggregate with \{context} arguments")
      Struct(_) =>
        raise Validation("cannot use struct with \{context} arguments")
    }
  }
  if has_u32 && has_i32 {
    raise Validation("ambiguous \{context} argument types")
  }
  if has_f32 {
    return F32
  }
  if has_abstract_float {
    return AbstractFloat
  }
  if has_u32 {
    return U32
  }
  if has_i32 {
    return I32
  }
  AbstractInt
}

///|
fn const_eval_select_join_type(
  first : ConstEvalValue,
  second : ConstEvalValue,
) -> ConstEvalType raise WeslCompileError {
  match (first.value_type(), second.value_type()) {
    (Bool, Bool) => Bool
    (Bool, _) | (_, Bool) =>
      raise Validation("`select` 1st and 2nd arguments are incompatible")
    _ => const_eval_numeric_join_type([first, second], "`select` 1st and 2nd")
  }
}

///|
fn const_eval_builtin_vector_width(
  name : String,
  args : Array[ConstEvalValue],
) -> Int? raise WeslCompileError {
  const_eval_vector_width_for_values(args, "`\{name}`")
}

///|
fn const_eval_apply_clamp(
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 3 {
    raise Validation("clamp expects three arguments")
  }
  match const_eval_builtin_vector_width("clamp", args) {
    Some(width) => {
      let values : Array[ConstEvalValue] = []
      for index in 0.. ()
  }
  let target = const_eval_clamp_type(args)
  match target {
    F32 => {
      let value = const_eval_require_f32(args[0], "clamp")
      let min_value = const_eval_require_f32(args[1], "clamp")
      let max_value = const_eval_require_f32(args[2], "clamp")
      F32(Float::clamp(value, min=min_value, max=max_value))
    }
    U32 => {
      let value = const_eval_require_u32(args[0], "clamp")
      let min_value = const_eval_require_u32(args[1], "clamp")
      let max_value = const_eval_require_u32(args[2], "clamp")
      U32(Int64::clamp(value, min=min_value, max=max_value))
    }
    _ => {
      let value = const_eval_require_i32(args[0], "clamp")
      let min_value = const_eval_require_i32(args[1], "clamp")
      let max_value = const_eval_require_i32(args[2], "clamp")
      I32(Int64::clamp(value, min=min_value, max=max_value))
    }
  }
}

///|
fn const_eval_apply_saturate(
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 1 {
    let signature = const_eval_format_call_signature("saturate", args)
    raise Validation("invalid function call signature: `\{signature}`")
  }
  match const_eval_builtin_vector_width("saturate", args) {
    Some(width) => {
      let values : Array[ConstEvalValue] = []
      for index in 0.. ()
  }
  match args[0] {
    Bool(_) | I32(_) | U32(_) =>
      raise Validation("`clamp` arguments are incompatible")
    FrexpAbstract(_, _) | FrexpF32(_, _) =>
      raise Validation("`clamp` arguments are incompatible")
    Vector(_, _, _) => raise Validation("`clamp` arguments are incompatible")
    Matrix(_, _, _, _) | Array(_, _, _) =>
      raise Validation("`clamp` arguments are incompatible")
    Struct(_, _) => raise Validation("`clamp` arguments are incompatible")
    AbstractInt(number) =>
      AbstractFloat(Double::clamp(number.to_double(), min=0.0, max=1.0), true)
    AbstractFloat(number, _) =>
      AbstractFloat(Double::clamp(number, min=0.0, max=1.0), true)
    F32(number) =>
      F32(
        Float::clamp(
          number,
          min=Float::from_double(0.0),
          max=Float::from_double(1.0),
        ),
      )
  }
}

///|
fn const_eval_apply_min_or_max(
  name : String,
  args : Array[ConstEvalValue],
  choose_min : Bool,
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 2 {
    raise Validation("`\{name}` expects two arguments")
  }
  match const_eval_builtin_vector_width(name, args) {
    Some(width) => {
      let values : Array[ConstEvalValue] = []
      for index in 0.. ()
  }
  let target = const_eval_numeric_join_type(args, "`\{name}`")
  match target {
    Void => raise Validation("cannot use void with `\{name}` arguments")
    AbstractFloat => {
      let left = const_eval_to_abstract_float(args[0], name)
      let right = const_eval_to_abstract_float(args[1], name)
      AbstractFloat(
        if choose_min {
          if left <= right {
            left
          } else {
            right
          }
        } else if left >= right {
          left
        } else {
          right
        },
        true,
      )
    }
    F32 => {
      let left = const_eval_require_f32(args[0], name)
      let right = const_eval_require_f32(args[1], name)
      F32(
        if choose_min {
          if left <= right {
            left
          } else {
            right
          }
        } else if left >= right {
          left
        } else {
          right
        },
      )
    }
    U32 => {
      let left = const_eval_require_u32(args[0], name)
      let right = const_eval_require_u32(args[1], name)
      U32(
        if choose_min {
          if left <= right {
            left
          } else {
            right
          }
        } else if left >= right {
          left
        } else {
          right
        },
      )
    }
    I32 => {
      let left = const_eval_require_i32(args[0], name)
      let right = const_eval_require_i32(args[1], name)
      I32(
        if choose_min {
          if left <= right {
            left
          } else {
            right
          }
        } else if left >= right {
          left
        } else {
          right
        },
      )
    }
    AbstractInt => {
      let left = const_eval_require_abstract_int(args[0], name)
      let right = const_eval_require_abstract_int(args[1], name)
      AbstractInt(
        if choose_min {
          if left <= right {
            left
          } else {
            right
          }
        } else if left >= right {
          left
        } else {
          right
        },
      )
    }
    Bool => raise Validation("cannot use bool with `\{name}` arguments")
    FrexpAbstractResult | FrexpF32Result =>
      raise Validation("cannot use frexp result with `\{name}` arguments")
    Vector(_, _) =>
      raise Validation("cannot use vector with `\{name}` arguments")
    Matrix(_, _, _) | Array(_, _) =>
      raise Validation("cannot use aggregate with `\{name}` arguments")
    Struct(_) => raise Validation("cannot use struct with `\{name}` arguments")
  }
}

///|
fn const_eval_abs_i64(value : Int64) -> Int64 {
  if value < 0 {
    -value
  } else {
    value
  }
}

///|
fn const_eval_abs_double(value : Double) -> Double {
  if value < 0.0 {
    -value
  } else {
    value
  }
}

///|
fn const_eval_abs_float(value : Float) -> Float {
  if value < 0.0 {
    -value
  } else {
    value
  }
}

///|
fn const_eval_apply_abs(
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 1 {
    raise Validation("`abs` expects one argument")
  }
  match args[0] {
    Bool(_) =>
      raise Validation("`abs` expects a scalar or vector of scalar argument")
    AbstractInt(number) => AbstractInt(const_eval_abs_i64(number))
    I32(number) => I32(const_eval_abs_i64(number))
    U32(number) => U32(number)
    AbstractFloat(number, f32_lossless) =>
      AbstractFloat(const_eval_abs_double(number), f32_lossless)
    F32(number) => F32(const_eval_abs_float(number))
    FrexpAbstract(_, _) | FrexpF32(_, _) =>
      raise Validation("`abs` expects a scalar or vector of scalar argument")
    Matrix(_, _, _, _) | Array(_, _, _) =>
      raise Validation("`abs` expects a scalar or vector of scalar argument")
    Struct(_, _) =>
      raise Validation("`abs` expects a scalar or vector of scalar argument")
    Vector(width, _, elements) => {
      let values : Array[ConstEvalValue] = []
      for element in elements {
        values.push(const_eval_apply_abs([element]))
      }
      const_eval_vector_from_elements(width, values, "abs")
    }
  }
}

///|
fn const_eval_apply_select(
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 3 {
    raise Validation("`select` expects three arguments")
  }
  match const_eval_builtin_vector_width("select", args) {
    Some(width) => {
      let values : Array[ConstEvalValue] = []
      for index in 0.. ()
  }
  let condition = match args[2] {
    Bool(value) => value
    _ =>
      raise Validation(
        "`select` 3rd argument must be a boolean or vector of boolean",
      )
  }
  let target = const_eval_select_join_type(args[0], args[1])
  let chosen = if condition { args[1] } else { args[0] }
  const_eval_convert_to_type(chosen, target, "select")
}

///|
fn const_eval_apply_bool_builtin(
  name : String,
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 1 {
    raise Validation("`\{name}` expects one argument")
  }
  match args[0] {
    Vector(_, _, elements) => {
      let mut result = name == "all"
      for element in elements {
        let value = const_eval_require_bool(element, name)
        if name == "all" {
          result = result && value
        } else {
          result = result || value
        }
      }
      Bool(result)
    }
    _ => Bool(const_eval_require_bool(args[0], name))
  }
}

///|
fn const_eval_require_float_builtin_arg(
  name : String,
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 1 {
    raise Validation("`\{name}` expects one argument")
  }
  match args[0] {
    Bool(_)
    | I32(_)
    | U32(_)
    | FrexpAbstract(_, _)
    | FrexpF32(_, _)
    | Struct(_, _) =>
      raise Validation("`\{name}` expects a float or vector of float argument")
    value => value
  }
}

///|
fn const_eval_apply_float_unary_builtin(
  name : String,
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  let value = const_eval_require_float_builtin_arg(name, args)
  match value {
    Vector(width, _, elements) => {
      let values : Array[ConstEvalValue] = []
      for element in elements {
        values.push(const_eval_apply_float_unary_builtin(name, [element]))
      }
      const_eval_vector_from_elements(width, values, name)
    }
    Struct(_, _) =>
      raise Validation("`\{name}` expects a float or vector of float argument")
    Matrix(_, _, _, _) | Array(_, _, _) =>
      raise Validation("`\{name}` expects a float or vector of float argument")
    AbstractInt(number) =>
      AbstractFloat(
        match name {
          "floor" => number.to_double().floor()
          "ceil" => number.to_double().ceil()
          "round" => number.to_double().round()
          "trunc" => number.to_double().trunc()
          "fract" => number.to_double() - number.to_double().floor()
          "sqrt" => number.to_double().sqrt()
          "inverseSqrt" => 1.0 / number.to_double().sqrt()
          "exp" => @math.exp(number.to_double())
          "exp2" => @math.pow(2.0, number.to_double())
          "log" => @math.ln(number.to_double())
          "log2" => @math.log2(number.to_double())
          "sin" => @math.sin(number.to_double())
          "cos" => @math.cos(number.to_double())
          "tan" => @math.tan(number.to_double())
          "asin" => @math.asin(number.to_double())
          "acos" => @math.acos(number.to_double())
          "atan" => @math.atan(number.to_double())
          "sinh" => @math.sinh(number.to_double())
          "cosh" => @math.cosh(number.to_double())
          "tanh" => @math.tanh(number.to_double())
          "radians" => number.to_double() * @math.acos(-1.0) / 180.0
          "degrees" => number.to_double() * 180.0 / @math.acos(-1.0)
          _ => raise Validation("unsupported float builtin `\{name}`")
        },
        true,
      )
    AbstractFloat(number, _) =>
      AbstractFloat(
        match name {
          "floor" => number.floor()
          "ceil" => number.ceil()
          "round" => number.round()
          "trunc" => number.trunc()
          "fract" => number - number.floor()
          "sqrt" => number.sqrt()
          "inverseSqrt" => 1.0 / number.sqrt()
          "exp" => @math.exp(number)
          "exp2" => @math.pow(2.0, number)
          "log" => @math.ln(number)
          "log2" => @math.log2(number)
          "sin" => @math.sin(number)
          "cos" => @math.cos(number)
          "tan" => @math.tan(number)
          "asin" => @math.asin(number)
          "acos" => @math.acos(number)
          "atan" => @math.atan(number)
          "sinh" => @math.sinh(number)
          "cosh" => @math.cosh(number)
          "tanh" => @math.tanh(number)
          "radians" => number * @math.acos(-1.0) / 180.0
          "degrees" => number * 180.0 / @math.acos(-1.0)
          _ => raise Validation("unsupported float builtin `\{name}`")
        },
        true,
      )
    F32(number) =>
      F32(
        match name {
          "floor" => number.floor()
          "ceil" => number.ceil()
          "round" => number.round()
          "trunc" => number.trunc()
          "fract" => number - number.floor()
          "sqrt" => number.sqrt()
          "inverseSqrt" => Float::from_double(1.0) / number.sqrt()
          "exp" => Float::from_double(@math.exp(number.to_double()))
          "exp2" => Float::from_double(@math.pow(2.0, number.to_double()))
          "log" => Float::from_double(@math.ln(number.to_double()))
          "log2" => Float::from_double(@math.log2(number.to_double()))
          "sin" => Float::from_double(@math.sin(number.to_double()))
          "cos" => Float::from_double(@math.cos(number.to_double()))
          "tan" => Float::from_double(@math.tan(number.to_double()))
          "asin" => Float::from_double(@math.asin(number.to_double()))
          "acos" => Float::from_double(@math.acos(number.to_double()))
          "atan" => Float::from_double(@math.atan(number.to_double()))
          "sinh" => Float::from_double(@math.sinh(number.to_double()))
          "cosh" => Float::from_double(@math.cosh(number.to_double()))
          "tanh" => Float::from_double(@math.tanh(number.to_double()))
          "radians" =>
            Float::from_double(number.to_double() * @math.acos(-1.0) / 180.0)
          "degrees" =>
            Float::from_double(number.to_double() * 180.0 / @math.acos(-1.0))
          _ => raise Validation("unsupported float builtin `\{name}`")
        },
      )
    Bool(_) | I32(_) | U32(_) | FrexpAbstract(_, _) | FrexpF32(_, _) =>
      raise Validation("`\{name}` expects a float or vector of float argument")
  }
}

///|
fn const_eval_float_binary_type(
  name : String,
  args : Array[ConstEvalValue],
) -> ConstEvalType raise WeslCompileError {
  if args.length() != 2 {
    raise Validation("`\{name}` expects two arguments")
  }
  let mut has_f32 = false
  for arg in args {
    match arg {
      Bool(_)
      | I32(_)
      | U32(_)
      | FrexpAbstract(_, _)
      | FrexpF32(_, _)
      | Struct(_, _)
      | Matrix(_, _, _, _)
      | Array(_, _, _)
      | Vector(_, _, _) =>
        raise Validation(
          "`\{name}` expects a float or vector of float argument",
        )
      F32(_) => has_f32 = true
      AbstractInt(_) | AbstractFloat(_, _) => ()
    }
  }
  if has_f32 {
    F32
  } else {
    AbstractFloat
  }
}

///|
fn const_eval_apply_float_binary_builtin(
  name : String,
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  match const_eval_builtin_vector_width(name, args) {
    Some(width) => {
      let values : Array[ConstEvalValue] = []
      for index in 0.. ()
  }
  let target = const_eval_float_binary_type(name, args)
  match target {
    F32 => {
      let left = const_eval_require_f32(args[0], name)
      let right = const_eval_require_f32(args[1], name)
      F32(
        Float::from_double(
          match name {
            "pow" => @math.pow(left.to_double(), right.to_double())
            "atan2" => @math.atan2(left.to_double(), right.to_double())
            _ => raise Validation("unsupported float builtin `\{name}`")
          },
        ),
      )
    }
    _ => {
      let left = const_eval_to_abstract_float(args[0], name)
      let right = const_eval_to_abstract_float(args[1], name)
      AbstractFloat(
        match name {
          "pow" => @math.pow(left, right)
          "atan2" => @math.atan2(left, right)
          _ => raise Validation("unsupported float builtin `\{name}`")
        },
        true,
      )
    }
  }
}

///|
fn const_eval_ldexp_factor(exponent : Int64) -> Double {
  @math.pow(2.0, exponent.to_double())
}

///|
fn const_eval_apply_ldexp(
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 2 {
    let signature = const_eval_format_call_signature("ldexp", args)
    raise Validation("invalid function call signature: `\{signature}`")
  }
  let exponent = match args[1] {
    AbstractInt(number) => number
    I32(number) => number
    _ =>
      raise Validation(
        "`ldexp` with scalar arguments expects a float and a i32 arguments",
      )
  }
  match args[0] {
    Bool(_)
    | I32(_)
    | U32(_)
    | FrexpAbstract(_, _)
    | FrexpF32(_, _)
    | Struct(_, _)
    | Matrix(_, _, _, _)
    | Array(_, _, _)
    | Vector(_, _, _) =>
      raise Validation(
        "`ldexp` with scalar arguments expects a float and a i32 arguments",
      )
    F32(number) =>
      F32(
        Float::from_double(
          number.to_double() * const_eval_ldexp_factor(exponent),
        ),
      )
    AbstractInt(number) =>
      match args[1] {
        I32(_) =>
          F32(
            Float::from_double(
              number.to_double() * const_eval_ldexp_factor(exponent),
            ),
          )
        _ =>
          AbstractFloat(
            number.to_double() * const_eval_ldexp_factor(exponent),
            true,
          )
      }
    AbstractFloat(number, _) =>
      match args[1] {
        I32(_) =>
          F32(Float::from_double(number * const_eval_ldexp_factor(exponent)))
        _ => AbstractFloat(number * const_eval_ldexp_factor(exponent), true)
      }
  }
}

///|
fn const_eval_require_float_builtin_double(
  value : ConstEvalValue,
  name : String,
) -> Double raise WeslCompileError {
  match value {
    Bool(_)
    | I32(_)
    | U32(_)
    | FrexpAbstract(_, _)
    | FrexpF32(_, _)
    | Struct(_, _)
    | Matrix(_, _, _, _)
    | Array(_, _, _)
    | Vector(_, _, _) =>
      raise Validation("`\{name}` expects a float or vector of float argument")
    AbstractInt(number) => number.to_double()
    AbstractFloat(number, _) => number
    F32(number) => number.to_double()
  }
}

///|
fn const_eval_apply_step(
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 2 {
    raise Validation("`step` expects two arguments")
  }
  match const_eval_builtin_vector_width("step", args) {
    Some(width) => {
      let values : Array[ConstEvalValue] = []
      for index in 0.. ()
  }
  let edge = const_eval_require_float_builtin_double(args[0], "step")
  let value = const_eval_require_float_builtin_double(args[1], "step")
  AbstractFloat(if value < edge { 0.0 } else { 1.0 }, true)
}

///|
fn const_eval_mix_type(
  args : Array[ConstEvalValue],
) -> ConstEvalType raise WeslCompileError {
  if args.length() != 3 {
    raise Validation("`mix` expects three arguments")
  }
  let mut has_f32 = false
  for arg in args {
    match arg {
      Bool(_)
      | I32(_)
      | U32(_)
      | FrexpAbstract(_, _)
      | FrexpF32(_, _)
      | Struct(_, _)
      | Matrix(_, _, _, _)
      | Array(_, _, _)
      | Vector(_, _, _) => raise Validation("`mix` arguments are incompatible")
      F32(_) => has_f32 = true
      AbstractInt(_) | AbstractFloat(_, _) => ()
    }
  }
  if has_f32 {
    F32
  } else {
    AbstractFloat
  }
}

///|
fn const_eval_apply_mix(
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  match const_eval_builtin_vector_width("mix", args) {
    Some(width) => {
      let values : Array[ConstEvalValue] = []
      for index in 0.. ()
  }
  let target = const_eval_mix_type(args)
  match target {
    F32 => {
      let first = const_eval_require_f32(args[0], "mix")
      let second = const_eval_require_f32(args[1], "mix")
      let factor = const_eval_require_f32(args[2], "mix")
      F32(first * (Float::from_double(1.0) - factor) + second * factor)
    }
    _ => {
      let first = const_eval_to_abstract_float(args[0], "mix")
      let second = const_eval_to_abstract_float(args[1], "mix")
      let factor = const_eval_to_abstract_float(args[2], "mix")
      AbstractFloat(first * (1.0 - factor) + second * factor, true)
    }
  }
}

///|
fn const_eval_sign_i64(value : Int64) -> Int64 {
  if value < 0 {
    -1L
  } else if value > 0 {
    1L
  } else {
    0L
  }
}

///|
fn const_eval_sign_double(value : Double) -> Double {
  if value < 0.0 {
    -1.0
  } else if value > 0.0 {
    1.0
  } else {
    0.0
  }
}

///|
fn const_eval_sign_float(value : Float) -> Float {
  if value < 0.0 {
    -1.0
  } else if value > 0.0 {
    1.0
  } else {
    0.0
  }
}

///|
fn const_eval_apply_sign(
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 1 {
    raise Validation("`sign` expects one argument")
  }
  match args[0] {
    Bool(_) =>
      raise Validation("`sign` expects a float or vector of float argument")
    AbstractInt(number) => AbstractInt(const_eval_sign_i64(number))
    I32(number) => I32(const_eval_sign_i64(number))
    U32(number) => U32(if number == 0 { 0L } else { 1L })
    AbstractFloat(number, _) =>
      AbstractFloat(const_eval_sign_double(number), true)
    F32(number) => F32(const_eval_sign_float(number))
    FrexpAbstract(_, _) | FrexpF32(_, _) =>
      raise Validation("`sign` expects a float or vector of float argument")
    Matrix(_, _, _, _) | Array(_, _, _) =>
      raise Validation("`sign` expects a float or vector of float argument")
    Struct(_, _) =>
      raise Validation("`sign` expects a float or vector of float argument")
    Vector(width, _, elements) => {
      let values : Array[ConstEvalValue] = []
      for element in elements {
        values.push(const_eval_apply_sign([element]))
      }
      const_eval_vector_from_elements(width, values, "sign")
    }
  }
}

///|
fn const_eval_count_one_bits_width(value : Int64, width : Int) -> Int64 {
  let mut bits = value
  let mut count = 0L
  for _ in 0..> 1
  }
  count
}

///|
fn const_eval_count_leading_zeros_width(value : Int64, width : Int) -> Int64 {
  let mut count = 0L
  let mut done = false
  for offset in 0..> shift) & 1L) == 0L {
      count += 1L
    } else {
      done = true
    }
  }
  count
}

///|
fn const_eval_count_trailing_zeros_width(value : Int64, width : Int) -> Int64 {
  let mut bits = value
  let mut count = 0L
  let mut done = false
  for _ in 0..> 1
    } else {
      done = true
    }
  }
  count
}

///|
fn const_eval_vector_constructor_type(name : String) -> (Int, ConstEvalType)? {
  match name {
    "vec2" => Some((2, Bool))
    "vec2" => Some((2, I32))
    "vec2" => Some((2, U32))
    "vec2" => Some((2, F32))
    "vec3" => Some((3, Bool))
    "vec3" => Some((3, I32))
    "vec3" => Some((3, U32))
    "vec3" => Some((3, F32))
    "vec4" => Some((4, Bool))
    "vec4" => Some((4, I32))
    "vec4" => Some((4, U32))
    "vec4" => Some((4, F32))
    _ => None
  }
}

///|
fn const_eval_apply_vector_constructor(
  width : Int,
  element : ConstEvalType,
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  let elements : Array[ConstEvalValue] = []
  if args.length() == 1 {
    for _ in 0..(...)`",
    )
  }
  for arg in args {
    elements.push(
      const_eval_convert_to_type(arg, element, "vector constructor"),
    )
  }
  Vector(width, element, elements)
}

///|
fn const_eval_format_call_signature(
  name : String,
  args : Array[ConstEvalValue],
) -> String {
  let parts : Array[String] = []
  for arg in args {
    parts.push(arg.value_type().label())
  }
  let joined = parts.join(", ")
  "\{name}(\{joined})"
}

///|
fn const_eval_require_one_bit_count_arg(
  name : String,
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 1 {
    raise Validation(
      "invalid function call signature: `\{const_eval_format_call_signature(name, args)}`",
    )
  }
  args[0]
}

///|
fn const_eval_apply_integer_bit_count(
  name : String,
  args : Array[ConstEvalValue],
  count : (Int64, Int) -> Int64,
) -> ConstEvalValue raise WeslCompileError {
  let arg = const_eval_require_one_bit_count_arg(name, args)
  match arg {
    Bool(_)
    | AbstractFloat(_, _)
    | F32(_)
    | FrexpAbstract(_, _)
    | FrexpF32(_, _)
    | Matrix(_, _, _, _)
    | Array(_, _, _)
    | Struct(_, _) =>
      raise Validation("`\{name}` expects a float or vector of float argument")
    Vector(width, _, elements) => {
      let values : Array[ConstEvalValue] = []
      for element in elements {
        values.push(const_eval_apply_integer_bit_count(name, [element], count))
      }
      const_eval_vector_from_elements(width, values, name)
    }
    AbstractInt(number) => AbstractInt(count(number, 64))
    I32(number) => I32(count(number, 32))
    U32(number) => U32(count(number, 32))
  }
}

///|
fn const_eval_frexp_parts(value : Double) -> (Double, Int64) {
  if value == 0.0 {
    return (value, 0L)
  }
  let mut fract = value
  let mut exponent = 0L
  let mut magnitude = const_eval_abs_double(fract)
  while magnitude < 0.5 {
    fract = fract * 2.0
    exponent -= 1L
    magnitude = const_eval_abs_double(fract)
  }
  while magnitude >= 1.0 {
    fract = fract / 2.0
    exponent += 1L
    magnitude = const_eval_abs_double(fract)
  }
  (fract, exponent)
}

///|
fn const_eval_frexp_abstract(value : Double) -> ConstEvalValue {
  let (fract, exponent) = const_eval_frexp_parts(value)
  FrexpAbstract(fract, exponent)
}

///|
fn const_eval_frexp_f32(value : Float) -> ConstEvalValue {
  let (fract, exponent) = const_eval_frexp_parts(value.to_double())
  FrexpF32(Float::from_double(fract), exponent)
}

///|
fn const_eval_apply_frexp(
  args : Array[ConstEvalValue],
) -> ConstEvalValue raise WeslCompileError {
  if args.length() != 1 {
    let signature = const_eval_format_call_signature("frexp", args)
    raise Validation("invalid function call signature: `\{signature}`")
  }
  match args[0] {
    AbstractFloat(value, _) => const_eval_frexp_abstract(value)
    F32(value) => const_eval_frexp_f32(value)
    _ => raise Validation("not implemented: `frexp`")
  }
}

///|
fn const_eval_dispatch_builtin(
  name : String,
  values : Array[ConstEvalValue],
) -> ConstEvalValue? raise WeslCompileError {
  match const_eval_vector_constructor_type(name) {
    Some((width, element)) =>
      return Some(const_eval_apply_vector_constructor(width, element, values))
    None => ()
  }
  match name {
    "clamp" => Some(const_eval_apply_clamp(values))
    "min" => Some(const_eval_apply_min_or_max("min", values, true))
    "max" => Some(const_eval_apply_min_or_max("max", values, false))
    "abs" => Some(const_eval_apply_abs(values))
    "select" => Some(const_eval_apply_select(values))
    "all" => Some(const_eval_apply_bool_builtin("all", values))
    "any" => Some(const_eval_apply_bool_builtin("any", values))
    "floor" => Some(const_eval_apply_float_unary_builtin("floor", values))
    "ceil" => Some(const_eval_apply_float_unary_builtin("ceil", values))
    "round" => Some(const_eval_apply_float_unary_builtin("round", values))
    "trunc" => Some(const_eval_apply_float_unary_builtin("trunc", values))
    "fract" => Some(const_eval_apply_float_unary_builtin("fract", values))
    "sqrt" => Some(const_eval_apply_float_unary_builtin("sqrt", values))
    "inverseSqrt" =>
      Some(const_eval_apply_float_unary_builtin("inverseSqrt", values))
    "exp" => Some(const_eval_apply_float_unary_builtin("exp", values))
    "exp2" => Some(const_eval_apply_float_unary_builtin("exp2", values))
    "log" => Some(const_eval_apply_float_unary_builtin("log", values))
    "log2" => Some(const_eval_apply_float_unary_builtin("log2", values))
    "sin" => Some(const_eval_apply_float_unary_builtin("sin", values))
    "cos" => Some(const_eval_apply_float_unary_builtin("cos", values))
    "tan" => Some(const_eval_apply_float_unary_builtin("tan", values))
    "asin" => Some(const_eval_apply_float_unary_builtin("asin", values))
    "acos" => Some(const_eval_apply_float_unary_builtin("acos", values))
    "atan" => Some(const_eval_apply_float_unary_builtin("atan", values))
    "sinh" => Some(const_eval_apply_float_unary_builtin("sinh", values))
    "cosh" => Some(const_eval_apply_float_unary_builtin("cosh", values))
    "tanh" => Some(const_eval_apply_float_unary_builtin("tanh", values))
    "radians" => Some(const_eval_apply_float_unary_builtin("radians", values))
    "degrees" => Some(const_eval_apply_float_unary_builtin("degrees", values))
    "saturate" => Some(const_eval_apply_saturate(values))
    "pow" => Some(const_eval_apply_float_binary_builtin("pow", values))
    "atan2" => Some(const_eval_apply_float_binary_builtin("atan2", values))
    "ldexp" => Some(const_eval_apply_ldexp(values))
    "step" => Some(const_eval_apply_step(values))
    "mix" => Some(const_eval_apply_mix(values))
    "sign" => Some(const_eval_apply_sign(values))
    "frexp" => Some(const_eval_apply_frexp(values))
    "countOneBits" =>
      Some(
        const_eval_apply_integer_bit_count(
          "countOneBits", values, const_eval_count_one_bits_width,
        ),
      )
    "countLeadingZeros" =>
      Some(
        const_eval_apply_integer_bit_count(
          "countLeadingZeros", values, const_eval_count_leading_zeros_width,
        ),
      )
    "countTrailingZeros" =>
      Some(
        const_eval_apply_integer_bit_count(
          "countTrailingZeros", values, const_eval_count_trailing_zeros_width,
        ),
      )
    "extractBits"
    | "firstLeadingBit"
    | "firstTrailingBit"
    | "fma"
    | "insertBits"
    | "modf"
    | "quantizeToF16"
    | "reverseBits"
    | "smoothstep" => {
      if const_eval_builtin_arity_is_valid(name, values.length()) {
        raise Validation("not implemented: `\{name}`")
      }
      let signature = const_eval_format_call_signature(name, values)
      raise Validation("invalid function call signature: `\{signature}`")
    }
    _ => None
  }
}

///|
fn const_eval_builtin_arity_is_valid(name : String, arity : Int) -> Bool {
  match name {
    "firstLeadingBit"
    | "firstTrailingBit"
    | "frexp"
    | "modf"
    | "quantizeToF16"
    | "reverseBits" => arity == 1
    "fma" | "smoothstep" => arity == 3
    "extractBits" | "insertBits" => arity == 4
    _ => false
  }
}