// 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 WgslIrLowerer::lower_type_ref(
  self : WgslIrLowerer,
  ty_ref : @ast.WgslTypeRef,
) -> Handle raise WgslIrLowerError {
  let key = ty_ref.canonical_text()
  match self.type_cache.get(key) {
    Some(handle) => handle
    None => {
      let handle = self.lower_uncached_type_ref(ty_ref)
      self.type_cache.set(key, handle)
      handle
    }
  }
}

///|
fn WgslIrLowerer::lower_function_type_ref(
  self : WgslIrLowerer,
  ty_ref : @ast.WgslTypeRef,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  if ty_ref.name() == "array" {
    self.lower_function_array_type_ref(ty_ref, function, scope)
  } else {
    self.lower_type_ref(ty_ref)
  }
}

///|
fn WgslIrLowerer::lower_function_array_type_ref(
  self : WgslIrLowerer,
  ty_ref : @ast.WgslTypeRef,
  function : Function,
  scope : WgslIrFunctionScope,
) -> Handle raise WgslIrLowerError {
  let args = ty_ref.template_arguments()
  guard args is [Type(element_ty), .. rest] else {
    raise UnsupportedType(ty_ref.canonical_text())
  }
  let element = self.lower_function_type_ref(element_ty, function, scope)
  let size = match rest {
    [size_arg, ..] =>
      self.lower_function_type_template_array_size(size_arg, function, scope)
    _ => Dynamic
  }
  let layout = self.type_layout(element)
  let stride = wgsl_ir_align_to(layout.size, layout.align)
  self.intern_type(
    "array<\{self.type_cache_name(element)}, \{wgsl_ir_array_size_key(size)}>",
    Array(element, size, stride),
  )
}

///|
fn WgslIrLowerer::lower_uncached_type_ref(
  self : WgslIrLowerer,
  ty_ref : @ast.WgslTypeRef,
) -> Handle raise WgslIrLowerError {
  let name = ty_ref.name()
  match self.type_cache.get(name) {
    Some(handle) => handle
    None =>
      match wgsl_ir_builtin_scalar_alias(name) {
        Some(inner) => self.intern_type(name, inner)
        None =>
          match wgsl_ir_builtin_vector_alias(name) {
            Some(inner) => self.intern_type(name, inner)
            None =>
              match wgsl_ir_builtin_matrix_alias(name) {
                Some(inner) => self.intern_type(name, inner)
                None =>
                  match wgsl_ir_predeclared_result_type_from_name(name) {
                    Some(predeclared) =>
                      self.predeclared_struct_type(predeclared)
                    None => self.lower_composite_type_ref(ty_ref)
                  }
              }
          }
      }
  }
}

