///|
fn WgslIrLowerer::address_of_expression_type(
  self : WgslIrLowerer,
  function : Function,
  handle : Handle,
) -> Handle? {
  ignore(self)
  match function.expressions.items.get(handle.index()) {
    Some(AddressOf(ty, _)) => Some(ty)
    _ => None
  }
}

///|
fn wgsl_ir_address_of_operand_ref(
  expr_ref : @ast.WgslExprRef,
) -> @ast.WgslExprNode? {
  match expr_ref.root() {
    Some(root) => wgsl_ir_address_of_operand_node(root)
    None => None
  }
}

///|
fn wgsl_ir_address_of_operand_node(
  node : @ast.WgslExprNode,
) -> @ast.WgslExprNode? {
  match node.kind() {
    Group(inner) => wgsl_ir_address_of_operand_node(inner)
    Unary(op, expr) => if op == BitAnd { Some(expr) } else { None }
    _ => None
  }
}

///|
fn wgsl_ir_local_variable_kind(
  kind : @ast.WgslLocalDeclarationKind,
) -> LocalVariableKind {
  match kind {
    Var => Var
    Let => Let
    Const => abort("local const declarations are not local variables")
  }
}

///|
fn WgslIrLowerer::lower_call_statement(
  self : WgslIrLowerer,
  expr_ref : @ast.WgslExprRef,
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
) -> Unit raise WgslIrLowerError {
  guard expr_ref.root() is Some(root) else {
    raise UnsupportedExpression("empty function call")
  }
  let expression_mark = function.expressions.items.length()
  match wgsl_ir_expr_call_node(root).kind() {
    Call(callee, arguments) => {
      let arguments = wgsl_ir_call_arguments(arguments)
      match callee.kind() {
        Identifier(name) =>
          match wgsl_ir_barrier_statement_from_call(name.name(), arguments) {
            Some(statement) => block.push(statement)
            None =>
              match wgsl_ir_atomic_function_from_name(name.name()) {
                Some(atomic_function) =>
                  self.lower_atomic_statement_call(
                    atomic_function, arguments, function, scope, block,
                  )
                None =>
                  match self.function_names.get(name.name()) {
                    Some(handle) => {
                      let values = self.lower_user_function_call_statement_arguments(
                        handle, arguments, function, scope, block,
                      )
                      match wgsl_ir_last_call_argument_handle(values) {
                        Some(last_value) =>
                          self.push_user_function_call_emit_ranges(
                            block, function, expression_mark, handle, values, last_value,
                          )
                        None => ()
                      }
                      block.push(Call(handle, values, None))
                    }
                    None =>
                      if !self.lower_ray_query_statement_call(
                          name.name(),
                          arguments,
                          function,
                          scope,
                          block,
                        ) &&
                        !self.lower_texture_statement_call(
                          name.name(),
                          arguments,
                          function,
                          scope,
                          block,
                        ) &&
                        !self.lower_cooperative_statement_call(
                          name.name(),
                          arguments,
                          function,
                          scope,
                          block,
                        ) {
                        ignore(
                          self.lower_function_expression_node(
                            root, function, scope,
                          ),
                        )
                      }
                  }
              }
          }
        _ => ignore(self.lower_function_expression_node(root, function, scope))
      }
    }
    _ => ignore(self.lower_function_expression_node(root, function, scope))
  }
}

///|
fn wgsl_ir_last_call_argument_handle(values : Array[Handle]) -> Handle? {
  let mut result : Handle? = None
  for value in values {
    match result {
      Some(current) =>
        if value.index() > current.index() {
          result = Some(value)
        }
      None => result = Some(value)
    }
  }
  result
}

///|
fn WgslIrLowerer::push_user_function_call_emit_ranges(
  self : WgslIrLowerer,
  block : Block,
  function : Function,
  expression_mark : Int32,
  callee : Handle,
  arguments : Array[Handle],
  end : Handle,
) -> Unit {
  let suppressed : @set.Set[Int32] = Set([])
  match self.shader_module.functions.items.get(callee.index()) {
    Some(callee_function) =>
      for index in 0..
            wgsl_ir_collect_pointer_call_lvalue_emit_suppression(
              function,
              arguments[index],
              suppressed,
            )
          _ => ()
        }
      }
    None => ()
  }
  BodyEmissionPlanner::push_statement_emit_ranges_skipping(
    block, function, expression_mark, end, suppressed,
  )
}

///|
fn wgsl_ir_collect_pointer_call_lvalue_emit_suppression(
  function : Function,
  handle : Handle,
  suppressed : @set.Set[Int32],
) -> Unit {
  if suppressed.contains(handle.index()) {
    return
  }
  match function.expressions.items.get(handle.index()) {
    Some(Access(base, _))
    | Some(AccessIndex(base, _))
    | Some(Component(base, _))
    | Some(Swizzle(_, base, _)) => {
      suppressed.add(handle.index())
      wgsl_ir_collect_pointer_call_lvalue_emit_suppression(
        function, base, suppressed,
      )
    }
    _ => ()
  }
}

///|
priv enum WgslIrBarrierBuiltin {
  BarrierControl(Barrier)
  BarrierMemory(Barrier)
}

