// Copyright (c) 2019, 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 'package:analysis_server/lsp_protocol/protocol.dart'
    hide TypeHierarchyItem, Element;
import 'package:analysis_server/src/lsp/error_or.dart';
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
import 'package:analysis_server/src/lsp/mapping.dart';
import 'package:analysis_server/src/lsp/registration/feature_registration.dart';
import 'package:analysis_server/src/search/type_hierarchy.dart';
import 'package:analysis_server/src/utilities/extensions/ast.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/util/performance/operation_performance.dart';

typedef StaticOptions =
    Either3<bool, ImplementationOptions, ImplementationRegistrationOptions>;

class ImplementationHandler
    extends SharedMessageHandler<TextDocumentPositionParams, List<Location>> {
  new(super.server);

  @override
  Method get handlesMessage => Method.textDocument_implementation;

  @override
  LspJsonHandler<TextDocumentPositionParams> get jsonHandler =>
      TextDocumentPositionParams.jsonHandler;

  @override
  bool get requiresTrustedCaller => false;

  @override
  Future<ErrorOr<List<Location>>> handle(
    TextDocumentPositionParams params,
    MessageInfo message,
    CancellationToken token,
  ) async {
    if (!isDartDocument(params.textDocument)) {
      return success(const []);
    }
    var performance = message.performance;
    var pos = params.position;
    var path = pathOfDoc(params.textDocument);
    var unit = await performance.runAsync(
      'requireResolvedUnit',
      (_) => path.mapResult(requireResolvedUnit),
    );
    var offset = unit.mapResultSync((unit) => toOffset(unit.lineInfo, pos));
    return await performance.runAsync(
      '_getImplementations',
      (performance) => (unit, offset).mapResults(
        (unit, offset) => _getImplementations(unit, offset, token, performance),
      ),
    );
  }

  Future<ErrorOr<List<Location>>> _getImplementations(
    ResolvedUnitResult result,
    int offset,
    CancellationToken token,
    OperationPerformanceImpl performance,
  ) async {
    var node = result.unit.nodeCovering(offset: offset);
    var element = node?.getElement();
    if (element == null) {
      return success([]);
    }

    var helper = TypeHierarchyComputerHelper.fromElement(element);
    var interfaceElement = helper.pivotClass;
    if (interfaceElement == null) {
      return success([]);
    }
    var needsMember = helper.findMemberElement(interfaceElement) != null;

    var allSubtypes = <InterfaceElement>{};
    await performance.runAsync(
      'appendAllSubtypes',
      (performance) => server.searchEngine.appendAllSubtypes(
        interfaceElement,
        allSubtypes,
        performance,
      ),
    );

    var locations = performance.run(
      'filter and get location',
      (_) => allSubtypes
          .map((element) {
            return needsMember
                // Filter based on type, so when searching for members we don't
                // include any intermediate classes that don't have
                // implementations for the method.
                ? helper.findMemberElement(element)?.nonSynthetic
                : element;
          })
          .nonNulls
          .toSet()
          .map((element) {
            var firstFragment = element.firstFragment;
            var libraryFragment = firstFragment.libraryFragment;
            if (libraryFragment == null) {
              return null;
            }

            var nameOffset = firstFragment.nameOffset;
            var name = firstFragment.name;
            if (nameOffset == null || name == null) {
              return null;
            }

            return Location(
              uri: uriConverter.toClientUri(libraryFragment.source.fullName),
              range: toRange(libraryFragment.lineInfo, nameOffset, name.length),
            );
          })
          .nonNulls
          .toList(),
    );

    return success(locations);
  }
}

class ImplementationRegistrations extends FeatureRegistration
    with SingleDynamicRegistration, StaticRegistration<StaticOptions> {
  new(super.info);

  @override
  ToJsonable? get options =>
      TextDocumentRegistrationOptions(documentSelector: fullySupportedTypes);

  @override
  Method get registrationMethod => Method.textDocument_implementation;

  @override
  StaticOptions get staticOptions => Either3.t1(true);

  @override
  bool get supportsDynamic => clientDynamic.implementation;
}