///|
fn WgslIrLowerer::lower_composite_type_ref(
  self : WgslIrLowerer,
  ty_ref : @ast.WgslTypeRef,
) -> Handle raise WgslIrLowerError {
  let name = ty_ref.name()
  match name {
    "vec2" | "vec3" | "vec4" => {
      let scalar = self.required_scalar_type_argument(ty_ref)
      self.intern_type(
        ty_ref.canonical_text(),
        Vector(wgsl_ir_vector_size_from_name(name), scalar),
      )
    }
    "mat2x2"
    | "mat2x3"
    | "mat2x4"
    | "mat3x2"
    | "mat3x3"
    | "mat3x4"
    | "mat4x2"
    | "mat4x3"
    | "mat4x4" => {
      let scalar = self.required_scalar_type_argument(ty_ref)
      let (columns, rows) = wgsl_ir_matrix_size_from_name(name)
      self.intern_type(ty_ref.canonical_text(), Matrix(columns, rows, scalar))
    }
    "coop_mat8x8" | "coop_mat16x16" => {
      let (columns, rows, scalar, role) = self.required_cooperative_matrix_type(
        ty_ref,
      )
      self.intern_type(
        ty_ref.canonical_text(),
        CooperativeMatrix(columns, rows, scalar, role),
      )
    }
    "array" => {
      let args = ty_ref.template_arguments()
      guard args is [Type(element_ty), .. rest] else {
        raise UnsupportedType(ty_ref.canonical_text())
      }
      let element = self.lower_type_ref(element_ty)
      let size = match rest {
        [size_arg, ..] => self.lower_type_template_array_size(size_arg)
        _ => Dynamic
      }
      let layout = self.type_layout(element)
      let stride = wgsl_ir_align_to(layout.size, layout.align)
      self.intern_type(ty_ref.canonical_text(), Array(element, size, stride))
    }
    "ptr" => {
      let args = ty_ref.template_arguments()
      guard args is [Type(space_ref), Type(target_ref), .. rest] else {
        raise UnsupportedType(ty_ref.canonical_text())
      }
      let space = if space_ref.name() == "storage" {
        match rest {
          [Type(access_ref), ..] =>
            AddressSpace::Storage(
              wgsl_ir_storage_access_from_name(access_ref.name()),
            )
          _ => Storage(StorageAccess::load())
        }
      } else {
        wgsl_ir_address_space_from_type_name(space_ref.name())
      }
      self.intern_type(
        ty_ref.canonical_text(),
        Pointer(self.lower_type_ref(target_ref), space),
      )
    }
    "atomic" => {
      let scalar = self.required_scalar_type_argument(ty_ref)
      self.intern_type(ty_ref.canonical_text(), Atomic(scalar))
    }
    "sampler" => self.intern_type(name, Sampler(false))
    "sampler_comparison" => self.intern_type(name, Sampler(true))
    "acceleration_structure" =>
      self.intern_type(
        ty_ref.canonical_text(),
        AccelerationStructure(wgsl_ir_type_ref_uses_vertex_return(ty_ref)),
      )
    "ray_query" =>
      self.intern_type(
        ty_ref.canonical_text(),
        RayQuery(wgsl_ir_type_ref_uses_vertex_return(ty_ref)),
      )
    "texture_1d"
    | "texture_2d"
    | "texture_2d_array"
    | "texture_3d"
    | "texture_cube"
    | "texture_cube_array"
    | "texture_multisampled_2d" => {
      let scalar = self.required_scalar_type_argument(ty_ref)
      self.intern_type(
        ty_ref.canonical_text(),
        Image(
          wgsl_ir_image_dimension_from_name(name),
          name.contains("array"),
          Sampled(scalar.kind, name.contains("multisampled")),
        ),
      )
    }
    "texture_depth_2d"
    | "texture_depth_2d_array"
    | "texture_depth_cube"
    | "texture_depth_cube_array"
    | "texture_depth_multisampled_2d" =>
      self.intern_type(
        name,
        Image(
          wgsl_ir_image_dimension_from_name(name),
          name.contains("array"),
          Depth(name.contains("multisampled")),
        ),
      )
    "texture_external" => self.intern_type(name, Image(D2, false, External))
    "texture_storage_1d"
    | "texture_storage_2d"
    | "texture_storage_2d_array"
    | "texture_storage_3d" => {
      let args = ty_ref.template_arguments()
      guard args is [Type(format_ref), .. rest] else {
        raise UnsupportedType(ty_ref.canonical_text())
      }
      let access = match rest {
        [Type(access_ref), ..] =>
          wgsl_ir_storage_access_from_name(access_ref.name())
        _ => StorageAccess::load()
      }
      self.intern_type(
        ty_ref.canonical_text(),
        Image(
          wgsl_ir_image_dimension_from_name(name),
          name.contains("array"),
          Storage(wgsl_ir_storage_format_from_name(format_ref.name()), access),
        ),
      )
    }
    "binding_array" => {
      let args = ty_ref.template_arguments()
      guard args is [Type(element_ty), .. rest] else {
        raise UnsupportedType(ty_ref.canonical_text())
      }
      let size = match rest {
        [size_arg, ..] => self.lower_type_template_array_size(size_arg)
        _ => Dynamic
      }
      self.intern_type(
        ty_ref.canonical_text(),
        BindingArray(self.lower_type_ref(element_ty), size),
      )
    }
    _ =>
      raise UnsupportedType("\{ty_ref.canonical_text()} before \{ty_ref.end()}")
  }
}

///|
fn WgslIrLowerer::required_cooperative_matrix_type(
  self : WgslIrLowerer,
  ty_ref : @ast.WgslTypeRef,
) -> (CooperativeSize, CooperativeSize, Scalar, CooperativeRole) raise WgslIrLowerError {
  let (columns, rows) = match ty_ref.name() {
    "coop_mat8x8" => (CooperativeSize::Eight, CooperativeSize::Eight)
    "coop_mat16x16" => (Sixteen, Sixteen)
    _ => raise UnsupportedType(ty_ref.canonical_text())
  }
  match ty_ref.template_arguments() {
    [Type(scalar_ref), Type(role_ref)] =>
      (
        columns,
        rows,
        self.scalar_from_type_ref(scalar_ref),
        wgsl_ir_cooperative_role_from_name(role_ref.name()),
      )
    _ => raise UnsupportedType(ty_ref.canonical_text())
  }
}