///|
fn wgsl_ir_barrier_builtin_from_name(name : String) -> WgslIrBarrierBuiltin? {
  match name {
    "workgroupBarrier" => Some(BarrierControl(Barrier::work_group()))
    "storageBarrier" => Some(BarrierMemory(Barrier::storage()))
    "textureBarrier" => Some(BarrierMemory(Barrier::texture()))
    "subgroupBarrier" => Some(BarrierControl(Barrier::sub_group()))
    _ => None
  }
}

///|
fn wgsl_ir_barrier_statement_from_call(
  name : String,
  arguments : Array[@ast.WgslExprNode],
) -> Statement? raise WgslIrLowerError {
  match wgsl_ir_barrier_builtin_from_name(name) {
    Some(barrier) => {
      guard arguments.length() == 0 else {
        raise UnsupportedExpression("\{name} expects no arguments")
      }
      match barrier {
        BarrierControl(barrier) => Some(ControlBarrier(barrier))
        BarrierMemory(barrier) => Some(MemoryBarrier(barrier))
      }
    }
    None => None
  }
}

///|
fn wgsl_ir_barrier_value_error(name : String) -> WgslIrLowerError {
  UnsupportedExpression("barrier builtin has no value: \{name}")
}

///|
fn WgslIrLowerer::lower_user_function_call_arguments(
  self : WgslIrLowerer,
  callee : Handle,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Array[Handle] raise WgslIrLowerError {
  let flattened = wgsl_ir_flatten_expression_arguments(arguments)
  let callee_function = match
    self.shader_module.functions.items.get(callee.index()) {
    Some(value) => value
    None => raise UnsupportedExpression("invalid function call target")
  }
  let planner = self.call_argument_planner(
    flattened, callee_function, function, scope,
  )
  planner.lower_arguments()
}

///|
fn WgslIrLowerer::callee_parameter_is_pointer(
  self : WgslIrLowerer,
  callee_function : Function,
  index : Int32,
) -> Bool {
  match callee_function.arguments.get(index) {
    Some(parameter) => self.lowerer_type_is_pointer(parameter.ty)
    None => false
  }
}

///|
fn wgsl_ir_expr_node_is_call_value(node : @ast.WgslExprNode) -> Bool {
  match node.kind() {
    Group(inner) => wgsl_ir_expr_node_is_call_value(inner)
    Call(_, _) => true
    _ => false
  }
}

///|
fn wgsl_ir_expr_node_is_static_literal_index(node : @ast.WgslExprNode) -> Bool {
  match node.kind() {
    Group(inner) => wgsl_ir_expr_node_is_static_literal_index(inner)
    _ => wgsl_ir_int_literal_from_expr_node(node) != None
  }
}

///|
fn WgslIrLowerer::lower_user_function_call_argument_at(
  self : WgslIrLowerer,
  argument : @ast.WgslExprNode,
  callee_function : Function,
  index : Int32,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let expected_ty = match callee_function.arguments.get(index) {
    Some(argument) => Some(argument.ty)
    None => None
  }
  match expected_ty {
    Some(ty) if self.lowerer_type_is_pointer(ty) =>
      match wgsl_ir_address_of_operand_node(argument) {
        Some(target) =>
          return self.lower_function_lvalue_node(target, function, scope)
        None => ()
      }
    _ => ()
  }
  let lowered = match wgsl_ir_expr_node_literal_token(argument) {
    Some(token) =>
      self.lower_function_fresh_literal_node_with_expected(
        token, function, expected_ty,
      )
    None =>
      self.lower_function_expression_node_with_expected(
        argument, function, scope, expected_ty,
      )
  }
  match expected_ty {
    Some(ty) if self.lowerer_type_is_pointer(ty) => lowered
    Some(_) => self.apply_function_load_rule(function, lowered)
    None => lowered
  }
}

///|
fn WgslIrLowerer::expression_node_contains_user_function_call(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
) -> Bool {
  match node.kind() {
    Group(inner) | Template(inner, _) =>
      self.expression_node_contains_user_function_call(inner)
    Call(callee, arguments) => {
      let arguments = wgsl_ir_call_arguments(arguments)
      let callee_is_user_function = match callee.kind() {
        Identifier(name) => self.function_names.contains(name.name())
        _ => false
      }
      callee_is_user_function ||
      arguments.any(fn(argument) {
        self.expression_node_contains_user_function_call(argument)
      })
    }
    Sequence(items) =>
      items.any(fn(item) {
        self.expression_node_contains_user_function_call(item)
      })
    Unary(_, expr) => self.expression_node_contains_user_function_call(expr)
    Binary(_, left, right) =>
      self.expression_node_contains_user_function_call(left) ||
      self.expression_node_contains_user_function_call(right)
    Member(base, _) => self.expression_node_contains_user_function_call(base)
    Index(base, index) =>
      self.expression_node_contains_user_function_call(base) ||
      self.expression_node_contains_user_function_call(index)
    _ => false
  }
}

///|
fn WgslIrLowerer::lower_user_function_call_statement_arguments(
  self : WgslIrLowerer,
  callee : Handle,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
) -> Array[Handle] raise WgslIrLowerError {
  let lowered = self.lower_user_function_call_arguments(
    callee, arguments, function, scope,
  )
  let materialized : Array[Handle] = []
  for index in 0.. @ast.WgslExprNode {
  match node.kind() {
    Group(inner) => wgsl_ir_expr_call_node(inner)
    Sequence([single]) => wgsl_ir_expr_call_node(single)
    _ => node
  }
}

///|
fn wgsl_ir_call_arguments(
  arguments : Array[@ast.WgslExprNode],
) -> Array[@ast.WgslExprNode] {
  wgsl_ir_flatten_expression_arguments(arguments)
}

///|
fn WgslIrLowerer::lower_ray_query_statement_call(
  self : WgslIrLowerer,
  name : String,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
) -> Bool raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  match name {
    "rayQueryInitialize" => {
      guard arguments is [query, acceleration, ray_desc] else { return false }
      let query_handle = self.lower_function_expression_node(
        query, function, scope,
      )
      let acceleration_handle = self.lower_function_expression_node(
        acceleration, function, scope,
      )
      let ray_desc_handle = self.lower_function_expression_node(
        ray_desc, function, scope,
      )
      block.push(
        RayQuery(query_handle, Initialize(acceleration_handle, ray_desc_handle)),
      )
      true
    }
    "rayQueryProceed" => {
      guard arguments is [query] else { return false }
      let query_handle = self.lower_function_expression_node(
        query, function, scope,
      )
      block.push(RayQuery(query_handle, Proceed(query_handle)))
      true
    }
    "rayQueryGenerateIntersection" => {
      guard arguments is [query, intersection_t] else { return false }
      let query_handle = self.lower_function_expression_node(
        query, function, scope,
      )
      let t_handle = self.lower_function_expression_node(
        intersection_t, function, scope,
      )
      block.push(RayQuery(query_handle, GenerateIntersection(t_handle)))
      true
    }
    "rayQueryConfirmIntersection" => {
      guard arguments is [query] else { return false }
      let query_handle = self.lower_function_expression_node(
        query, function, scope,
      )
      block.push(RayQuery(query_handle, ConfirmIntersection))
      true
    }
    "rayQueryTerminate" => {
      guard arguments is [query] else { return false }
      let query_handle = self.lower_function_expression_node(
        query, function, scope,
      )
      block.push(RayQuery(query_handle, Terminate))
      true
    }
    _ => false
  }
}

///|
fn WgslIrLowerer::lower_atomic_statement_call(
  self : WgslIrLowerer,
  atomic_function : AtomicFunction,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
) -> Unit raise WgslIrLowerError {
  if atomic_function == Store {
    guard arguments is [pointer_argument, value_argument] else {
      raise UnsupportedExpression("atomicStore expects 2 arguments")
    }
    let pointer = self.lower_atomic_pointer_argument(
      pointer_argument, function, scope,
    )
    let value = self.lower_function_expression_node(
      value_argument, function, scope,
    )
    block.push(Atomic(pointer, Store, value, None, None))
    return
  }
  let handle = self.lower_function_atomic_call(
    atomic_function, arguments, function, scope,
  )
  if self.function_expression_type(function, handle) != None {
    ignore(self.materialize_function_expression(function, scope, block, handle))
  } else {
    block.push(Emit(HandleRange(handle, handle)))
  }
}

///|
fn WgslIrLowerer::lower_texture_statement_call(
  self : WgslIrLowerer,
  name : String,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
) -> Bool raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  match name {
    "textureAtomicAdd"
    | "textureAtomicAnd"
    | "textureAtomicMax"
    | "textureAtomicMin"
    | "textureAtomicOr"
    | "textureAtomicXor" => {
      let atomic_function = match
        wgsl_ir_texture_atomic_function_from_name(name) {
        Some(function) => function
        None => abort("unreachable texture atomic builtin")
      }
      self.lower_texture_atomic_statement(
        name, atomic_function, arguments, function, scope, block,
      )
      true
    }
    "textureStore" => {
      guard arguments.length() == 3 || arguments.length() == 4 else {
        raise UnsupportedExpression("textureStore expects 3 or 4 arguments")
      }
      let expression_mark = function.expressions.items.length()
      let coordinate = self.lower_function_expression_node(
        arguments[1],
        function,
        scope,
      )
      let (raw_array_index, value_index) = if arguments.length() == 4 {
        (
          Some(
            self.lower_function_expression_node(arguments[2], function, scope),
          ),
          3,
        )
      } else {
        (None, 2)
      }
      let value = self.lower_function_expression_node(
        arguments[value_index],
        function,
        scope,
      )
      let image = self.lower_function_expression_node(
        arguments[0],
        function,
        scope,
      )
      let coordinate = self.materialize_image_store_operand(
        function, scope, block, coordinate,
      )
      let array_index = self.materialize_optional_image_store_operand(
        function, scope, block, raw_array_index,
      )
      let value = self.materialize_image_store_operand(
        function, scope, block, value,
      )
      let mut end = wgsl_ir_max_handle(image, coordinate)
      match array_index {
        Some(handle) => end = wgsl_ir_max_handle(end, handle)
        None => ()
      }
      end = wgsl_ir_max_handle(end, value)
      BodyEmissionPlanner::push_statement_emit_ranges(
        block, function, expression_mark, end,
      )
      block.push(ImageStore(image, coordinate, array_index, value))
      true
    }
    _ => false
  }
}

