///|
fn WgslComposeSession::record_graph_planned_request(
  self : WgslComposeSession,
  importer_rel_path : String,
  request : WgslImportRequest,
) -> Unit {
  self.graph_planned_requests.add(
    wgsl_import_request_visit_key(importer_rel_path, request),
  )
  if !request.import_full_module {
    for item in request.requested_items {
      self.graph_planned_requests.add(
        wgsl_import_request_item_visit_key(importer_rel_path, request, item),
      )
    }
  }
}

///|
fn WgslComposeSession::graph_request_is_planned(
  self : WgslComposeSession,
  importer_rel_path : String,
  request : WgslImportRequest,
) -> Bool {
  if self.graph_planned_requests.contains(
      wgsl_import_request_visit_key(importer_rel_path, request),
    ) {
    return true
  }
  if request.import_full_module {
    return false
  }
  if request.requested_items.length() == 0 {
    return false
  }
  for item in request.requested_items {
    if !self.graph_planned_requests.contains(
        wgsl_import_request_item_visit_key(importer_rel_path, request, item),
      ) {
      return false
    }
  }
  true
}

///|
fn WgslComposeSession::record_graph_planned_full_module(
  self : WgslComposeSession,
  rel_path : String,
  alias_name : String,
) -> Unit {
  self.graph_planned_requests.add(
    wgsl_full_module_visit_key(rel_path, alias_name),
  )
}

///|
fn WgslComposeSession::graph_full_module_is_planned(
  self : WgslComposeSession,
  rel_path : String,
  alias_name : String,
) -> Bool {
  self.graph_planned_requests.contains(
    wgsl_full_module_visit_key(rel_path, alias_name),
  )
}

///|
fn wgsl_compose_assigned_final_name(
  module_path : String,
  alias_prefixes : Array[String],
  original_name : String,
) -> String {
  if module_path != "" {
    wgsl_naga_oil_decorated_final_name(module_path, original_name)
  } else if alias_prefixes.length() == 0 {
    original_name
  } else {
    let prefix = alias_prefixes.join("__")
    "\{prefix}__\{original_name}"
  }
}

///|
fn WgslComposeSession::module_rel_path_for_module_path(
  self : WgslComposeSession,
  module_path : String,
) -> String? {
  self.modules.get(module_path)
}

///|
fn WgslComposeSession::module_path_for_rel_path(
  self : WgslComposeSession,
  rel_path : String,
) -> String? {
  let normalized_rel = normalize_shader_rel_path(rel_path)
  for entry in self.modules.iter() {
    let (module_path, module_rel_path) = entry
    if normalize_shader_rel_path(module_rel_path) == normalized_rel {
      return Some(module_path)
    }
  }
  None
}

///|
fn record_wgsl_virtual_override_final_names_from_source(
  session : WgslComposeSession,
  rel_path : String,
  source : String,
) -> Unit {
  let targets = collect_wgsl_virtual_override_targets_strict(source) catch {
    _ => return
  }
  record_wgsl_virtual_override_final_names_from_targets(
    session, rel_path, targets,
  )
}

///|
fn record_wgsl_virtual_override_final_names_from_targets(
  session : WgslComposeSession,
  rel_path : String,
  targets : Array[String],
) -> Unit {
  let composing_root = session.graph_planning_rel_paths.length() <= 1 &&
    session.active_rel_paths.length() <= 1
  let override_module_path = match session.module_path_for_rel_path(rel_path) {
    Some(module_path) if !composing_root => module_path
    _ => ""
  }
  for raw_target in targets {
    let (target_module_path, original_name) = match
      split_last_identifier(raw_target) {
      Some((module_path, name)) => (module_path, name)
      None => (override_module_path, raw_target)
    }
    if original_name == "" {
      continue
    }
    let target_rel_path = if target_module_path == "" {
      rel_path
    } else {
      match session.module_rel_path_for_module_path(target_module_path) {
        Some(resolved_rel_path) => resolved_rel_path
        None => continue
      }
    }
    let target_name = wgsl_naga_oil_decorated_final_name(
      target_module_path, original_name,
    )
    let override_name = wgsl_naga_oil_virtual_override_final_name(
      target_module_path, override_module_path, original_name,
    )
    session.record_virtual_override_final_name(
      raw_target, target_rel_path, target_name, override_name,
    )
  }
}