///|
fn WgslIrLowerer::cooperative_matrix_type_handle(
  self : WgslIrLowerer,
  columns : CooperativeSize,
  rows : CooperativeSize,
  scalar : Scalar,
  role : CooperativeRole,
) -> Handle {
  let key = "coop_mat\{wgsl_ir_cooperative_size_name(columns)}x\{wgsl_ir_cooperative_size_name(rows)}<\{wgsl_ir_scalar_name(scalar.kind, scalar.width)}, \{wgsl_ir_cooperative_role_name(role)}>"
  self.intern_type(key, CooperativeMatrix(columns, rows, scalar, role))
}

///|
fn wgsl_ir_cooperative_role_from_name(
  name : String,
) -> CooperativeRole raise WgslIrLowerError {
  match name {
    "A" => A
    "B" => B
    "C" => C
    _ => raise UnsupportedType("unsupported cooperative matrix role: \{name}")
  }
}

///|
pub fn wgsl_ir_cooperative_role_name(role : CooperativeRole) -> String {
  match role {
    A => "A"
    B => "B"
    C => "C"
  }
}

///|
pub fn wgsl_ir_cooperative_size_name(size : CooperativeSize) -> String {
  match size {
    Eight => "8"
    Sixteen => "16"
  }
}

///|
fn WgslIrLowerer::lower_type_template_array_size(
  self : WgslIrLowerer,
  argument : @ast.WgslTypeArgument,
) -> ArraySize raise WgslIrLowerError {
  match argument {
    Expression(expr) =>
      match wgsl_ir_int_literal_from_expr_ref(expr) {
        Some(value) => Constant(value)
        None => {
          let handle = self.lower_global_expression_ref(expr)
          self.lower_constant_array_size_expression(handle)
        }
      }
    Type(ty_ref) => self.lower_type_template_array_size_type_ref(ty_ref)
  }
}

///|
fn WgslIrLowerer::lower_function_type_template_array_size(
  self : WgslIrLowerer,
  argument : @ast.WgslTypeArgument,
  function : Function,
  scope : WgslIrFunctionScope,
) -> ArraySize raise WgslIrLowerError {
  match argument {
    Expression(expr) =>
      match wgsl_ir_int_literal_from_expr_ref(expr) {
        Some(value) => Constant(value)
        None => {
          let handle = self.lower_function_expression_ref(expr, function, scope)
          self.lower_function_constant_array_size_expression(function, handle)
        }
      }
    Type(ty_ref) =>
      self.lower_function_type_template_array_size_type_ref(
        ty_ref, function, scope,
      )
  }
}

///|
fn WgslIrLowerer::lower_function_type_template_array_size_type_ref(
  self : WgslIrLowerer,
  ty_ref : @ast.WgslTypeRef,
  function : Function,
  scope : WgslIrFunctionScope,
) -> ArraySize raise WgslIrLowerError {
  let name = ty_ref.name()
  let literal = wgsl_ir_array_size_literal_from_type_name(name)
  match literal {
    Some(value) => Constant(value)
    None =>
      match scope.local_constants.get(name) {
        Some({ value: LazyConstant(local_constant), .. }) => {
          let handle = match local_constant.normalized {
            Some(normalized) =>
              function.expressions.append_with_origin(
                normalized.expression,
                normalized.origin,
              )
            None =>
              self.lower_function_expression_ref_with_expected(
                local_constant.expr_ref,
                function,
                scope,
                local_constant.explicit_ty,
              )
          }
          match scope.local_constants.get(name) {
            Some(cell) =>
              cell.value = ExpressionAlias(handle, Some(local_constant))
            None => ()
          }
          self.lower_function_constant_array_size_expression(function, handle)
        }
        Some({ value: ExpressionAlias(handle, _), .. }) =>
          self.lower_function_constant_array_size_expression(function, handle)
        None => self.lower_type_template_array_size_type_ref(ty_ref)
      }
  }
}