///|
fn wgsl_ir_texture_atomic_function_from_name(name : String) -> AtomicFunction? {
  match name {
    "textureAtomicAdd" => Some(Add)
    "textureAtomicAnd" => Some(And)
    "textureAtomicMax" => Some(Max)
    "textureAtomicMin" => Some(Min)
    "textureAtomicOr" => Some(InclusiveOr)
    "textureAtomicXor" => Some(ExclusiveOr)
    _ => None
  }
}

///|
fn WgslIrLowerer::lower_texture_atomic_statement(
  self : WgslIrLowerer,
  name : String,
  atomic_function : AtomicFunction,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  block : Block,
) -> Unit raise WgslIrLowerError {
  guard arguments.length() == 3 || arguments.length() == 4 else {
    raise UnsupportedExpression(
      "\{name} expects 3 arguments for non-arrayed storage textures or 4 arguments for arrayed storage textures",
    )
  }
  let image = self.lower_function_expression_node(arguments[0], function, scope)
  let coordinate = self.lower_function_expression_node(
    arguments[1],
    function,
    scope,
  )
  let arrayed = self.image_expression_is_arrayed(function, image)
  let (array_index, value_index) = if arrayed {
    guard arguments.length() == 4 else {
      raise UnsupportedExpression(
        "\{name} on arrayed storage texture expects array index",
      )
    }
    (
      Some(self.lower_function_expression_node(arguments[2], function, scope)),
      3,
    )
  } else {
    guard arguments.length() == 3 else {
      raise UnsupportedExpression(
        "\{name} on non-arrayed storage texture does not accept array index",
      )
    }
    (None, 2)
  }
  let value = self.lower_function_expression_node(
    arguments[value_index],
    function,
    scope,
  )
  block.push(
    ImageAtomic(image, coordinate, array_index, atomic_function, value),
  )
}