///|
fn record_wgsl_planned_final_names_from_extraction(
  session : WgslComposeSession,
  rel_path : String,
  module_path_override : String?,
  alias_prefixes : Array[String],
  extraction : WgslModuleItemExtraction,
) -> Unit {
  if rel_path == "" {
    return
  }
  for origin in extraction.origins() {
    let identity = WgslComposeSymbolIdentity(rel_path, origin.name())
    let module_path = match module_path_override {
      Some(value) => value
      None =>
        match session.module_path_for_rel_path(rel_path) {
          Some(value) => value
          None => ""
        }
    }
    let final_name = wgsl_compose_assigned_final_name(
      module_path,
      alias_prefixes,
      origin.name(),
    )
    if alias_prefixes.length() == 0 {
      session.assign_canonical_final_name(identity, final_name)
    } else {
      session.assign_alias_final_name(
        WgslComposeAliasSymbolIdentity(
          rel_path,
          alias_prefixes.join("__"),
          origin.name(),
        ),
        final_name,
      )
    }
  }
}

///|
fn record_wgsl_planned_final_names_from_source(
  session : WgslComposeSession,
  rel_path : String,
  module_path_override : String?,
  source : String,
  alias_prefixes : Array[String],
) -> Unit {
  record_wgsl_virtual_override_final_names_from_source(
    session, rel_path, source,
  )
  let extraction = extract_wgsl_module_item_origins_strict(source, []) catch {
    _ => return
  }
  record_wgsl_planned_final_names_from_extraction(
    session, rel_path, module_path_override, alias_prefixes, extraction,
  )
}

///|
fn record_wgsl_planned_final_names_from_processed(
  session : WgslComposeSession,
  rel_path : String,
  module_path_override : String?,
  alias_prefixes : Array[String],
  processed : WgslProcessedComposeModule,
) -> Unit {
  let targets = processed.virtual_override_target_names() catch { _ => [] }
  record_wgsl_virtual_override_final_names_from_targets(
    session, rel_path, targets,
  )
  let extraction = processed.item_extraction([]) catch { _ => return }
  record_wgsl_planned_final_names_from_extraction(
    session, rel_path, module_path_override, alias_prefixes, extraction,
  )
}

///|
fn WgslImportGraphBuilder::plan_with_path(
  self : WgslImportGraphBuilder,
  current_rel_path : String,
  current_module_path : String?,
  source : String,
  inherited_defs : WgslActiveComposeDefs?,
  alias_prefixes : Array[String],
  declared_imports : Bool,
  source_already_prepared : Bool,
) -> Unit raise ComposerError {
  let maybe_registered_processed = match
    self.composer.registry.registered_source(current_rel_path) {
    Some(registered_source) if registered_source == source =>
      Some(
        self.composer.processed_registered_wgsl_compose_module(
          current_rel_path,
          self.session,
          inherited_defs,
        ),
      )
    _ => None
  }
  let (active_defs, prepared) = match maybe_registered_processed {
    Some(processed) => (processed.active_defs, processed.prepared)
    None => {
      let active_defs = self.composer.active_compose_defs_for_rel_path(
        current_rel_path,
        self.session,
        inherited_defs,
      )
      let prepared = if source_already_prepared {
        prepare_active_wgsl_compose_source(source, self.session)
      } else {
        merge_wgsl_source_local_shader_defs(
          active_defs,
          source,
          self.session,
          normalize_shader_rel_path(current_rel_path),
        )
        prepare_wgsl_compose_source(
          source,
          current_rel_path,
          self.session,
          active_defs,
        )
      }
      (active_defs, prepared)
    }
  }
  let explicit_requests = if declared_imports {
    self.composer.build_wgsl_declared_import_requests_from_source_directives(
      current_rel_path,
      prepared.active_source,
      self.session,
      active_defs,
    )
  } else {
    self.composer.build_wgsl_import_requests_from_source_directives(
      current_rel_path,
      prepared,
      self.session,
      active_defs,
    )
  }
  match maybe_registered_processed {
    Some(processed) => {
      self.session.record_processed_semantic_facts(processed)
      record_wgsl_planned_final_names_from_processed(
        self.session,
        current_rel_path,
        current_module_path,
        alias_prefixes,
        processed,
      )
    }
    None =>
      record_wgsl_planned_final_names_from_source(
        self.session,
        current_rel_path,
        current_module_path,
        prepared.active_source,
        alias_prefixes,
      )
  }
  let implicit_requests = self.composer.build_wgsl_implicit_import_requests(
    current_rel_path,
    prepared,
    self.session,
    active_defs,
  )
  self.plan_import_request_batch(
    current_rel_path, implicit_requests, active_defs, alias_prefixes,
  )
  self.plan_import_request_batch(
    current_rel_path, explicit_requests, active_defs, alias_prefixes,
  )
}

