// Copyright (c) 2022, 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:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/element/type_visitor.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/ast/extensions.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/dart/element/type_visitor.dart';
import 'package:analyzer/src/summary2/type_builder.dart';

/// The type builder for a [RecordType].
class RecordTypeBuilder extends TypeBuilder {
  /// The type system of the library with the type name.
  final TypeSystemImpl typeSystem;

  /// The node for which this builder is created.
  final RecordTypeAnnotationImpl node;

  /// The ordered list of field types, first positional, then named.
  final List<DartType> fieldTypes;

  @override
  final NullabilitySuffix nullabilitySuffix;

  /// This flag is set to `true` while building this type.
  bool _isBuilding = false;

  /// The actual built type, not a [TypeBuilder] anymore.
  RecordTypeImpl? _type;

  RecordTypeBuilder({
    required this.typeSystem,
    required this.node,
    required this.fieldTypes,
    required this.nullabilitySuffix,
  });

  factory RecordTypeBuilder.of(
    TypeSystemImpl typeSystem,
    RecordTypeAnnotationImpl node,
  ) {
    return RecordTypeBuilder(
      typeSystem: typeSystem,
      node: node,
      fieldTypes: node.fields.map((field) => field.type.typeOrThrow).toList(),
      nullabilitySuffix: node.question != null
          ? NullabilitySuffix.question
          : NullabilitySuffix.none,
    );
  }

  @override
  R accept<R>(TypeVisitor<R> visitor) {
    if (visitor is LinkingTypeVisitor<R>) {
      var visitor2 = visitor as LinkingTypeVisitor<R>;
      return visitor2.visitRecordTypeBuilder(this);
    } else {
      throw StateError('Should not happen outside linking.');
    }
  }

  @override
  RecordTypeImpl build() {
    var type = _type;
    if (type != null) {
      return type;
    }

    if (_isBuilding) {
      return _type = _buildRecordType(
        recursionFound: true,
      );
    }

    _isBuilding = true;
    try {
      return _type = _buildRecordType();
    } finally {
      _isBuilding = false;
    }
  }

  @override
  String toString() {
    return node.toSource();
  }

  RecordTypeImpl _buildRecordType({
    bool recursionFound = false,
  }) {
    var fieldTypeIndex = 0;

    DartType nextFieldType() {
      if (recursionFound) {
        return typeSystem.typeProvider.dynamicType;
      } else {
        var type = fieldTypes[fieldTypeIndex++];
        return _buildType(type);
      }
    }

    var positionalFields = node.positionalFields.map((field) {
      return RecordTypePositionalFieldImpl(
        type: nextFieldType(),
      );
    }).toList();

    var namedFields = node.namedFields?.fields.map((field) {
      return RecordTypeNamedFieldImpl(
        name: field.name.lexeme,
        type: nextFieldType(),
      );
    }).toList();

    return node.type = RecordTypeImpl(
      positionalFields: positionalFields,
      namedFields: namedFields ?? const [],
      nullabilitySuffix: nullabilitySuffix,
    );
  }

  /// If the [type] is a [TypeBuilder], build it; otherwise return as is.
  static DartType _buildType(DartType type) {
    if (type is TypeBuilder) {
      return type.build();
    } else {
      return type;
    }
  }
}