///|
fn WgslIrLowerer::lower_texture_expression_call(
  self : WgslIrLowerer,
  name : String,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle? raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  match name {
    "textureSample" =>
      Some(self.lower_image_sample_expression(arguments, function, scope, Auto))
    "textureSampleBias" =>
      Some(
        self.lower_image_sample_with_level_argument(
          arguments, function, scope, "textureSampleBias", "bias",
        ),
      )
    "textureSampleGrad" =>
      Some(self.lower_image_sample_grad_expression(arguments, function, scope))
    "textureSampleLevel" =>
      Some(
        self.lower_image_sample_with_level_argument(
          arguments, function, scope, "textureSampleLevel", "level",
        ),
      )
    "textureSampleCompare" =>
      Some(
        self.lower_image_sample_compare_expression(
          arguments,
          function,
          scope,
          Auto,
          "textureSampleCompare",
        ),
      )
    "textureSampleCompareLevel" =>
      Some(
        self.lower_image_sample_compare_expression(
          arguments,
          function,
          scope,
          Zero,
          "textureSampleCompareLevel",
        ),
      )
    "textureSampleBaseClampToEdge" =>
      Some(
        self.lower_image_sample_base_clamp_to_edge_expression(
          arguments, function, scope,
        ),
      )
    "textureGather" =>
      Some(self.lower_image_gather_expression(arguments, function, scope))
    "textureGatherCompare" =>
      Some(
        self.lower_image_gather_compare_expression(arguments, function, scope),
      )
    "textureLoad" =>
      Some(self.lower_image_load_expression(arguments, function, scope))
    "textureDimensions" =>
      Some(self.lower_image_query_expression(arguments, function, scope))
    "textureNumLevels" =>
      Some(
        self.lower_image_query_kind_expression(
          arguments,
          function,
          scope,
          NumLevels,
          "textureNumLevels",
        ),
      )
    "textureNumLayers" =>
      Some(
        self.lower_image_query_kind_expression(
          arguments,
          function,
          scope,
          NumLayers,
          "textureNumLayers",
        ),
      )
    "textureNumSamples" =>
      Some(
        self.lower_image_query_kind_expression(
          arguments,
          function,
          scope,
          NumSamples,
          "textureNumSamples",
        ),
      )
    _ => None
  }
}

///|
priv struct WgslIrTextureTrailingArgs {
  array_index : Handle?
  trailing : Array[Handle]
  offset : Handle?
}

///|
priv enum WgslIrTextureOffsetPlan {
  LowerTextureOffsetNode(@ast.WgslExprNode)
  FinishNamedTextureOffsetConstructor(
    String,
    Array[@ast.WgslExprNode],
    Array[Handle],
    Int32
  )
  FinishTemplatedTextureOffsetConstructor(Handle, Array[Handle])
}

