// Copyright (c) 2014, 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/base/analyzer_public_api.dart';
import 'package:analyzer/dart/ast/ast.dart' show AnnotatedNode;
import 'package:analyzer/dart/ast/token.dart' show Token;
import 'package:analyzer/src/dart/element/element.dart' show ElementImpl;

export 'package:_fe_analyzer_shared/src/util/resolve_relative_uri.dart'
    show resolveRelativeUri;

/// If the given [node] has a documentation comment, remember its content
/// and range into the given [element].
void setElementDocumentationComment(ElementImpl element, AnnotatedNode node) {
  var comment = node.documentationComment;
  if (comment != null) {
    element.documentationComment =
        comment.tokens.map((Token t) => t.lexeme).join('\n');
  }
}

/// Check whether [uri1] starts with (or 'is prefixed by') [uri2] by checking
/// path segments.
bool startsWith(Uri uri1, Uri uri2) {
  List<String> uri1Segments = uri1.pathSegments;
  List<String> uri2Segments = uri2.pathSegments.toList();
  // Punt if empty (https://github.com/dart-lang/sdk/issues/24126)
  if (uri2Segments.isEmpty) {
    return false;
  }
  // Trim trailing empty segments ('/foo/' => ['foo', ''])
  if (uri2Segments.last == '') {
    uri2Segments.removeLast();
  }

  if (uri2Segments.length > uri1Segments.length) {
    return false;
  }

  for (int i = 0; i < uri2Segments.length; ++i) {
    if (uri2Segments[i] != uri1Segments[i]) {
      return false;
    }
  }
  return true;
}

/// The kind of a parameter. A parameter can be either positional or named, and
/// can be either required or optional.
@AnalyzerPublicApi(message: 'exposed by ParameterElement.parameterKind')
class ParameterKind implements Comparable<ParameterKind> {
  /// A positional required parameter.
  static const ParameterKind REQUIRED = ParameterKind(
    name: 'REQUIRED',
    ordinal: 0,
    isPositional: true,
    isRequiredPositional: true,
    isOptionalPositional: false,
    isNamed: false,
    isRequiredNamed: false,
    isOptionalNamed: false,
    isRequired: true,
    isOptional: false,
  );

  /// A positional optional parameter.
  static const ParameterKind POSITIONAL = ParameterKind(
    name: 'POSITIONAL',
    ordinal: 1,
    isPositional: true,
    isRequiredPositional: false,
    isOptionalPositional: true,
    isNamed: false,
    isRequiredNamed: false,
    isOptionalNamed: false,
    isRequired: false,
    isOptional: true,
  );

  /// A named required parameter.
  static const ParameterKind NAMED_REQUIRED = ParameterKind(
    name: 'NAMED_REQUIRED',
    ordinal: 2,
    isPositional: false,
    isRequiredPositional: false,
    isOptionalPositional: false,
    isNamed: true,
    isRequiredNamed: true,
    isOptionalNamed: false,
    isRequired: true,
    isOptional: false,
  );

  /// A named optional parameter.
  static const ParameterKind NAMED = ParameterKind(
    name: 'NAMED',
    ordinal: 3,
    isPositional: false,
    isRequiredPositional: false,
    isOptionalPositional: false,
    isNamed: true,
    isRequiredNamed: false,
    isOptionalNamed: true,
    isRequired: false,
    isOptional: true,
  );

  static const List<ParameterKind> values = [
    REQUIRED,
    POSITIONAL,
    NAMED_REQUIRED,
    NAMED
  ];

  /// The name of this parameter.
  final String name;

  /// The ordinal value of the parameter.
  final int ordinal;

  /// Return `true` if is a positional parameter.
  ///
  /// Positional parameters can either be required or optional.
  final bool isPositional;

  /// Return `true` if both a required and positional parameter.
  final bool isRequiredPositional;

  /// Return `true` if both an optional and positional parameter.
  final bool isOptionalPositional;

  /// Return `true` if a named parameter.
  ///
  /// Named parameters can either be required or optional.
  final bool isNamed;

  /// Return `true` if both a required and named parameter.
  ///
  /// Note: this will return `false` for a named parameter that is annotated
  /// with the `@required` annotation.
  final bool isRequiredNamed;

  /// Return `true` if both an optional and named parameter.
  final bool isOptionalNamed;

  /// Return `true` if a required parameter.
  ///
  /// Required parameters can either be positional or named.
  ///
  /// Note: this will return `false` for a named parameter that is annotated
  /// with the `@required` annotation.
  final bool isRequired;

  /// Return `true` if an optional parameter.
  ///
  /// Optional parameters can either be positional or named.
  final bool isOptional;

  /// Initialize a newly created kind with the given state.
  const ParameterKind({
    required this.name,
    required this.ordinal,
    required this.isPositional,
    required this.isRequiredPositional,
    required this.isOptionalPositional,
    required this.isNamed,
    required this.isRequiredNamed,
    required this.isOptionalNamed,
    required this.isRequired,
    required this.isOptional,
  });

  @override
  int get hashCode => ordinal;

  @override
  int compareTo(ParameterKind other) => ordinal - other.ordinal;

  @override
  String toString() => name;
}
