// Copyright (c) 2017, 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:built_value/built_value.dart';
import 'package:meta/meta.dart';

import '../base.dart';
import '../visitors.dart';
import 'code.dart';
import 'expression.dart';
import 'type_reference.dart';

/// Short-hand for `Reference(symbol, url)`.
Reference refer(String symbol, [String? url]) => Reference(symbol, url);

/// A reference to [symbol], such as a class, or top-level method or field.
///
/// References can be collected and collated in order to automatically generate
/// `import` statements for all used symbols.
@immutable
class Reference extends Expression implements Spec {
  /// Relative, `package:` or `dart:` URL of the library.
  ///
  /// May be omitted (`null`) in order to express "same library".
  final String? url;

  /// Name of the class, method, or field.
  ///
  /// May be `null` for references without symbols, for instance a function type
  /// has no symbol.
  final String? symbol;

  /// Create a reference to [symbol] in [url].
  const Reference(this.symbol, [this.url]);

  @override
  R accept<R>(
    SpecVisitor<R> visitor, [
    R? context,
  ]) =>
      visitor.visitReference(this, context);

  @override
  int get hashCode => '$url#$symbol'.hashCode;

  @override
  bool operator ==(Object other) =>
      other is Reference && other.url == url && other.symbol == symbol;

  /// Returns a new instance of this expression.
  Expression newInstance(
    Iterable<Expression> positionalArguments, [
    Map<String, Expression> namedArguments = const {},
    List<Reference> typeArguments = const [],
  ]) =>
      InvokeExpression.newOf(
        this,
        positionalArguments.toList(),
        namedArguments,
        typeArguments,
      );

  /// Returns a new instance of this expression with a named constructor.
  Expression newInstanceNamed(
    String name,
    Iterable<Expression> positionalArguments, [
    Map<String, Expression> namedArguments = const {},
    List<Reference> typeArguments = const [],
  ]) =>
      InvokeExpression.newOf(
        this,
        positionalArguments.toList(),
        namedArguments,
        typeArguments,
        name,
      );

  /// Returns a const instance of this expression.
  Expression constInstance(
    Iterable<Expression> positionalArguments, [
    Map<String, Expression> namedArguments = const {},
    List<Reference> typeArguments = const [],
  ]) =>
      InvokeExpression.constOf(
        this,
        positionalArguments.toList(),
        namedArguments,
        typeArguments,
      );

  /// Returns a const instance of this expression with a named constructor.
  Expression constInstanceNamed(
    String name,
    Iterable<Expression> positionalArguments, [
    Map<String, Expression> namedArguments = const {},
    List<Reference> typeArguments = const [],
  ]) =>
      InvokeExpression.constOf(
        this,
        positionalArguments.toList(),
        namedArguments,
        typeArguments,
        name,
      );

  @override
  Expression get expression => CodeExpression(Code.scope((a) => a(this)));

  @override
  String toString() => (newBuiltValueToStringHelper('Reference')
        ..add('url', url)
        ..add('symbol', symbol))
      .toString();

  /// Returns as a [TypeReference], which allows adding generic type parameters.
  Reference get type => TypeReference((b) => b
    ..url = url
    ..symbol = symbol);
}