///|
fn WgslIrLowerer::prelower_texture_offset_plan(
  self : WgslIrLowerer,
  node : @ast.WgslExprNode,
  function : Function,
  scope : WgslIrFunctionScope,
) -> WgslIrTextureOffsetPlan raise WgslIrLowerError {
  match node.kind() {
    Group(inner) => self.prelower_texture_offset_plan(inner, function, scope)
    Call(callee, arguments) => {
      let arguments = wgsl_ir_call_arguments(arguments)
      match callee.kind() {
        Template(_, _) => {
          let ty = self.lower_templated_callee_type(callee)
          match ty {
            Some(ty) =>
              FinishTemplatedTextureOffsetConstructor(
                ty,
                self.lower_function_constructor_arguments(
                  ty, arguments, function, scope,
                ),
              )
            None => LowerTextureOffsetNode(node)
          }
        }
        Identifier(name) => {
          let ty = Some(
            self.lower_untemplated_constructor_type_from_ast(
              name.name(),
              arguments,
              "texture offset constructor",
            ),
          ) catch {
            _ => None
          }
          match ty {
            Some(_) => {
              let expression_mark = function.expressions.items.length()
              let lowered_arguments = self.lower_function_constructor_arguments_with_expected(
                arguments,
                function,
                scope,
                None,
              )
              FinishNamedTextureOffsetConstructor(
                name.name(),
                arguments,
                lowered_arguments,
                expression_mark,
              )
            }
            None => LowerTextureOffsetNode(node)
          }
        }
        _ => LowerTextureOffsetNode(node)
      }
    }
    _ => LowerTextureOffsetNode(node)
  }
}

///|
fn WgslIrLowerer::finish_texture_offset_plan(
  self : WgslIrLowerer,
  plan : WgslIrTextureOffsetPlan,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  match plan {
    LowerTextureOffsetNode(node) =>
      self.lower_function_expression_node(node, function, scope)
    FinishNamedTextureOffsetConstructor(
      name,
      arguments,
      lowered_arguments,
      expression_mark
    ) =>
      self.finish_function_constructor_call(
        name,
        arguments,
        lowered_arguments,
        function,
        scope,
        None,
        expression_mark,
      )
    FinishTemplatedTextureOffsetConstructor(ty, lowered_arguments) =>
      self.lower_function_constructor_expression_from_handles(
        ty, lowered_arguments, function,
      )
  }
}