///|
fn WgslIrLowerer::lower_type_template_array_size_type_ref(
  self : WgslIrLowerer,
  ty_ref : @ast.WgslTypeRef,
) -> ArraySize raise WgslIrLowerError {
  let name = ty_ref.name()
  let literal = wgsl_ir_array_size_literal_from_type_name(name)
  match literal {
    Some(value) => Constant(value)
    None =>
      match self.ensure_constant_declaration_lowered(name) {
        Some(handle) =>
          self.lower_constant_array_size_from_constant_handle(handle)
        None =>
          match self.ensure_override_declaration_predeclared(name) {
            Some(handle) =>
              Pending(
                self.shader_module.append_global_expression(Override(handle)),
              )
            None => Dynamic
          }
      }
  }
}

///|
fn WgslIrLowerer::lower_constant_array_size_from_constant_handle(
  self : WgslIrLowerer,
  handle : Handle,
) -> ArraySize raise WgslIrLowerError {
  match self.shader_module.constants.items.get(handle.index()) {
    Some(constant) => self.lower_constant_array_size_expression(constant.init)
    None => raise UnsupportedType("array size references missing constant")
  }
}

///|
fn WgslIrLowerer::lower_constant_array_size_expression(
  self : WgslIrLowerer,
  handle : Handle,
) -> ArraySize raise WgslIrLowerError {
  let evaluated = Some(self.eval_global_integer_expression(handle)) catch {
    _ => None
  }
  match evaluated {
    Some(value) => Constant(value.to_non_negative_int())
    None => Pending(handle)
  }
}

///|
fn wgsl_ir_array_size_key(size : ArraySize) -> String {
  match size {
    Constant(value) => "\{value}"
    Dynamic => ""
    Pending(handle) => "expr#\{handle.index()}"
  }
}

///|
fn wgsl_ir_array_size_literal_from_type_name(name : String) -> Int32? {
  let text = if name.length() > 0 {
    let suffix = name[name.length() - 1:]
    if suffix == "u" || suffix == "i" {
      name[:name.length() - 1]
    } else {
      name
    }
  } else {
    name
  }
  Some(@string.from_str(text)) catch {
    _ => None
  }
}

///|
fn wgsl_ir_type_ref_uses_vertex_return(ty_ref : @ast.WgslTypeRef) -> Bool {
  for argument in ty_ref.template_arguments() {
    match argument {
      Type(type_arg) if type_arg.name() == "vertex_return" => return true
      _ => ()
    }
  }
  false
}

///|
fn WgslIrLowerer::required_scalar_type_argument(
  self : WgslIrLowerer,
  ty_ref : @ast.WgslTypeRef,
) -> Scalar raise WgslIrLowerError {
  match ty_ref.template_arguments() {
    [Type(arg), ..] => self.scalar_from_type_ref(arg)
    _ => raise UnsupportedType(ty_ref.canonical_text())
  }
}

///|
fn WgslIrLowerer::scalar_from_type_ref(
  self : WgslIrLowerer,
  ty_ref : @ast.WgslTypeRef,
) -> Scalar raise WgslIrLowerError {
  match ty_ref.name() {
    "bool" => Scalar(Bool, 1)
    "i32" => Scalar(Sint, 4)
    "i64" => Scalar(Sint, 8)
    "u32" => Scalar(Uint, 4)
    "u64" => Scalar(Uint, 8)
    "f32" => Scalar(Float, 4)
    "f16" => Scalar(Float, 2)
    _ => {
      let handle = self.lower_type_ref(ty_ref)
      match self.shader_module.types.get(handle) {
        Some(ty) =>
          match ty.inner {
            Scalar(scalar) => scalar
            _ => raise UnsupportedType(ty_ref.canonical_text())
          }
        None => raise UnsupportedType(ty_ref.canonical_text())
      }
    }
  }
}

///|
fn WgslIrLowerer::lower_templated_callee_type(
  self : WgslIrLowerer,
  callee : @ast.WgslExprNode,
) -> Handle? raise WgslIrLowerError {
  match callee.kind() {
    Template(base, template_arguments) =>
      match base.kind() {
        Identifier(name) => {
          let atom = @ast.WgslTypeAtom(
            Identifier,
            name.name(),
            name.start(),
            name.end(),
          )
          let ty_ref = @ast.WgslTypeRef::new_with_template_arguments(
            atom,
            template_arguments,
            callee.start(),
            callee.end(),
          )
          Some(self.lower_type_ref(ty_ref))
        }
        _ =>
          raise UnsupportedExpression(
            "template call callee must be a type identifier",
          )
      }
    _ => None
  }
}

