use super::GenerateStage;
use crate::chunk_graph::ChunkGraph;
use crate::utils::chunk::conflict_resolver::{ConflictResolver, deconflict_order_key};
use crate::utils::chunk::normalize_preserve_entry_signature;
use crate::utils::external_import_interop::external_import_needs_interop;
use itertools::{Itertools, multizip};
use oxc_index::{IndexVec, index_vec};
use oxc_str::CompactStr;
use rolldown_common::{
  ChunkIdx, ChunkKind, ChunkMeta, CrossChunkImportItem, EntryPointKind, ExportsKind, ImportKind,
  ImportRecordMeta, Module, ModuleIdx, NamedImport, OutputFormat, PostChunkOptimizationOperation,
  PreserveEntrySignatures, RUNTIME_HELPER_NAMES, SymbolRef, WrapKind,
};
use rolldown_utils::index_vec_ext::IndexVecRefExt as _;
use rolldown_utils::indexmap::{FxIndexMap, FxIndexSet};
use rolldown_utils::rayon::{ParallelBridge, ParallelIterator};
use rustc_hash::{FxHashMap, FxHashSet};

type IndexChunkDependedSymbols = IndexVec<ChunkIdx, FxIndexSet<SymbolRef>>;
type IndexChunkImportsFromExternalModules =
  IndexVec<ChunkIdx, FxHashMap<ModuleIdx, Vec<(ModuleIdx, NamedImport)>>>;
type IndexChunkAllImportsFromExternalModules = IndexVec<ChunkIdx, FxIndexSet<ModuleIdx>>;
type IndexChunkExportedSymbols = IndexVec<ChunkIdx, FxHashMap<SymbolRef, Vec<CompactStr>>>;
type IndexCrossChunkImports = IndexVec<ChunkIdx, FxHashSet<ChunkIdx>>;
type IndexCrossChunkDynamicImports = IndexVec<ChunkIdx, FxIndexSet<ChunkIdx>>;
type IndexImportsFromOtherChunks =
  IndexVec<ChunkIdx, FxHashMap<ChunkIdx, Vec<CrossChunkImportItem>>>;

