///|
fn WgslIrLowerer::predeclare_global_type_declaration(
  self : WgslIrLowerer,
  declaration : @ast.WgslGlobalDecl,
) -> Unit {
  match declaration.data() {
    Struct(struct_decl) => self.predeclare_struct_declaration(struct_decl)
    _ => ()
  }
}

///|
fn WgslIrLowerer::predeclare_global_type_alias(
  self : WgslIrLowerer,
  declaration : @ast.WgslGlobalDecl,
) -> Unit raise WgslIrLowerError {
  match declaration.data() {
    TypeAlias(alias_decl) => self.lower_type_alias(alias_decl)
    _ => ()
  }
}

///|
fn WgslIrLowerer::predeclare_global_value_declaration(
  self : WgslIrLowerer,
  declaration : @ast.WgslGlobalDecl,
) -> Unit raise WgslIrLowerError {
  match declaration.data() {
    Function(function) => self.predeclare_function_declaration(function)
    GlobalVariable(global) => self.predeclare_global_variable(global)
    Const(constant) => self.predeclare_constant_declaration(constant)
    Override(override_decl) =>
      self.predeclare_override_declaration(override_decl)
    _ => ()
  }
}

///|
fn WgslIrLowerer::lower_global_type_body_declaration(
  self : WgslIrLowerer,
  declaration : @ast.WgslGlobalDecl,
) -> Unit raise WgslIrLowerError {
  match declaration.data() {
    Struct(struct_decl) => self.lower_struct_declaration(struct_decl)
    TypeAlias(alias_decl) => self.lower_type_alias(alias_decl)
    _ => ()
  }
}

///|
fn WgslIrLowerer::predeclare_struct_declaration(
  self : WgslIrLowerer,
  struct_decl : @ast.WgslStruct,
) -> Unit {
  if self.type_cache.contains(struct_decl.name().name()) {
    return
  }
  let handle = self.shader_module.append_type({
    name: Some(struct_decl.name().name()),
    inner: Struct([], 1),
  })
  self.type_cache.set(struct_decl.name().name(), handle)
}

///|
fn WgslIrLowerer::predeclare_function_declaration(
  self : WgslIrLowerer,
  function : @ast.WgslFunction,
) -> Unit raise WgslIrLowerError {
  if self.function_names.contains(function.name().name()) {
    return
  }
  let result = match function.result() {
    Some(result) =>
      match result.ty_ref() {
        Some(ty_ref) =>
          Some(FunctionResult::{
            ty: self.lower_type_ref(ty_ref),
            binding: wgsl_ir_binding_from_attributes(result.attributes()),
          })
        None => None
      }
    None => None
  }
  let placeholder = Function::{
    name: Some(function.name().name()),
    source_start: function.fn_start(),
    arguments: [],
    result,
    local_variables: LocalVariableArena(),
    expressions: ExpressionArena(),
    named_expressions: [],
    body: Block(),
    diagnostic_filter_leaf: None,
  }
  let handle = self.shader_module.append_function(placeholder)
  self.function_names.set(function.name().name(), handle)
}

///|
fn WgslIrLowerer::predeclare_global_variable(
  self : WgslIrLowerer,
  global : @ast.WgslGlobalVariable,
) -> Unit raise WgslIrLowerError {
  let name = global.name().name()
  if self.global_names.contains(name) {
    return
  }
  let inferred_ty = match global.ty_ref() {
    Some(ty_ref) => self.lower_type_ref(ty_ref)
    None =>
      self.infer_local_type_from_initializer(name, global.initializer_ref())
  }
  let ty = self.concretize_abstract_storage_type(inferred_ty)
  let handle = self.shader_module.append_global_variable({
    name: Some(name),
    space: self.global_address_space(global.template_list(), ty),
    binding: wgsl_ir_resource_binding_from_attributes(global.attributes()),
    ty,
    init: None,
    memory_decorations: wgsl_ir_memory_decorations_from_attributes(
      global.attributes(),
    ),
  })
  self.global_names.set(name, handle)
}