///|
fn WgslIrLowerer::lower_untemplated_constructor_type_from_ast(
  self : WgslIrLowerer,
  name : String,
  arguments : Array[@ast.WgslExprNode],
  diagnostic_name : String,
) -> Handle raise WgslIrLowerError {
  match self.type_cache.get(name) {
    Some(handle) => handle
    None =>
      match wgsl_ir_builtin_vector_alias(name) {
        Some(inner) => self.intern_type(name, inner)
        None =>
          match wgsl_ir_builtin_matrix_alias(name) {
            Some(inner) => self.intern_type(name, inner)
            None =>
              match wgsl_ir_builtin_scalar_alias(name) {
                Some(inner) => self.intern_type(name, inner)
                None =>
                  self.lower_generic_constructor_type_from_ast(
                    name, arguments, diagnostic_name,
                  )
              }
          }
      }
  }
}

///|
fn WgslIrLowerer::lower_generic_constructor_type_from_ast(
  self : WgslIrLowerer,
  name : String,
  arguments : Array[@ast.WgslExprNode],
  diagnostic_name : String,
) -> Handle raise WgslIrLowerError {
  match name {
    "vec2" | "vec3" | "vec4" => {
      let scalar = self.constructor_scalar_from_ast_arguments(
        arguments, diagnostic_name,
      )
      self.intern_type(
        "\{name}<\{wgsl_ir_scalar_name(scalar.kind, scalar.width)}>",
        Vector(wgsl_ir_vector_size_from_name(name), scalar),
      )
    }
    "mat2x2"
    | "mat2x3"
    | "mat2x4"
    | "mat3x2"
    | "mat3x3"
    | "mat3x4"
    | "mat4x2"
    | "mat4x3"
    | "mat4x4" => {
      let scalar = self.constructor_scalar_from_ast_arguments(
        arguments, diagnostic_name,
      )
      let (columns, rows) = wgsl_ir_matrix_size_from_name(name)
      self.intern_type(
        "\{name}<\{wgsl_ir_scalar_name(scalar.kind, scalar.width)}>",
        Matrix(columns, rows, scalar),
      )
    }
    _ => raise UnsupportedType(name)
  }
}

///|
fn WgslIrLowerer::infer_array_constructor_type_from_ast(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  diagnostic_name : String,
) -> Handle raise WgslIrLowerError {
  let flattened = wgsl_ir_flatten_expression_arguments(arguments)
  guard flattened is [first, ..] else { raise MissingType(diagnostic_name) }
  let element_ty = self.infer_expression_type(first, diagnostic_name)
  self.intern_inferred_array_type(element_ty, flattened.length())
}

///|
fn WgslIrLowerer::constructor_scalar_from_ast_arguments(
  self : WgslIrLowerer,
  arguments : Array[@ast.WgslExprNode],
  diagnostic_name : String,
) -> Scalar raise WgslIrLowerError {
  let arguments = wgsl_ir_flatten_expression_arguments(arguments)
  guard arguments is [first, .. rest] else {
    raise MissingType(diagnostic_name)
  }
  let first_ty = self.infer_expression_type(first, diagnostic_name)
  let mut scalar = self.scalar_from_type_handle(first_ty, diagnostic_name)
  for argument in rest {
    let ty = self.infer_expression_type(argument, diagnostic_name)
    let next = self.scalar_from_type_handle(ty, diagnostic_name)
    scalar = self.merge_constructor_scalar(scalar, next, diagnostic_name)
  }
  scalar
}

///|
fn WgslIrLowerer::lower_untemplated_constructor_type_from_handles(
  self : WgslIrLowerer,
  name : String,
  arguments : Array[@ast.WgslExprNode],
  lowered_arguments : Array[Handle],
  function : Function,
) -> Handle raise WgslIrLowerError {
  match self.type_cache.get(name) {
    Some(handle) => handle
    None =>
      match wgsl_ir_builtin_vector_alias(name) {
        Some(inner) => self.intern_type(name, inner)
        None =>
          match wgsl_ir_builtin_matrix_alias(name) {
            Some(inner) => self.intern_type(name, inner)
            None =>
              match wgsl_ir_builtin_scalar_alias(name) {
                Some(inner) => self.intern_type(name, inner)
                None =>
                  self.lower_generic_constructor_type_from_handles(
                    name, arguments, lowered_arguments, function,
                  )
              }
          }
      }
  }
}