///|
fn WgslImportGraphBuilder::plan_import_request_batch(
  self : WgslImportGraphBuilder,
  current_rel_path : String,
  requests : Array[WgslImportRequest],
  importer_defs : WgslActiveComposeDefs,
  alias_prefixes : Array[String],
) -> Unit raise ComposerError {
  record_wgsl_import_request_batch_link_edges(
    self.session.link_graph,
    current_rel_path,
    requests,
  )
  for request in requests {
    if self.session.graph_request_is_planned(current_rel_path, request) {
      continue
    }
    self.session.record_graph_planned_request(current_rel_path, request)
    let request_alias_prefixes = alias_prefixes.copy()
    if request.alias_name != "" {
      request_alias_prefixes.push(
        wgsl_semantic_import_alias_prefix(request.alias_name),
      )
    }
    let request_alias_scope = request_alias_prefixes.join("__")
    if request.import_full_module &&
      self.session.graph_full_module_is_planned(
        request.rel_path,
        request_alias_scope,
      ) {
      continue
    }
    if wgsl_path_stack_contains(
        self.session.graph_planning_rel_paths,
        request.rel_path,
      ) {
      raise ImportCycle(
        wgsl_import_cycle_string(
          self.session.graph_planning_rel_paths,
          request.rel_path,
        ),
      )
    }
    if request.import_full_module {
      self.session.record_graph_planned_full_module(
        request.rel_path,
        request_alias_scope,
      )
    }
    let processed = self.composer.processed_registered_wgsl_compose_module(
      request.rel_path,
      self.session,
      Some(importer_defs),
    )
    self.session.record_processed_semantic_facts(processed)
    let raw_source = processed.raw_source
    self.session.graph_planning_rel_paths.push(request.rel_path)
    if request.import_full_module {
      self.plan_with_path(
        request.rel_path,
        Some(request.module_path),
        raw_source,
        Some(importer_defs),
        request_alias_prefixes,
        true,
        false,
      )
    } else {
      let import_defs = processed.active_defs
      let prepared = processed.prepared
      let extraction = processed.item_extraction(request.requested_items)
      let extracted_source = extraction.source()
      if extracted_source != "" {
        record_wgsl_planned_final_names_from_extraction(
          self.session,
          request.rel_path,
          Some(request.module_path),
          request_alias_prefixes,
          extraction,
        )
        let requests = self.composer.build_wgsl_import_requests_from_emitted_item_slice(
          request.rel_path,
          extracted_source,
          prepared.active_source,
          Some(extraction),
          self.session,
          import_defs,
        )
        self.plan_import_request_batch(
          request.rel_path,
          requests,
          import_defs,
          request_alias_prefixes,
        )
      } else {
        let targets : Array[WgslImportTarget] = []
        collect_wgsl_import_targets_from_source_directives(
          self.session,
          prepared.active_source,
          targets,
        )
        let dependency_requests = self.composer.build_wgsl_import_requests_from_targets(
          request.rel_path,
          targets,
          prepared,
          self.session,
          import_defs,
          false,
        )
        let requests : Array[WgslImportRequest] = []
        let dependency_facts = prepared.active_semantic_facts(self.session)
        self.composer.merge_wgsl_non_resource_dependency_requests(
          requests,
          dependency_requests,
          dependency_facts,
          self.session,
          import_defs,
        )
        self.plan_import_request_batch(
          request.rel_path,
          requests,
          import_defs,
          request_alias_prefixes,
        )
      }
    }
    self.session.graph_planning_rel_paths.pop() |> ignore
  }
}

///|
fn WgslImportGraphBuilder::complete_execution(
  self : WgslImportGraphBuilder,
  current_rel_path : String,
  source : String,
  inherited_defs : WgslActiveComposeDefs?,
) -> Unit raise ComposerError {
  if self.session.graph_execution_complete {
    return
  }
  self.session.graph_planning_rel_paths.push(current_rel_path)
  self.plan_with_path(
    current_rel_path,
    None,
    source,
    inherited_defs,
    [],
    false,
    false,
  )
  self.session.graph_planning_rel_paths.pop() |> ignore
  self.session.graph_execution_complete = true
}