impl GenerateStage<'_> {
  #[tracing::instrument(level = "debug", skip_all)]
  pub fn compute_cross_chunk_links(&mut self, chunk_graph: &mut ChunkGraph) {
    let mut index_chunk_depended_symbols: IndexChunkDependedSymbols =
      index_vec![FxIndexSet::<SymbolRef>::default(); chunk_graph.chunk_table.len()];
    let mut index_chunk_exported_symbols: IndexChunkExportedSymbols =
      index_vec![FxHashMap::<SymbolRef, Vec<CompactStr>>::default(); chunk_graph.chunk_table.len()];
    let mut index_chunk_direct_imports_from_external_modules: IndexChunkImportsFromExternalModules = index_vec![FxHashMap::<ModuleIdx, Vec<(ModuleIdx, NamedImport)>>::default(); chunk_graph.chunk_table.len()];
    // Used for cjs,umd,iife only
    let mut index_chunk_indirect_imports_from_external_modules: IndexChunkAllImportsFromExternalModules =
      index_vec![FxIndexSet::<ModuleIdx>::default(); chunk_graph.chunk_table.len()];

    let mut index_imports_from_other_chunks: IndexImportsFromOtherChunks = index_vec![FxHashMap::<ChunkIdx, Vec<CrossChunkImportItem>>::default(); chunk_graph.chunk_table.len()];
    let mut index_cross_chunk_imports: IndexCrossChunkImports =
      index_vec![FxHashSet::default(); chunk_graph.chunk_table.len()];
    let mut index_cross_chunk_dynamic_imports: IndexCrossChunkDynamicImports =
      index_vec![FxIndexSet::default(); chunk_graph.chunk_table.len()];

    self.collect_depended_symbols(
      chunk_graph,
      &mut index_chunk_depended_symbols,
      &mut index_chunk_direct_imports_from_external_modules,
      &mut index_cross_chunk_dynamic_imports,
    );

    self.compute_chunk_imports(
      chunk_graph,
      &index_chunk_depended_symbols,
      &index_chunk_direct_imports_from_external_modules,
      &mut index_chunk_exported_symbols,
      &mut index_cross_chunk_imports,
      &mut index_imports_from_other_chunks,
      &mut index_chunk_indirect_imports_from_external_modules,
    );
    self.deconflict_exported_names(chunk_graph, &index_chunk_exported_symbols);

    let index_sorted_cross_chunk_imports = index_cross_chunk_imports
      .par_iter_enumerated()
      .map(|(chunk_idx, cross_chunk_imports)| {
        // Include imports from `imports_from_other_chunks` which may have been
        // added during chunk merging optimization (PR #7194).
        // See: https://github.com/rolldown/rolldown/issues/7297
        let mut cross_chunk_imports = cross_chunk_imports
          .iter()
          .copied()
          .chain(chunk_graph.chunk_table[chunk_idx].imports_from_other_chunks.keys().copied())
          .collect::<Vec<_>>();
        cross_chunk_imports
          .sort_unstable_by_key(|chunk_id| chunk_graph.chunk_table[*chunk_id].exec_order);
        cross_chunk_imports
      })
      .collect::<Vec<_>>();

    let index_sorted_imports_from_other_chunks = index_imports_from_other_chunks
      .into_iter_enumerated()
      .map(|(chunk_idx, mut importee_map)| {
        for (idx, items) in &chunk_graph.chunk_table[chunk_idx].imports_from_other_chunks {
          importee_map.entry(*idx).or_default().extend_from_slice(items);
        }
        importee_map
          .into_iter()
          .sorted_unstable_by_key(|(importee_chunk_id, _)| {
            chunk_graph.chunk_table[*importee_chunk_id].exec_order
          })
          .collect::<FxIndexMap<_, _>>()
      })
      .collect::<Vec<_>>();

    let index_sorted_imports_from_external_modules =
      index_chunk_direct_imports_from_external_modules
        .into_iter()
        .map(|imports_from_external_modules| {
          imports_from_external_modules
            .into_iter()
            .sorted_unstable_by_key(|(external_module_id, _)| {
              self.link_output.module_table[*external_module_id].exec_order()
            })
            .collect_vec()
        })
        .collect::<Vec<_>>();

    multizip((
      chunk_graph.chunk_table.iter_mut(),
      index_sorted_imports_from_other_chunks,
      index_sorted_imports_from_external_modules,
      index_sorted_cross_chunk_imports,
      index_cross_chunk_dynamic_imports,
      index_chunk_indirect_imports_from_external_modules,
    ))
    .par_bridge()
    .for_each(
      |(
        chunk,
        sorted_imports_from_other_chunks,
        imports_from_external_modules,
        cross_chunk_imports,
        cross_chunk_dynamic_imports,
        mut chunk_indirect_imports_from_external_modules,
      )| {
        // deduplicated
        for (module_idx, _) in &imports_from_external_modules {
          chunk_indirect_imports_from_external_modules.shift_remove(module_idx);
        }
        chunk.imports_from_other_chunks = sorted_imports_from_other_chunks;
        chunk.direct_imports_from_external_modules = imports_from_external_modules;
        chunk.cross_chunk_imports = cross_chunk_imports;
        chunk.cross_chunk_dynamic_imports =
          cross_chunk_dynamic_imports.into_iter().collect::<Vec<_>>();
        chunk.import_symbol_from_external_modules = chunk_indirect_imports_from_external_modules;
      },
    );
  }

  /// - Assign each symbol to the chunk it belongs to
  /// - Collect all referenced symbols and consider them potential imports
  fn collect_depended_symbols(
    &mut self,
    chunk_graph: &ChunkGraph,
    index_chunk_depended_symbols: &mut IndexChunkDependedSymbols,
    index_chunk_imports_from_external_modules: &mut IndexChunkImportsFromExternalModules,
    index_cross_chunk_dynamic_imports: &mut IndexCrossChunkDynamicImports,
  ) {
    let symbols = &self.link_output.symbol_db;
    let chunk_id_to_symbols_vec = append_only_vec::AppendOnlyVec::new();

    let chunks_iter = multizip((
      chunk_graph.chunk_table.iter_enumerated(),
      index_chunk_depended_symbols.iter_mut(),
      index_chunk_imports_from_external_modules.iter_mut(),
      index_cross_chunk_dynamic_imports.iter_mut(),
    ));

    chunks_iter.par_bridge().for_each(
      |(
        (chunk_id, chunk),
        depended_symbols,
        imports_from_external_modules,
        cross_chunk_dynamic_imports,
      )| {
        let mut symbol_needs_to_assign = vec![];
        chunk.modules.iter().copied().for_each(|module_id| {
          let Module::Normal(module) = &self.link_output.module_table[module_id] else {
            return;
          };
          module
            .import_records
            .iter()
            .filter_map(|rec| rec.resolved_module.map(|module_idx| (rec, module_idx)))
            .for_each(|(rec, module_idx)| {
              match &self.link_output.module_table[module_idx] {
                Module::Normal(_) => {
                  // The the resolved module is not included in module graph, skip it.
                  if !self.link_output.metas[module_idx].is_included {
                    return;
                  }
                  if matches!(rec.kind, ImportKind::DynamicImport) {
                    let importee_chunk =
                      chunk_graph.module_to_chunk[module_idx].expect("importee chunk should exist");
                    cross_chunk_dynamic_imports.insert(importee_chunk);
                  }
                }
                Module::External(_) => {
                  // Ensure the external module is imported in case it has side effects.
                  if matches!(rec.kind, ImportKind::Import)
                    && !rec.meta.contains(ImportRecordMeta::IsExportStar)
                  {
                    imports_from_external_modules.entry(module_idx).or_default();
                  }
                }
              }
            });

          module
            .named_imports
            .iter()
            .filter_map(|(_, import)| {
              module.import_records[import.record_idx]
                .resolved_module
                .map(|module_idx| (import, module_idx))
            })
            .for_each(|(import, module_idx)| {
              if let Module::External(importee) = &self.link_output.module_table[module_idx] {
                imports_from_external_modules
                  .entry(importee.idx)
                  .or_default()
                  .push((module.idx, import.clone()));
              }
            });
          self.link_output.stmt_infos[module.idx].iter_enumerated().for_each(
            |(stmt_info_idx, stmt_info)| {
              if !self.link_output.metas[module.idx].stmt_info_included.has_bit(stmt_info_idx) {
                return;
              }
              stmt_info.declared_symbols.iter().for_each(|declared| {
                symbol_needs_to_assign.push(*declared);
              });

              stmt_info.referenced_symbols.iter().for_each(|reference_ref| {
                match reference_ref {
                  rolldown_common::SymbolOrMemberExprRef::Symbol(referenced) => {
                    self.add_depended_symbol_with_wrapped_esm_init(
                      depended_symbols,
                      symbols.canonical_ref_resolving_namespace(*referenced),
                    );
                  }
                  rolldown_common::SymbolOrMemberExprRef::MemberExpr(member_expr) => {
                    match member_expr.represent_symbol_ref(
                      &self.link_output.metas[module.idx].resolved_member_expr_refs,
                    ) {
                      Some(sym_ref) => {
                        self.add_depended_symbol_with_wrapped_esm_init(
                          depended_symbols,
                          symbols.canonical_ref_resolving_namespace(sym_ref),
                        );
                      }
                      _ => {
                        // `None` means the member expression resolve to a ambiguous export, which means it actually resolve to nothing.
                        // It would be rewrite to `undefined` in the final code, so we don't need to include anything to make `undefined` work.
                      }
                    }
                  }
                }
              });
            },
          );
        });

        if let Some(entry_id) = &chunk.entry_module_idx() {
          let entry = &self.link_output.module_table[*entry_id].as_normal().unwrap();
          let entry_meta = &self.link_output.metas[entry.idx];

          if !matches!(entry_meta.wrap_kind(), WrapKind::Cjs) {
            for export_ref in entry_meta
              .resolved_exports
              .iter()
              .sorted_unstable_by_key(|(name, _)| *name)
              .map(|(_, export)| export)
              // A chunk should always consume a cjs export symbol by property access, so filter
              // out a exported symbol that came from a cjs module.
              .filter(|resolved_export| !resolved_export.came_from_commonjs)
            {
              self.add_depended_symbol_with_wrapped_esm_init(
                depended_symbols,
                symbols.canonical_ref_resolving_namespace(export_ref.symbol_ref),
              );
            }
          }

          if !matches!(entry_meta.wrap_kind(), WrapKind::None) {
            depended_symbols
              .insert(entry_meta.wrapper_ref.expect("cjs should be wrapped in esm output"));
          }

          if matches!(self.options.format, OutputFormat::Cjs)
            && matches!(entry.exports_kind, ExportsKind::Esm)
          {
            depended_symbols.insert(self.link_output.runtime.resolve_symbol("__toCommonJS"));
            depended_symbols.insert(entry.namespace_object_ref);
          }
        }

        // Depending runtime helpers
        for helper in chunk.depended_runtime_helper {
          let index = helper.bits().trailing_zeros() as usize;
          let name = RUNTIME_HELPER_NAMES[index];
          depended_symbols.insert(self.link_output.runtime.resolve_symbol(name));
        }

        chunk_id_to_symbols_vec.push((chunk_id, symbol_needs_to_assign));
      },
    );
    // shadowing previous immutable borrow
    let symbols = &mut self.link_output.symbol_db;
    for (chunk_idx, symbol_list) in chunk_id_to_symbols_vec {
      for declared in symbol_list {
        let declared = declared.inner();
        if cfg!(debug_assertions) {
          let symbol_data = symbols.get(declared);
          debug_assert!(
            symbol_data.chunk_idx.unwrap_or(chunk_idx) == chunk_idx,
            "Symbol: {:?}, {:?} in {:?} should only belong to one chunk. Existed {:?}, new {chunk_idx:?}",
            declared.name(symbols),
            declared,
            self.link_output.module_table[declared.owner].id().as_str(),
            symbol_data.chunk_idx,
          );
        }

        let symbol_data = symbols.get_mut(declared);
        symbol_data.chunk_idx = Some(chunk_idx);
      }
    }
  }

  fn add_depended_symbol_with_wrapped_esm_init(
    &self,
    depended_symbols: &mut FxIndexSet<SymbolRef>,
    symbol_ref: SymbolRef,
  ) {
    depended_symbols.insert(symbol_ref);

    let meta = &self.link_output.metas[symbol_ref.owner];
    if matches!(meta.wrap_kind(), WrapKind::Esm)
      && let Some(wrapper_ref) = meta.wrapper_ref
      && wrapper_ref != symbol_ref
    {
      depended_symbols.insert(wrapper_ref);
    }
  }

  /// - Filter out depended symbols to come from other chunks
  /// - Mark exports of importee chunks
  #[expect(clippy::too_many_arguments, clippy::too_many_lines)]
  fn compute_chunk_imports(
    &self,
    chunk_graph: &ChunkGraph,
    index_chunk_depended_symbols: &IndexChunkDependedSymbols,
    index_chunk_direct_imports_from_external_modules: &IndexChunkImportsFromExternalModules,
    index_chunk_exported_symbols: &mut IndexChunkExportedSymbols,
    index_cross_chunk_imports: &mut IndexCrossChunkImports,
    index_imports_from_other_chunks: &mut IndexImportsFromOtherChunks,
    index_chunk_indirect_imports_from_external_modules: &mut IndexChunkAllImportsFromExternalModules,
  ) {
    // For each module that has been absorbed as a facade namespace, we need to know
    // which other modules dynamically import it so we can tell whether the absorbed
    // namespace must be published cross-chunk. `EntryPoint::related_stmt_infos` only
    // covers `DynamicImport`-kind entries; emitted entries that are also dynamically
    // imported (e.g. via `this.emitFile` + `import()` in the same build) wouldn't be
    // found that way. Walking import_records directly catches both.
    let dynamic_importers_by_target: FxHashMap<ModuleIdx, FxHashSet<ModuleIdx>> = {
      let mut map: FxHashMap<ModuleIdx, FxHashSet<ModuleIdx>> = FxHashMap::default();
      let absorbed_targets: FxHashSet<ModuleIdx> = chunk_graph
        .common_chunk_exported_facade_chunk_namespace
        .values()
        .flatten()
        .copied()
        .collect();
      if !absorbed_targets.is_empty() {
        for (importer_idx, module) in self.link_output.module_table.iter_enumerated() {
          let Some(module) = module.as_normal() else { continue };
          for rec in &module.import_records {
            if rec.kind == ImportKind::DynamicImport
              && let Some(resolved) = rec.resolved_module
              && absorbed_targets.contains(&resolved)
            {
              map.entry(resolved).or_default().insert(importer_idx);
            }
          }
        }
      }
      map
    };

    chunk_graph
      .chunk_table
      .iter_enumerated()
      // Skip chunks that are purely removed (merged into other chunks without preserving exports).
      // Chunks with PreserveExports flag (e.g., emitted chunks merged into common chunks) are kept
      // because their exports still need to be computed.
      .filter(|(chunk_id, _)| {
        !chunk_graph
          .post_chunk_optimization_operations
          .get(chunk_id)
          .map(|flag| *flag == PostChunkOptimizationOperation::Removed)
          .unwrap_or(false)
      })
      .for_each(|(chunk_id, chunk)| {
        if let ChunkKind::EntryPoint { module: module_idx, meta, .. } = chunk.kind {
          let is_dynamic_imported = meta.contains(ChunkMeta::DynamicImported);
          let is_user_defined =
            meta.intersects(ChunkMeta::UserDefinedEntry | ChunkMeta::EmittedChunk);

          let normalized_entry_signatures = normalize_preserve_entry_signature(
            &self.link_output.overrode_preserve_entry_signature_map,
            self.options,
            module_idx,
          );
          let needs_export_entry_signatures = if self.options.preserve_modules {
            if is_user_defined {
              !matches!(normalized_entry_signatures, PreserveEntrySignatures::False)
            } else {
              is_dynamic_imported
            }
          } else {
            is_dynamic_imported
              || !matches!(normalized_entry_signatures, PreserveEntrySignatures::False)
          };
          if needs_export_entry_signatures {
            // If the entry point is external, we don't need to compute exports.
            let meta = &self.link_output.metas[module_idx];
            for (name, symbol) in meta
              .referenced_canonical_exports_symbols(
                module_idx,
                if is_user_defined {
                  EntryPointKind::UserDefined
                } else {
                  EntryPointKind::DynamicImport
                },
                &self.link_output.dynamic_import_exports_usage_map,
                false,
              )
              .map(|(name, export)| (name, export.symbol_ref))
            {
              index_chunk_exported_symbols[chunk_id].entry(symbol).or_default().push(name.clone());
            }
          }
        }

        // A chunk that absorbed a dynamic-entry facade must publish that absorbed
        // entry's namespace/wrapper so the importer's rewritten dynamic import can
        // extract it via `.then(n => n.<ns>)`. Applies regardless of chunk kind:
        // a `DynamicEntryMergedIntoUserDefinedEntry` elimination puts the entry
        // into a `ChunkKind::EntryPoint`, while a `DynamicEntryMergedIntoCommonChunk`
        // elimination puts it into a `ChunkKind::Common`.
        //
        // We only publish the export when at least one dynamic importer lives in
        // a different chunk. Same-chunk dynamic imports take the
        // `Promise.resolve().then(() => (init_xxx(), namespace))` path in
        // `rewrite_dynamic_import_for_merged_entry` and never read from the
        // surrounding chunk's exports, so the export would otherwise be dead.
        if let Some(set) = chunk_graph.common_chunk_exported_facade_chunk_namespace.get(&chunk_id) {
          for dynamic_entry_module in set {
            let has_external_dynamic_importer =
              dynamic_importers_by_target.get(dynamic_entry_module).is_some_and(|importers| {
                importers.iter().any(|importer_idx| {
                  chunk_graph.module_to_chunk[*importer_idx]
                    .is_some_and(|importer_chunk_idx| importer_chunk_idx != chunk_id)
                })
              });
            if !has_external_dynamic_importer {
              continue;
            }
            let meta = &self.link_output.metas[*dynamic_entry_module];
            match meta.wrap_kind() {
              WrapKind::Cjs => {
                // For CJS modules, export only wrapper_ref (require_xxx)
                // Generated code: `import('./chunk.js').then((n) => __toESM(n.require_xxx()))`
                if let Some(wrapper_ref) = meta.wrapper_ref {
                  index_chunk_exported_symbols[chunk_id].entry(wrapper_ref).or_default();
                }
              }
              WrapKind::Esm => {
                // For ESM modules, export both wrapper_ref (init_xxx) and namespace
                // Generated code: `import('./chunk.js').then((n) => (n.init_xxx(), n.namespace))`
                if let Some(wrapper_ref) = meta.wrapper_ref {
                  index_chunk_exported_symbols[chunk_id].entry(wrapper_ref).or_default();
                }
                let ns_ref = self.link_output.module_table[*dynamic_entry_module]
                  .namespace_object_ref()
                  .expect("dynamic entry should be normal module");
                index_chunk_exported_symbols[chunk_id].entry(ns_ref).or_default();
              }
              WrapKind::None => {
                // For non-wrapped modules, export only namespace
                // Generated code: `import('./chunk.js').then((n) => n.namespace)`
                let ns_ref = self.link_output.module_table[*dynamic_entry_module]
                  .namespace_object_ref()
                  .expect("dynamic entry should be normal module");
                index_chunk_exported_symbols[chunk_id].entry(ns_ref).or_default();
              }
            }
          }
        }

        let chunk_meta_imports = &index_chunk_depended_symbols[chunk_id];
        for import_ref in chunk_meta_imports.iter().copied() {
          if !self.link_output.used_symbol_refs.contains(&import_ref) {
            continue;
          }
          // If the symbol from external module and the format is commonjs, we might need to insert runtime
          // symbol ref `__toESM` if it's being used (for namespace or default imports)
          // related to https://github.com/rolldown/rolldown/blob/c100a53c6cfc67b4f92e230da072eef8494862ef/crates/rolldown/src/ecmascript/format/cjs.rs?plain=1#L120-L124
          let import_ref = if self.link_output.module_table[import_ref.owner].is_external() {
            index_chunk_indirect_imports_from_external_modules[chunk_id].insert(import_ref.owner);
            if matches!(self.options.format, OutputFormat::Esm) {
              continue;
            }

            if !index_chunk_direct_imports_from_external_modules[chunk_id]
              .get(&import_ref.owner)
              .is_some_and(|imports| external_import_needs_interop(imports))
            {
              continue;
            }

            // Note: `__toESM` might have been referenced during `collect_depended_symbols` for
            // namespace or default imports from external modules. Named-only imports render as
            // direct `require()` bindings and must not inherit another chunk's `__toESM`.
            let to_esm_ref = self.link_output.runtime.resolve_symbol("__toESM");
            if self.link_output.symbol_db.get(to_esm_ref).chunk_idx.is_some() {
              // __toESM is in a chunk, so it's being used
              to_esm_ref
            } else {
              // __toESM is not being used, so skip this import
              // This happens when the interop helper was optimized away.
              continue;
            }
          } else {
            import_ref
          };
          let import_symbol = self.link_output.symbol_db.get(import_ref);
          let importee_chunk_idx = import_symbol.chunk_idx.unwrap_or_else(|| {
            let symbol_owner = &self.link_output.module_table[import_ref.owner];
            let symbol_name = import_ref.name(&self.link_output.symbol_db);
            panic!(
              "Symbol {:?} in {:?} should belong to a chunk",
              symbol_name,
              symbol_owner.id().as_str()
            )
          });
          // Check if the import is from another chunk
          if chunk_id != importee_chunk_idx {
            index_cross_chunk_imports[chunk_id].insert(importee_chunk_idx);
            let imports_from_other_chunks = &mut index_imports_from_other_chunks[chunk_id];
            imports_from_other_chunks
              .entry(importee_chunk_idx)
              .or_default()
              .push(CrossChunkImportItem { import_ref });
            index_chunk_exported_symbols[importee_chunk_idx].entry(import_ref).or_default();
          }
        }

        if let ChunkKind::EntryPoint { module: entry_module_idx, .. } = &chunk.kind {
          // If the entry module is in a different chunk (facade entry), ensure that chunk
          // is imported. Without this, the facade would be empty and the entry module's
          // code would never execute.
          if let Some(entry_chunk_idx) = chunk_graph.module_to_chunk[*entry_module_idx] {
            if entry_chunk_idx != chunk_id {
              index_cross_chunk_imports[chunk_id].insert(entry_chunk_idx);
              let imports_from_other_chunks = &mut index_imports_from_other_chunks[chunk_id];
              imports_from_other_chunks.entry(entry_chunk_idx).or_default();
            }
          }

          if self.options.preserve_modules {
            let entry_module =
              chunk.entry_module(&self.link_output.module_table).expect("Should have entry module");
            entry_module
              .import_records
              .iter()
              .filter(|rec| rec.kind != ImportKind::DynamicImport)
              .filter_map(|r| r.resolved_module)
              .for_each(|module_idx| {
                if !self.link_output.module_table[module_idx].side_effects().has_side_effects() {
                  return;
                }
                let Some(importee_chunk_idx) = chunk_graph.module_to_chunk[module_idx] else {
                  return;
                };
                index_cross_chunk_imports[chunk_id].insert(importee_chunk_idx);
                let imports_from_other_chunks = &mut index_imports_from_other_chunks[chunk_id];
                imports_from_other_chunks.entry(importee_chunk_idx).or_default();
              });
          }
        }

        // Add bare imports for side-effectful dependencies in other chunks.
        //
        // With strict_execution_order/wrapping, modules aren't executed in loading but on-demand.
        // So we don't need to do plain imports to address the side effects. It would be ensured
        // by those `init_xxx()` calls.
        if !self.options.is_strict_execution_order_enabled() {
          for &module_idx in &chunk.modules {
            let Some(module) = self.link_output.module_table[module_idx].as_normal() else {
              continue;
            };

            // From import records.
            // This adds side-effectful imports as bare imports if necessary.
            for rec in &module.import_records {
              if rec.kind != ImportKind::Import {
                continue;
              }
              let Some(importee_module_idx) = rec.resolved_module else {
                continue;
              };
              if !self.link_output.module_table[importee_module_idx]
                .side_effects()
                .has_side_effects()
              {
                continue;
              }
              let Some(importee_chunk_idx) = chunk_graph.module_to_chunk[importee_module_idx]
              else {
                continue;
              };
              if importee_chunk_idx == chunk_id {
                continue;
              }
              index_cross_chunk_imports[chunk_id].insert(importee_chunk_idx);
              let imports_from_other_chunks = &mut index_imports_from_other_chunks[chunk_id];
              imports_from_other_chunks.entry(importee_chunk_idx).or_default();
            }

            // Runtime module may have side effects (e.g. dev/HMR mode) without an import record.
            if self.link_output.metas[module_idx].has_side_effectful_runtime_dep {
              let runtime_idx = self.link_output.runtime.id();
              if let Some(runtime_chunk_idx) = chunk_graph.module_to_chunk[runtime_idx] {
                if runtime_chunk_idx != chunk_id {
                  index_cross_chunk_imports[chunk_id].insert(runtime_chunk_idx);
                  let imports_from_other_chunks = &mut index_imports_from_other_chunks[chunk_id];
                  imports_from_other_chunks.entry(runtime_chunk_idx).or_default();
                }
              }
            }
          }
        }
      });
  }

  fn deconflict_exported_names(
    &self,
    chunk_graph: &mut ChunkGraph,
    index_chunk_exported_symbols: &IndexChunkExportedSymbols,
  ) {
    let is_preserve_modules_enabled = self.options.preserve_modules;
    let allow_to_minify_internal_exports =
      !is_preserve_modules_enabled && self.options.minify_internal_exports;
    // Generate cross-chunk exports. These must be computed before cross-chunk
    // imports because of export alias renaming, which must consider all export
    // aliases simultaneously to avoid collisions.
    let preserve_export_names_modules =
      std::mem::take(&mut chunk_graph.common_chunk_preserve_export_names_modules);
    for (chunk_id, chunk) in chunk_graph.chunk_table.iter_mut_enumerated() {
      if allow_to_minify_internal_exports {
        // Reference: https://github.com/rollup/rollup/blob/f76339428586620ff3e4c32fce48f923e7be7b05/src/utils/exportNames.ts#L5
        let mut named_index = 0;
        let mut used_names = FxHashSet::default();

        let mut processed_entry_exports = FxHashSet::default();
        if let Some(entry_module_idx) = chunk.entry_module_idx() {
          let exported_chunk_symbols = &index_chunk_exported_symbols[chunk_id];
          // If this's an entry point, we need to make sure the entry modules' exports are not minified.
          let entry_module = &self.link_output.metas[entry_module_idx];
          entry_module.canonical_exports(false).for_each(|(name, export)| {
            let export_ref = self.link_output.symbol_db.canonical_ref_for(export.symbol_ref);
            if !exported_chunk_symbols.contains_key(&export.symbol_ref)
              || !self.link_output.used_symbol_refs.contains(&export.symbol_ref)
            {
              // Rolldown supports tree-shaking on dynamic entries, so not all exports are used.
              return;
            }
            used_names.insert(name.clone());
            chunk.exports_to_other_chunks.entry(export_ref).or_default().push(name.clone());
            processed_entry_exports.insert(export_ref);
          });
        }
        // Also preserve exports from AllowExtension emitted chunks that were merged into this chunk
        if let Some(modules) = preserve_export_names_modules.get(&chunk_id) {
          let exported_chunk_symbols = &index_chunk_exported_symbols[chunk_id];
          for &module_idx in modules {
            let module_meta = &self.link_output.metas[module_idx];
            module_meta.canonical_exports(false).for_each(|(name, export)| {
              let export_ref = self.link_output.symbol_db.canonical_ref_for(export.symbol_ref);
              // Use canonical ref for lookup since that's the key in exported_chunk_symbols
              if !exported_chunk_symbols.contains_key(&export_ref)
                || !self.link_output.used_symbol_refs.contains(&export_ref)
              {
                return;
              }
              // Skip if already processed (e.g., same symbol re-exported from multiple modules)
              if processed_entry_exports.contains(&export_ref) {
                return;
              }
              used_names.insert(name.clone());
              chunk.exports_to_other_chunks.entry(export_ref).or_default().push(name.clone());
              processed_entry_exports.insert(export_ref);
            });
          }
        }
        for (chunk_export, _predefined_names) in index_chunk_exported_symbols[chunk_id]
          .iter()
          .sorted_unstable_by_key(|(symbol_ref, _predefined_names)| {
            // Canonical naming order — see `deconflict_order_key`.
            deconflict_order_key(
              **symbol_ref,
              &self.link_output.module_table,
              &self.link_output.symbol_db,
            )
          })
        {
          let export_ref = self.link_output.symbol_db.canonical_ref_for(*chunk_export);
          if processed_entry_exports.contains(&export_ref) {
            continue;
          }

          let mut export_name: CompactStr;
          loop {
            named_index += 1;
            export_name = generate_minified_names(named_index).into();
            if !used_names.contains(&export_name) {
              break;
            }
          }
          used_names.insert(export_name.clone());
          chunk.exports_to_other_chunks.entry(export_ref).or_default().push(export_name);
        }

        continue;
      }

      let mut resolver =
        ConflictResolver::with_capacity(index_chunk_exported_symbols[chunk_id].len());
      for (chunk_export, predefined_names) in index_chunk_exported_symbols[chunk_id]
        .iter()
        .sorted_by_cached_key(|(symbol_ref, _predefined_names)| {
          // Canonical naming order — see `deconflict_order_key`.
          deconflict_order_key(
            **symbol_ref,
            &self.link_output.module_table,
            &self.link_output.symbol_db,
          )
        })
      {
        if !self.link_output.used_symbol_refs.contains(chunk_export) {
          continue;
        }
        let original_name: CompactStr = match predefined_names.as_slice() {
          [] => CompactStr::new(chunk_export.name(&self.link_output.symbol_db)),
          lst => {
            for item in lst {
              resolver.reserve(CompactStr::new(item));
            }

            chunk.exports_to_other_chunks.entry(*chunk_export).or_default().extend_from_slice(lst);
            continue;
          }
        };
        // A special case for `default` export when setting `preserve_modules`: the
        // single default export per chunk must be named `default`. Otherwise use the
        // `default_export_ref` representative name. The `&&` keeps the `entry_module`
        // lookup guarded behind the `preserve_modules` check.
        let base = if self.options.preserve_modules
          && chunk.entry_module(&self.link_output.module_table).unwrap().default_export_ref
            == *chunk_export
        {
          CompactStr::new_const("default")
        } else {
          original_name
        };
        let chosen = resolver.resolve(base, |_, _| true);
        chunk.exports_to_other_chunks.entry(*chunk_export).or_default().push(chosen);
      }
    }
  }
}