///|
fn WgslIrLowerer::predeclare_constant_declaration(
  self : WgslIrLowerer,
  constant : @ast.WgslConst,
) -> Unit raise WgslIrLowerError {
  let name = constant.name().name()
  if self.constant_names.contains(name) {
    return
  }
  let ty = self.infer_constant_declaration_type(constant)
  let placeholder_init = self.shader_module.append_global_expression(
    ZeroValue(ty),
  )
  let handle = self.shader_module.append_constant({
    name: Some(name),
    source_start: constant.name().start(),
    ty,
    init: placeholder_init,
  })
  self.constant_names.set(name, handle)
}

///|
fn WgslIrLowerer::predeclare_override_declaration(
  self : WgslIrLowerer,
  override_decl : @ast.WgslOverride,
) -> Unit raise WgslIrLowerError {
  let name = override_decl.name().name()
  if self.override_names.contains(name) {
    return
  }
  if self.override_type_inference_stack.contains(name) {
    raise MissingType(name)
  }
  self.override_type_inference_stack.add(name)
  let ty = match override_decl.ty_ref() {
    Some(ty_ref) => self.lower_type_ref(ty_ref)
    None =>
      self.infer_local_type_from_initializer(
        name,
        override_decl.initializer_ref(),
      )
  }
  let handle = self.shader_module.append_override({
    name: Some(name),
    id: wgsl_ir_id_from_attributes(override_decl.attributes()),
    ty,
    init: None,
  })
  self.override_names.set(name, handle)
  ignore(self.override_type_inference_stack.remove(name))
}

///|
fn WgslIrLowerer::lower_global_declaration(
  self : WgslIrLowerer,
  declaration : @ast.WgslGlobalDecl,
) -> Unit raise WgslIrLowerError {
  match declaration.data() {
    Struct(_) | TypeAlias(_) => ()
    GlobalVariable(global) => self.lower_global_variable(global)
    Const(constant) => self.lower_constant(constant)
    ConstAssert(expr) => self.lower_global_const_assert(expr)
    Override(override_decl) => self.lower_override(override_decl)
    Function(function) => self.lower_function_declaration(function)
    Directive(directive) => self.lower_source_directive(directive)
    _ => ()
  }
}

///|
fn WgslIrLowerer::lower_source_directive(
  self : WgslIrLowerer,
  directive : @ast.WgslDirective,
) -> Unit {
  match directive.kind() {
    Enable | Requires | Diagnostic =>
      self.shader_module.append_directive(wgsl_ir_directive_text(directive))
  }
}

///|
fn wgsl_ir_directive_text(directive : @ast.WgslDirective) -> String {
  let out = StringBuilder()
  out.write_string(directive.name().name())
  if directive.has_tail() {
    if directive.has_required_tail_separator() {
      out.write_string(" ")
    }
    out.write_string(directive.tail())
  }
  out.write_string(";")
  out.to_string()
}

///|
fn WgslIrLowerer::lower_type_alias(
  self : WgslIrLowerer,
  alias_decl : @ast.WgslTypeAlias,
) -> Unit raise WgslIrLowerError {
  guard alias_decl.ty_ref() is Some(ty_ref) else {
    raise MissingType(alias_decl.name().name())
  }
  let target = self.lower_type_ref(ty_ref)
  self.type_cache.set(alias_decl.name().name(), target)
}