///|
fn WgslIrLowerer::lower_generic_constructor_type_from_handles(
  self : WgslIrLowerer,
  name : String,
  arguments : Array[@ast.WgslExprNode],
  lowered_arguments : Array[Handle],
  function : Function,
) -> Handle raise WgslIrLowerError {
  match name {
    "vec2" | "vec3" | "vec4" => {
      let scalar = self.constructor_scalar_from_handles(
        lowered_arguments, function, name,
      )
      self.intern_type(
        "\{name}<\{wgsl_ir_scalar_name(scalar.kind, scalar.width)}>",
        Vector(wgsl_ir_vector_size_from_name(name), scalar),
      )
    }
    "mat2x2"
    | "mat2x3"
    | "mat2x4"
    | "mat3x2"
    | "mat3x3"
    | "mat3x4"
    | "mat4x2"
    | "mat4x3"
    | "mat4x4" => {
      let scalar = self.constructor_scalar_from_handles(
        lowered_arguments, function, name,
      )
      let (columns, rows) = wgsl_ir_matrix_size_from_name(name)
      self.intern_type(
        "\{name}<\{wgsl_ir_scalar_name(scalar.kind, scalar.width)}>",
        Matrix(columns, rows, scalar),
      )
    }
    _ => self.lower_type_name_with_expression_arguments(name, arguments)
  }
}

///|
fn WgslIrLowerer::infer_array_constructor_type_from_handles(
  self : WgslIrLowerer,
  lowered_arguments : Array[Handle],
  function : Function,
  diagnostic_name : String,
) -> Handle raise WgslIrLowerError {
  guard lowered_arguments is [first, ..] else {
    raise MissingType(diagnostic_name)
  }
  guard self.function_expression_type(function, first) is Some(element_ty) else {
    raise MissingType(diagnostic_name)
  }
  self.intern_inferred_array_type(element_ty, lowered_arguments.length())
}

///|
fn WgslIrLowerer::constructor_scalar_from_handles(
  self : WgslIrLowerer,
  lowered_arguments : Array[Handle],
  function : Function,
  diagnostic_name : String,
) -> Scalar raise WgslIrLowerError {
  guard lowered_arguments is [first, .. rest] else {
    raise MissingType(diagnostic_name)
  }
  guard self.function_expression_type(function, first) is Some(ty) else {
    raise MissingType(diagnostic_name)
  }
  let mut scalar = self.scalar_from_type_handle(ty, diagnostic_name)
  for argument in rest {
    guard self.function_expression_type(function, argument) is Some(ty) else {
      raise MissingType(diagnostic_name)
    }
    let next = self.scalar_from_type_handle(ty, diagnostic_name)
    scalar = self.merge_constructor_scalar(scalar, next, diagnostic_name)
  }
  scalar
}

///|
fn WgslIrLowerer::merge_constructor_scalar(
  self : WgslIrLowerer,
  left : Scalar,
  right : Scalar,
  diagnostic_name : String,
) -> Scalar raise WgslIrLowerError {
  ignore(self)
  if !wgsl_ir_scalar_kind_is_abstract(left.kind) &&
    !wgsl_ir_scalar_kind_is_abstract(right.kind) {
    if left == right {
      return left
    }
    raise UnsupportedType(diagnostic_name)
  }
  if !wgsl_ir_scalar_kind_is_abstract(left.kind) {
    if wgsl_ir_abstract_scalar_can_convert_to(right.kind, left.kind) {
      return left
    }
    raise UnsupportedType(diagnostic_name)
  }
  if !wgsl_ir_scalar_kind_is_abstract(right.kind) {
    if wgsl_ir_abstract_scalar_can_convert_to(left.kind, right.kind) {
      return right
    }
    raise UnsupportedType(diagnostic_name)
  }
  match (left.kind, right.kind) {
    (AbstractFloat, _) | (_, AbstractFloat) => Scalar(AbstractFloat, 8)
    _ => left
  }
}

