// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:collection';

import 'package:analysis_server/src/services/completion/dart/candidate_suggestion.dart';
import 'package:analyzer/dart/element/element.dart';

/// This class tracks the set of names already added in the completion list in
/// order to prevent suggesting elements that have been shadowed by more local
/// declarations.
class VisibilityTracker {
  /// The set of known previously declared names.
  final Set<String> _declaredNames = {};
  final _notImportedNames = HashMap<String, List<String>>();

  /// Whether the name of the [element] is visible at the completion location.
  ///
  /// A name is not visible if it is shadowed by an element on the scope chain
  /// that has the same name.
  ///
  /// If the [importData] indicated that the element is already imported (either
  /// by being `null` or by returning `false` from `isNotImported`), the name
  /// is visible.
  ///
  /// If it is already imported, it will be added to the list of
  /// [_declaredNames] so that it will shadow any elements of the same name
  /// further up the scope chain.
  bool isVisible({required Element? element, required ImportData? importData}) {
    var name = element?.displayName;
    if (name == null) {
      return false;
    }

    var isNotImported = importData?.isNotImported ?? false;
    var prefix = importData?.prefix;
    var qualifiedName = prefix != null ? '$prefix.$name' : name;

    // Track names from non imported libraries so as to allow multiple
    // suggestions with same name from differnet libraries.
    if (isNotImported) {
      if (_declaredNames.contains(qualifiedName)) {
        return false;
      }
      var libraryUri = importData!.libraryUri.toString();
      var notImportedList = _notImportedNames.putIfAbsent(
        name,
        () => <String>[],
      );
      if (notImportedList.isEmpty || !notImportedList.contains(libraryUri)) {
        notImportedList.add(libraryUri);
        return true;
      }
      return false;
    }
    return _declaredNames.add(qualifiedName);
  }
}