///|
fn WgslIrLowerer::lower_struct_declaration(
  self : WgslIrLowerer,
  struct_decl : @ast.WgslStruct,
) -> Unit raise WgslIrLowerError {
  let members : Array[StructMember] = []
  let mut offset = 0
  let mut max_align = 1
  for struct_member in struct_decl.members() {
    guard struct_member.ty_ref() is Some(member_ty_ref) else {
      raise MissingType(struct_member.name().name())
    }
    let ty = self.lower_type_ref(member_ty_ref)
    let layout = self.type_layout(ty)
    let member_size = wgsl_ir_struct_member_size(
      struct_member.attributes(),
      layout,
    )
    let member_align = wgsl_ir_struct_member_align(
      struct_member.attributes(),
      layout,
    )
    offset = wgsl_ir_align_to(offset, member_align)
    members.push({
      name: Some(struct_member.name().name()),
      ty,
      binding: wgsl_ir_binding_from_attributes(struct_member.attributes()),
      offset,
      size: if wgsl_ir_attribute_named(struct_member.attributes(), "size")
        is Some(_) {
        Some(member_size)
      } else {
        None
      },
      align: if wgsl_ir_attribute_named(struct_member.attributes(), "align")
        is Some(_) {
        Some(member_align)
      } else {
        None
      },
    })
    offset = offset + member_size
    if member_align > max_align {
      max_align = member_align
    }
  }
  let span = wgsl_ir_align_to(if offset == 0 { 1 } else { offset }, max_align)
  let ty = Type::{
    name: Some(struct_decl.name().name()),
    inner: Struct(members, span),
  }
  match self.type_cache.get(struct_decl.name().name()) {
    Some(handle) => self.shader_module.types.set(handle, ty)
    None => {
      let handle = self.shader_module.append_type(ty)
      self.type_cache.set(struct_decl.name().name(), handle)
    }
  }
}

///|
fn WgslIrLowerer::lower_global_variable(
  self : WgslIrLowerer,
  global : @ast.WgslGlobalVariable,
) -> Unit raise WgslIrLowerError {
  let inferred_ty = match global.ty_ref() {
    Some(ty_ref) => self.lower_type_ref(ty_ref)
    None =>
      self.infer_local_type_from_initializer(
        global.name().name(),
        global.initializer_ref(),
      )
  }
  let ty = self.concretize_abstract_storage_type(inferred_ty)
  let init = match global.initializer_ref() {
    Some(expr) =>
      Some(self.lower_global_expression_ref_with_expected(expr, Some(ty)))
    None => None
  }
  let variable = GlobalVariable::{
    name: Some(global.name().name()),
    space: self.global_address_space(global.template_list(), ty),
    binding: wgsl_ir_resource_binding_from_attributes(global.attributes()),
    ty,
    init,
    memory_decorations: wgsl_ir_memory_decorations_from_attributes(
      global.attributes(),
    ),
  }
  match self.global_names.get(global.name().name()) {
    Some(handle) => self.shader_module.global_variables.set(handle, variable)
    None => {
      let handle = self.shader_module.append_global_variable(variable)
      self.global_names.set(global.name().name(), handle)
    }
  }
}

///|
fn WgslIrLowerer::global_address_space(
  self : WgslIrLowerer,
  template_list : Array[@ast.WgslTypeRef],
  ty : Handle,
) -> AddressSpace raise WgslIrLowerError {
  if template_list.length() > 0 {
    return wgsl_ir_address_space_from_template_list(template_list)
  }
  if self.global_type_uses_handle_address_space(ty) {
    return Handle
  }
  Private
}

///|
fn WgslIrLowerer::global_type_uses_handle_address_space(
  self : WgslIrLowerer,
  ty : Handle,
) -> Bool {
  match self.shader_module.types.get(ty) {
    Some(type_) =>
      match type_.inner {
        Image(_, _, _) | Sampler(_) | AccelerationStructure(_) | RayQuery(_) =>
          true
        BindingArray(element, _) =>
          self.global_type_uses_handle_address_space(element)
        _ => false
      }
    None => false
  }
}