///|
fn wgsl_ir_abstract_scalar_can_convert_to(
  from : ScalarKind,
  to : ScalarKind,
) -> Bool {
  match (from, to) {
    (AbstractInt, Sint | Uint | Float | AbstractInt | AbstractFloat) => true
    (AbstractFloat, Float | AbstractFloat) => true
    _ => false
  }
}

///|
fn WgslIrLowerer::intern_inferred_array_type(
  self : WgslIrLowerer,
  element_ty : Handle,
  length : Int32,
) -> Handle raise WgslIrLowerError {
  let layout = self.type_layout(element_ty)
  let stride = wgsl_ir_align_to(layout.size, layout.align)
  self.intern_type(
    "array<\{self.type_cache_name(element_ty)}, \{length}>",
    Array(element_ty, Constant(length), stride),
  )
}

///|
fn WgslIrLowerer::local_declaration_storage_type(
  self : WgslIrLowerer,
  kind : @ast.WgslLocalDeclarationKind,
  ty : Handle,
) -> Handle raise WgslIrLowerError {
  match kind {
    Var | Let => self.concretize_abstract_storage_type(ty)
    Const => ty
  }
}

///|
fn WgslIrLowerer::concretize_abstract_storage_type(
  self : WgslIrLowerer,
  ty : Handle,
) -> Handle raise WgslIrLowerError {
  match self.shader_module.types.items.get(ty.index()) {
    Some(type_) =>
      match type_.inner {
        Scalar(scalar) =>
          match self.concretize_abstract_storage_scalar(scalar) {
            Some(concrete) => self.scalar_type_handle(concrete)
            None => ty
          }
        Vector(size, scalar) =>
          match self.concretize_abstract_storage_scalar(scalar) {
            Some(concrete) => self.vector_type_handle(size, concrete)
            None => ty
          }
        Matrix(columns, rows, scalar) =>
          match self.concretize_abstract_storage_scalar(scalar) {
            Some(concrete) =>
              self.intern_type(
                "mat\{columns.width()}x\{rows.width()}<\{wgsl_ir_scalar_name(concrete.kind, concrete.width)}>",
                Matrix(columns, rows, concrete),
              )
            None => ty
          }
        Array(element, size, _) => {
          let concrete_element = self.concretize_abstract_storage_type(element)
          if concrete_element == element {
            ty
          } else {
            let layout = self.type_layout(concrete_element)
            let stride = wgsl_ir_align_to(layout.size, layout.align)
            self.intern_type(
              "array<\{self.type_key(concrete_element)}, \{wgsl_ir_array_size_key(size)}>",
              Array(concrete_element, size, stride),
            )
          }
        }
        BindingArray(element, size) => {
          let concrete_element = self.concretize_abstract_storage_type(element)
          if concrete_element == element {
            ty
          } else {
            self.intern_type(
              "binding_array<\{self.type_key(concrete_element)}, \{wgsl_ir_array_size_key(size)}>",
              BindingArray(concrete_element, size),
            )
          }
        }
        _ => ty
      }
    None => ty
  }
}

///|
fn WgslIrLowerer::concretize_abstract_storage_scalar(
  self : WgslIrLowerer,
  scalar : Scalar,
) -> Scalar? {
  ignore(self)
  match scalar.kind {
    AbstractInt => Some(Scalar(Sint, 4))
    AbstractFloat => Some(Scalar(Float, 4))
    _ => None
  }
}

///|
fn WgslIrLowerer::type_cache_name(
  self : WgslIrLowerer,
  handle : Handle,
) -> String raise WgslIrLowerError {
  match self.shader_module.types.get(handle) {
    Some(type_) =>
      match type_.name {
        Some(name) => name
        None => "type_\{handle.index()}"
      }
    None => raise MissingType("type handle")
  }
}

///|
fn WgslIrLowerer::scalar_from_type_handle(
  self : WgslIrLowerer,
  ty : Handle,
  diagnostic_name : String,
) -> Scalar raise WgslIrLowerError {
  match self.shader_module.types.get(ty) {
    Some(type_) =>
      match type_.inner {
        Scalar(scalar) => scalar
        Vector(_, scalar) => scalar
        Matrix(_, _, scalar) => scalar
        _ => raise MissingType(diagnostic_name)
      }
    None => raise MissingType(diagnostic_name)
  }
}

///|
priv struct WgslIrLayout {
  align : Int32
  size : Int32
}