///|
fn WgslIrLowerer::lower_texture_trailing_args(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  image : Handle,
  base_count : Int32,
  trailing_count : Int32,
  callee : String,
) -> WgslIrTextureTrailingArgs raise WgslIrLowerError {
  let arrayed = self.image_expression_is_arrayed(function, image)
  let remaining = arguments.length() - base_count
  guard remaining >= trailing_count else {
    raise UnsupportedExpression("\{callee} has too few arguments")
  }
  let (array_index, trailing_start) = if arrayed {
    guard remaining == trailing_count + 1 || remaining == trailing_count + 2 else {
      raise UnsupportedExpression(
        "\{callee} argument list does not match arrayed texture signature",
      )
    }
    (
      Some(
        self.lower_function_expression_node(
          arguments[base_count],
          function,
          scope,
        ),
      ),
      base_count + 1,
    )
  } else {
    guard remaining == trailing_count || remaining == trailing_count + 1 else {
      raise UnsupportedExpression(
        "\{callee} argument list does not match texture signature",
      )
    }
    (None, base_count)
  }
  let trailing : Array[Handle] = []
  for i in 0.. Bool {
  match self.image_type_handle_inner(ty) {
    Some((_, _, Depth(_))) => true
    _ => false
  }
}

///|
fn WgslIrLowerer::image_type_handle_is_arrayed(
  self : WgslIrLowerer,
  ty : Handle,
) -> Bool {
  match self.image_type_handle_inner(ty) {
    Some((_, arrayed, _)) => arrayed
    None => false
  }
}

///|
fn WgslIrLowerer::image_type_handle_is_multisampled(
  self : WgslIrLowerer,
  ty : Handle,
) -> Bool {
  match self.image_type_handle_inner(ty) {
    Some((_, _, Sampled(_, multisampled))) => multisampled
    Some((_, _, Depth(multisampled))) => multisampled
    _ => false
  }
}

///|
fn WgslIrLowerer::image_type_handle_is_storage(
  self : WgslIrLowerer,
  ty : Handle,
) -> Bool {
  match self.image_type_handle_inner(ty) {
    Some((_, _, Storage(_, _))) => true
    _ => false
  }
}

///|
fn WgslIrLowerer::image_type_handle_is_external(
  self : WgslIrLowerer,
  ty : Handle,
) -> Bool {
  match self.image_type_handle_inner(ty) {
    Some((_, _, External)) => true
    _ => false
  }
}

///|
fn WgslIrLowerer::image_type_handle_inner(
  self : WgslIrLowerer,
  ty : Handle,
) -> (ImageDimension, Bool, ImageClass)? {
  match self.shader_module.types.get(self.deref_pointer_type_handle(ty)) {
    Some(type_) =>
      match type_.inner {
        Image(dimension, arrayed, image_class) =>
          Some((dimension, arrayed, image_class))
        _ => None
      }
    None => None
  }
}

///|
fn WgslIrLowerer::texture_offset_index_from_image_type(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  image_ty : Handle,
  base_count : Int32,
  trailing_count : Int32,
  callee : String,
) -> Int32? raise WgslIrLowerError {
  let arrayed = self.image_type_handle_is_arrayed(image_ty)
  let remaining = arguments.length() - base_count
  guard remaining >= trailing_count else {
    raise UnsupportedExpression("\{callee} has too few arguments")
  }
  let trailing_start = if arrayed {
    guard remaining == trailing_count + 1 || remaining == trailing_count + 2 else {
      raise UnsupportedExpression(
        "\{callee} argument list does not match arrayed texture signature",
      )
    }
    base_count + 1
  } else {
    guard remaining == trailing_count || remaining == trailing_count + 1 else {
      raise UnsupportedExpression(
        "\{callee} argument list does not match texture signature",
      )
    }
    base_count
  }
  let offset_index = trailing_start + trailing_count
  if offset_index < arguments.length() {
    Some(offset_index)
  } else {
    None
  }
}

///|
fn WgslIrLowerer::predeclare_texture_coordinate_root(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  coordinate_index : Int32,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Unit raise WgslIrLowerError {
  match arguments.get(coordinate_index) {
    Some(coordinate) =>
      self.predeclare_function_member_root_expression(
        coordinate, function, scope,
      )
    None => ()
  }
}

///|
fn WgslIrLowerer::lower_texture_coordinate_before_image_if_needed(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  coordinate_index : Int32,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle? raise WgslIrLowerError {
  match arguments.get(coordinate_index) {
    Some(coordinate) =>
      if self.texture_coordinate_is_mutable_local_value(
          coordinate, function, scope,
        ) {
        Some(self.lower_function_expression_node(coordinate, function, scope))
      } else {
        self.predeclare_function_member_root_expression(
          coordinate, function, scope,
        )
        None
      }
    None => None
  }
}

///|
fn WgslIrLowerer::texture_coordinate_is_mutable_local_value(
  self : WgslIrLowerer,
  coordinate : @ast.WgslExprNode,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Bool {
  ignore(self)
  guard scope.block_depth > 0 else { return false }
  match coordinate.kind() {
    Group(inner) =>
      self.texture_coordinate_is_mutable_local_value(inner, function, scope)
    Identifier(name) =>
      match scope.locals.get(name.name()) {
        Some(handle) =>
          match function.local_variables.items.get(handle.index()) {
            Some(local_var) =>
              local_var.kind == Var && !local_var.generated_temporary
            None => false
          }
        None => false
      }
    _ => false
  }
}

///|
fn WgslIrLowerer::lower_texture_mip_level_argument(
  self : WgslIrLowerer,
  argument : @ast.WgslExprNode,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  self.lower_function_expression_node_with_expected(
    argument,
    function,
    scope,
    Some(self.scalar_type_handle(Scalar(Sint, 4))),
  )
}

///|
fn WgslIrLowerer::image_expression_is_arrayed(
  self : WgslIrLowerer,
  function : Function,
  image : Handle,
) -> Bool {
  match self.image_expression_inner(function, image) {
    Some((_, arrayed, _)) => arrayed
    None => false
  }
}

///|
fn WgslIrLowerer::image_expression_inner(
  self : WgslIrLowerer,
  function : Function,
  image : Handle,
) -> (ImageDimension, Bool, ImageClass)? {
  match self.function_expression_type(function, image) {
    Some(ty_handle) =>
      match self.shader_module.types.get(ty_handle) {
        Some(ty) =>
          match ty.inner {
            Image(dimension, arrayed, image_class) =>
              Some((dimension, arrayed, image_class))
            _ => None
          }
        None => None
      }
    None => None
  }
}

///|
fn WgslIrLowerer::lower_image_sample_expression(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  level : SampleLevel,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments.length() >= 3 else {
    raise UnsupportedExpression("textureSample expects at least 3 arguments")
  }
  self.predeclare_texture_coordinate_root(arguments, 2, function, scope)
  let image = self.lower_function_expression_node(arguments[0], function, scope)
  let sampler = self.lower_function_expression_node(
    arguments[1],
    function,
    scope,
  )
  let coordinate = self.lower_function_expression_node(
    arguments[2],
    function,
    scope,
  )
  let parsed = self.lower_texture_trailing_args(
    arguments, function, scope, image, 3, 0, "textureSample",
  )
  function.expressions.append(
    ImageSample(
      image,
      sampler,
      None,
      coordinate,
      parsed.array_index,
      parsed.offset,
      level,
      None,
      false,
    ),
  )
}

///|
fn WgslIrLowerer::lower_image_sample_with_level_argument(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  callee : String,
  level_name : String,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments.length() >= 4 else {
    raise UnsupportedExpression("\{callee} expects at least 4 arguments")
  }
  let prelowered_coordinate = if level_name == "level" {
    self.lower_texture_coordinate_before_image_if_needed(
      arguments, 2, function, scope,
    )
  } else {
    self.predeclare_texture_coordinate_root(arguments, 2, function, scope)
    None
  }
  let image = self.lower_function_expression_node(arguments[0], function, scope)
  let sampler = self.lower_function_expression_node(
    arguments[1],
    function,
    scope,
  )
  let coordinate = match prelowered_coordinate {
    Some(handle) => handle
    None => self.lower_function_expression_node(arguments[2], function, scope)
  }
  let parsed = self.lower_texture_trailing_args(
    arguments, function, scope, image, 3, 1, callee,
  )
  guard parsed.trailing is [level_handle] else {
    raise UnsupportedExpression("\{callee} missing \{level_name} argument")
  }
  let level = if level_name == "bias" {
    SampleLevel::Bias(level_handle)
  } else {
    Exact(level_handle)
  }
  function.expressions.append(
    ImageSample(
      image,
      sampler,
      None,
      coordinate,
      parsed.array_index,
      parsed.offset,
      level,
      None,
      false,
    ),
  )
}

///|
fn WgslIrLowerer::lower_image_sample_grad_expression(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments.length() >= 5 else {
    raise UnsupportedExpression(
      "textureSampleGrad expects at least 5 arguments",
    )
  }
  self.predeclare_texture_coordinate_root(arguments, 2, function, scope)
  let image = self.lower_function_expression_node(arguments[0], function, scope)
  let sampler = self.lower_function_expression_node(
    arguments[1],
    function,
    scope,
  )
  let coordinate = self.lower_function_expression_node(
    arguments[2],
    function,
    scope,
  )
  let parsed = self.lower_texture_trailing_args(
    arguments, function, scope, image, 3, 2, "textureSampleGrad",
  )
  guard parsed.trailing is [ddx, ddy] else {
    raise UnsupportedExpression("textureSampleGrad missing gradient arguments")
  }
  function.expressions.append(
    ImageSample(
      image,
      sampler,
      None,
      coordinate,
      parsed.array_index,
      parsed.offset,
      Gradient(ddx, ddy),
      None,
      false,
    ),
  )
}

///|
fn WgslIrLowerer::lower_image_sample_compare_expression(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  level : SampleLevel,
  callee : String,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments.length() >= 4 else {
    raise UnsupportedExpression("\{callee} expects at least 4 arguments")
  }
  self.predeclare_texture_coordinate_root(arguments, 2, function, scope)
  let image = self.lower_function_expression_node(arguments[0], function, scope)
  let sampler = self.lower_function_expression_node(
    arguments[1],
    function,
    scope,
  )
  let coordinate = self.lower_function_expression_node(
    arguments[2],
    function,
    scope,
  )
  let parsed = self.lower_texture_trailing_args(
    arguments, function, scope, image, 3, 1, callee,
  )
  guard parsed.trailing is [depth_ref] else {
    raise UnsupportedExpression("\{callee} missing depth reference argument")
  }
  function.expressions.append(
    ImageSample(
      image,
      sampler,
      None,
      coordinate,
      parsed.array_index,
      parsed.offset,
      level,
      Some(depth_ref),
      false,
    ),
  )
}

///|
fn WgslIrLowerer::lower_image_sample_base_clamp_to_edge_expression(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments.length() == 3 else {
    raise UnsupportedExpression(
      "textureSampleBaseClampToEdge expects 3 arguments",
    )
  }
  self.predeclare_texture_coordinate_root(arguments, 2, function, scope)
  let image = self.lower_function_expression_node(arguments[0], function, scope)
  let sampler = self.lower_function_expression_node(
    arguments[1],
    function,
    scope,
  )
  let coordinate = self.lower_function_expression_node(
    arguments[2],
    function,
    scope,
  )
  function.expressions.append(
    ImageSample(image, sampler, None, coordinate, None, None, Zero, None, true),
  )
}

///|
fn WgslIrLowerer::lower_image_gather_expression(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments.length() >= 3 else {
    raise UnsupportedExpression("textureGather expects at least 3 arguments")
  }
  let probe_image_index = if wgsl_ir_expr_node_is_gather_component(arguments[0]) {
    1
  } else {
    0
  }
  let probe_image_ty = self.infer_expression_type_in_function_scope(
    arguments[probe_image_index],
    "textureGather image",
    function,
    scope,
  )
  let depth = self.image_type_handle_is_depth(probe_image_ty)
  let has_component = probe_image_index == 1
  let (component, image_index, sampler_index, coordinate_index, base_count) = if depth {
    let (component, sampler_index, coordinate_index, base_count) = if has_component {
      guard arguments.length() >= 4 else {
        raise UnsupportedExpression(
          "textureGather with a component argument expects image, sampler, and coordinate arguments",
        )
      }
      (wgsl_ir_gather_component_from_node(arguments[0]), 2, 3, 4)
    } else {
      (X, 1, 2, 3)
    }
    (
      Some(component),
      probe_image_index,
      sampler_index,
      coordinate_index,
      base_count,
    )
  } else {
    guard arguments.length() >= 4 else {
      raise UnsupportedExpression(
        "textureGather for non-depth textures expects a component argument",
      )
    }
    let component = wgsl_ir_gather_component_from_node(arguments[0])
    (Some(component), probe_image_index, 2, 3, 4)
  }
  let offset_plan = match
    self.texture_offset_index_from_image_type(
      arguments, probe_image_ty, base_count, 0, "textureGather",
    ) {
    Some(offset_index) =>
      Some(
        self.prelower_texture_offset_plan(
          arguments[offset_index],
          function,
          scope,
        ),
      )
    None => None
  }
  let image = self.lower_function_expression_node(
    arguments[image_index],
    function,
    scope,
  )
  let sampler = self.lower_function_expression_node(
    arguments[sampler_index],
    function,
    scope,
  )
  let coordinate = self.lower_function_expression_node(
    arguments[coordinate_index],
    function,
    scope,
  )
  let array_index = if self.image_type_handle_is_arrayed(probe_image_ty) {
    Some(
      self.lower_function_expression_node(
        arguments[base_count],
        function,
        scope,
      ),
    )
  } else {
    None
  }
  let offset = match offset_plan {
    Some(plan) => Some(self.finish_texture_offset_plan(plan, function, scope))
    None => None
  }
  function.expressions.append(
    ImageSample(
      image,
      sampler,
      component,
      coordinate,
      array_index,
      offset,
      Auto,
      None,
      false,
    ),
  )
}

///|
fn WgslIrLowerer::lower_image_gather_compare_expression(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments.length() >= 4 else {
    raise UnsupportedExpression(
      "textureGatherCompare expects at least 4 arguments",
    )
  }
  self.predeclare_texture_coordinate_root(arguments, 2, function, scope)
  let image = self.lower_function_expression_node(arguments[0], function, scope)
  let sampler = self.lower_function_expression_node(
    arguments[1],
    function,
    scope,
  )
  let coordinate = self.lower_function_expression_node(
    arguments[2],
    function,
    scope,
  )
  let parsed = self.lower_texture_trailing_args(
    arguments, function, scope, image, 3, 1, "textureGatherCompare",
  )
  guard parsed.trailing is [depth_ref] else {
    raise UnsupportedExpression(
      "textureGatherCompare missing depth reference argument",
    )
  }
  function.expressions.append(
    ImageSample(
      image,
      sampler,
      Some(X),
      coordinate,
      parsed.array_index,
      parsed.offset,
      Auto,
      Some(depth_ref),
      false,
    ),
  )
}

///|
fn WgslIrLowerer::lower_image_load_expression(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments.length() >= 2 && arguments.length() <= 4 else {
    raise UnsupportedExpression("textureLoad expects 2 to 4 arguments")
  }
  let image_ty = self.infer_expression_type_in_function_scope(
    arguments[0],
    "textureLoad image",
    function,
    scope,
  )
  let coordinate = self.lower_function_expression_node(
    arguments[1],
    function,
    scope,
  )
  let arrayed = self.image_type_handle_is_arrayed(image_ty)
  let multisampled = self.image_type_handle_is_multisampled(image_ty)
  let storage = self.image_type_handle_is_storage(image_ty)
  let external = self.image_type_handle_is_external(image_ty)
  let no_handle : Handle? = None
  let (array_index, sample, level, image) = if multisampled {
    guard !arrayed && arguments.length() == 3 else {
      raise UnsupportedExpression(
        "textureLoad multisampled texture expects sample index",
      )
    }
    let sample = self.lower_function_expression_node(
      arguments[2],
      function,
      scope,
    )
    let image = self.lower_function_expression_node(
      arguments[0],
      function,
      scope,
    )
    (no_handle, Some(sample), no_handle, image)
  } else if arrayed {
    if storage {
      guard arguments.length() == 3 else {
        raise UnsupportedExpression(
          "textureLoad storage texture array expects array index",
        )
      }
      let array_index = self.lower_function_expression_node(
        arguments[2],
        function,
        scope,
      )
      let image = self.lower_function_expression_node(
        arguments[0],
        function,
        scope,
      )
      (Some(array_index), no_handle, no_handle, image)
    } else {
      guard arguments.length() == 4 else {
        raise UnsupportedExpression(
          "textureLoad sampled texture array expects array index and level",
        )
      }
      let array_index = self.lower_function_expression_node(
        arguments[2],
        function,
        scope,
      )
      let image = self.lower_function_expression_node(
        arguments[0],
        function,
        scope,
      )
      (
        Some(array_index),
        no_handle,
        Some(
          self.lower_texture_mip_level_argument(arguments[3], function, scope),
        ),
        image,
      )
    }
  } else if storage || external {
    guard arguments.length() == 2 else {
      raise UnsupportedExpression(
        "textureLoad storage/external texture expects no level argument",
      )
    }
    let image = self.lower_function_expression_node(
      arguments[0],
      function,
      scope,
    )
    (no_handle, no_handle, no_handle, image)
  } else {
    guard arguments.length() == 3 else {
      raise UnsupportedExpression(
        "textureLoad sampled texture expects mip level",
      )
    }
    let image = self.lower_function_expression_node(
      arguments[0],
      function,
      scope,
    )
    (
      no_handle,
      no_handle,
      Some(self.lower_texture_mip_level_argument(arguments[2], function, scope)),
      image,
    )
  }
  ignore(self.image_expression_result_type(function, image, None, None))
  function.expressions.append(
    ImageLoad(image, coordinate, array_index, sample, level),
  )
}

///|
fn WgslIrLowerer::lower_image_query_expression(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments.length() == 1 || arguments.length() == 2 else {
    raise UnsupportedExpression("textureDimensions expects 1 or 2 arguments")
  }
  let image = self.lower_function_expression_node(arguments[0], function, scope)
  let level = if arguments.length() == 2 {
    Some(self.lower_texture_mip_level_argument(arguments[1], function, scope))
  } else {
    None
  }
  function.expressions.append(ImageQuery(image, Size(level)))
}

///|
fn WgslIrLowerer::lower_image_query_kind_expression(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  function : Function,
  scope : WgslIrFunctionScope,
  query : ImageQuery,
  callee : String,
) -> Handle raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments.length() == 1 else {
    raise UnsupportedExpression("\{callee} expects 1 argument")
  }
  let image = self.lower_function_expression_node(arguments[0], function, scope)
  function.expressions.append(ImageQuery(image, query))
}