///|
fn WgslIrLowerer::lower_constant(
  self : WgslIrLowerer,
  constant : @ast.WgslConst,
) -> Unit raise WgslIrLowerError {
  let name = constant.name().name()
  if self.lowered_constants.contains(name) {
    return
  }
  if self.constant_lowering_stack.contains(name) {
    raise MissingType(name)
  }
  self.constant_lowering_stack.add(name)
  let explicit_ty = match constant.ty_ref() {
    Some(ty_ref) => Some(self.lower_type_ref(ty_ref))
    None => None
  }
  let (handle, ty) = match self.constant_names.get(name) {
    Some(handle) =>
      match self.shader_module.constants.items.get(handle.index()) {
        Some(existing) => (handle, existing.ty)
        None => raise MissingType(name)
      }
    None => {
      let ty = match explicit_ty {
        Some(ty) => ty
        None =>
          self.infer_local_type_from_initializer(
            name,
            constant.initializer_ref(),
          )
      }
      let placeholder_init = self.shader_module.append_global_expression(
        ZeroValue(ty),
      )
      let handle = self.shader_module.append_constant({
        name: Some(name),
        source_start: constant.name().start(),
        ty,
        init: placeholder_init,
      })
      self.constant_names.set(name, handle)
      (handle, ty)
    }
  }
  let init = match constant.initializer_ref() {
    Some(expr) =>
      self.lower_global_expression_ref_with_expected(expr, explicit_ty)
    None => self.shader_module.append_global_expression(ZeroValue(ty))
  }
  module_set_constant(self.shader_module, handle, {
    name: Some(name),
    source_start: constant.name().start(),
    ty,
    init,
  })
  self.lowered_constants.add(name)
  ignore(self.constant_lowering_stack.remove(name))
}

///|
fn WgslIrLowerer::ensure_constant_declaration_lowered(
  self : WgslIrLowerer,
  name : String,
) -> Handle? raise WgslIrLowerError {
  if self.constant_lowering_stack.contains(name) {
    raise MissingType(name)
  }
  match self.constant_names.get(name) {
    Some(handle) =>
      if self.lowered_constants.contains(name) {
        Some(handle)
      } else {
        match self.constant_declarations.get(name) {
          Some(constant) => {
            self.lower_constant(constant)
            Some(handle)
          }
          None => Some(handle)
        }
      }
    None =>
      match self.constant_declarations.get(name) {
        Some(constant) => {
          self.lower_constant(constant)
          self.constant_names.get(name)
        }
        None => None
      }
  }
}

///|
fn WgslIrLowerer::ensure_override_declaration_predeclared(
  self : WgslIrLowerer,
  name : String,
) -> Handle? raise WgslIrLowerError {
  match self.override_names.get(name) {
    Some(handle) => Some(handle)
    None =>
      match self.override_declarations.get(name) {
        Some(override_decl) => {
          self.predeclare_override_declaration(override_decl)
          self.override_names.get(name)
        }
        None => None
      }
  }
}

///|
fn WgslIrLowerer::lower_global_const_assert(
  self : WgslIrLowerer,
  expr : @ast.WgslExprRef,
) -> Unit raise WgslIrLowerError {
  self.shader_module.append_const_assert(self.lower_global_expression_ref(expr))
}

///|
fn WgslIrLowerer::lower_override(
  self : WgslIrLowerer,
  override_decl : @ast.WgslOverride,
) -> Unit raise WgslIrLowerError {
  let explicit_ty = match override_decl.ty_ref() {
    Some(ty_ref) => Some(self.lower_type_ref(ty_ref))
    None => None
  }
  let ty = match explicit_ty {
    Some(ty) => ty
    None =>
      self.infer_local_type_from_initializer(
        override_decl.name().name(),
        override_decl.initializer_ref(),
      )
  }
  let override_ = Override::{
    name: Some(override_decl.name().name()),
    id: wgsl_ir_id_from_attributes(override_decl.attributes()),
    ty,
    init: match override_decl.initializer_ref() {
      Some(expr) =>
        Some(self.lower_global_expression_ref_with_expected(expr, explicit_ty))
      None => None
    },
  }
  match self.override_names.get(override_decl.name().name()) {
    Some(handle) => self.shader_module.set_override(handle, override_)
    None => {
      let handle = self.shader_module.append_override(override_)
      self.override_names.set(override_decl.name().name(), handle)
    }
  }
}