// 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 'package:_fe_analyzer_shared/src/util/dependency_walker.dart' as graph;
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_visitor.dart';
import 'package:analyzer/src/summary2/link.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';

/// Builds extension types, in particular representation types. There might be
/// dependencies between them, so they all should be processed simultaneously.
void buildExtensionTypes(Linker linker, List<AstNode> declarations) {
  var walker = _Walker(linker);
  var nodes = <_Node>[];
  var elements = <ExtensionTypeElementImpl2>[];
  for (var declaration in declarations) {
    if (declaration is ExtensionTypeDeclarationImpl) {
      var element = declaration.declaredFragment!.element;
      var node = walker.getNode(declaration);
      nodes.add(node);
      elements.add(element);
    }
  }

  for (var node in nodes) {
    walker.walk(node);
  }

  _breakImplementsCycles(elements);
}

/// Clears interfaces for extension types that have cycles.
void _breakImplementsCycles(List<ExtensionTypeElementImpl2> elements) {
  var walker = _ImplementsWalker();
  for (var element in elements) {
    var node = walker.getNode(element);
    walker.walk(node);
  }
}

/// Collector of referenced extension types in a type.
class _DependenciesCollector extends RecursiveTypeVisitor {
  final List<ExtensionTypeElementImpl2> dependencies = [];

  _DependenciesCollector() : super(includeTypeAliasArguments: false);

  @override
  bool visitInterfaceType(InterfaceType type) {
    var element = type.element3;
    if (element is ExtensionTypeElementImpl2) {
      dependencies.add(element);
    }

    return super.visitInterfaceType(type);
  }
}

class _ImplementsNode extends graph.Node<_ImplementsNode> {
  final _ImplementsWalker walker;
  final ExtensionTypeElementImpl2 element;

  @override
  bool isEvaluated = false;

  _ImplementsNode(this.walker, this.element);

  @override
  List<_ImplementsNode> computeDependencies() {
    return element.interfaces
        .map((interface) => interface.element3)
        .whereType<ExtensionTypeElementImpl2>()
        .map(walker.getNode)
        .toList();
  }

  void _evaluate() {
    isEvaluated = true;
  }

  void _markCircular() {
    isEvaluated = true;
    element.hasImplementsSelfReference = true;

    var representationType = element.firstFragment.representation.type;
    var typeSystem = element.library2.typeSystem;

    var superInterface = typeSystem.isNonNullable(representationType)
        ? typeSystem.objectNone
        : typeSystem.objectQuestion;
    element.firstFragment.interfaces = [superInterface];
  }
}

class _ImplementsWalker extends graph.DependencyWalker<_ImplementsNode> {
  final Map<ExtensionTypeElementImpl2, _ImplementsNode> nodeMap =
      Map.identity();

  @override
  void evaluate(_ImplementsNode v) {
    v._evaluate();
  }

  @override
  void evaluateScc(List<_ImplementsNode> scc) {
    for (var node in scc) {
      node._markCircular();
    }
  }

  _ImplementsNode getNode(ExtensionTypeElementImpl2 element) {
    return nodeMap[element] ??= _ImplementsNode(this, element);
  }
}

class _Node extends graph.Node<_Node> {
  final _Walker walker;
  final ExtensionTypeDeclarationImpl node;
  final ExtensionTypeElementImpl2 element;

  @override
  bool isEvaluated = false;

  _Node(this.walker, this.node, this.element);

  @override
  List<_Node> computeDependencies() {
    var type = node.representation.fieldType.typeOrThrow;
    var visitor = _DependenciesCollector();
    type.accept(visitor);

    var dependencies = <_Node>[];
    for (var element in visitor.dependencies) {
      var declaration = walker.linker.getLinkingNode2(element.firstFragment);
      if (declaration is ExtensionTypeDeclarationImpl) {
        var node = walker.getNode(declaration);
        dependencies.add(node);
      }
    }

    return dependencies;
  }

  void _evaluate() {
    var type = node.representation.fieldType.typeOrThrow;
    _evaluateWithType(type);
  }

  void _evaluateWithType(TypeImpl type) {
    var typeSystem = element.library2.typeSystem;

    element.firstFragment.representation.type = type;
    element.firstFragment.typeErasure = type.extensionTypeErasure;
    element.firstFragment.interfaces = element.interfaces
        .whereType<InterfaceType>()
        .where(typeSystem.isValidExtensionTypeSuperinterface)
        .toFixedList();

    var primaryConstructor = element.constructors2.first;
    var primaryFormalParameter = primaryConstructor.formalParameters.first;
    primaryFormalParameter as FieldFormalParameterElementImpl2;
    primaryFormalParameter.type = type;
    isEvaluated = true;
  }

  void _markCircular() {
    element.hasRepresentationSelfReference = true;
    _evaluateWithType(InvalidTypeImpl.instance);
  }
}

class _Walker extends graph.DependencyWalker<_Node> {
  final Linker linker;
  final Map<ExtensionTypeElementImpl2, _Node> nodeMap = Map.identity();

  _Walker(this.linker);

  @override
  void evaluate(_Node v) {
    v._evaluate();
  }

  @override
  void evaluateScc(List<_Node> scc) {
    for (var node in scc) {
      node._markCircular();
    }
  }

  _Node getNode(ExtensionTypeDeclarationImpl node) {
    var element = node.declaredFragment!.element;
    return nodeMap[element] ??= _Node(this, node, element);
  }
}