// The same implementation with https://github.com/oxc-project/oxc/blob/crates_v0.86.0/crates/oxc_mangler/src/base54.rs#L30-L31
const FIRST_BASE: u32 = 54;
const REST_BASE: u32 = 64;
const FREQUENT_CHARS: &[u8; REST_BASE as usize] =
  b"etnriaoscludfpmhg_vybxSCwTEDOkAjMNPFILRzBVHUWGKqJYXZQ$1024368579";

// Intentionally NOT routed through `ConflictResolver`: this is a generative
// base54 namer (not `$N`-suffix), so it shares only `deconflict_order_key`,
// not the conflict loop. See docs/superpowers/specs/2026-06-17-renamer-naming-engine-design.md.
fn generate_minified_names(mut value: u32) -> String {
  let mut buffer = vec![];

  // Base 54 at first because these are the usable first characters in JavaScript identifiers
  let byte = FREQUENT_CHARS[(value % FIRST_BASE) as usize];
  buffer.push(byte);
  value /= FIRST_BASE;

  while value > 0 {
    let byte = FREQUENT_CHARS[(value % REST_BASE) as usize];
    buffer.push(byte);
    value /= REST_BASE;
  }
  // SAFETY: `buffer` is base64 characters, it is valid utf8 characters
  unsafe { String::from_utf8_unchecked(buffer) }
}
