// Copyright (c) 2024, 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 'proto.dart';
import 'references.dart';
import 'type_annotations.dart';

/// Scope used to lookup named entities.
abstract class Scope {
  /// Returns the [Proto] corresponding to [name] in this scope.
  ///
  /// This might be an [UnresolvedIdentifier] if [name] was not found in this
  /// scope.
  Proto lookup(String name);
}

/// Scope of a type declaration used to lookup static members.
abstract class TypeDeclarationScope {
  /// Returns the [Proto] corresponding to accessing [name] on the type
  /// declaration.
  ///
  /// If the access is preceded by [typeArguments], these must be passed and
  /// included in the returned [Proto].
  Proto lookup(String name, [List<TypeAnnotation>? typeArguments]);
}

/// Base implementation for creating a [TypeDeclarationScope] for a class.
abstract base class BaseClassScope implements TypeDeclarationScope {
  ClassReference get classReference;

  Proto createConstructorProto(List<TypeAnnotation>? typeArguments,
      ConstructorReference constructorReference) {
    return new ConstructorProto(
        classReference, typeArguments ?? const [], constructorReference);
  }

  Proto createMemberProto<T>(List<TypeAnnotation>? typeArguments, String name,
      T? member, Proto Function(T, String) memberToProto) {
    if (member == null) {
      if (typeArguments != null) {
        return new UnresolvedAccess(
            new GenericClassProto(classReference, this, typeArguments), name);
      } else {
        return new UnresolvedAccess(new ClassProto(classReference, this), name);
      }
    } else {
      if (typeArguments != null) {
        return new InvalidAccessProto(
            new GenericClassProto(classReference, this, typeArguments), name);
      } else {
        return memberToProto(member, name);
      }
    }
  }
}

/// Base implementation for creating a [TypeDeclarationScope] for an extension.
abstract base class BaseExtensionScope implements TypeDeclarationScope {
  ExtensionReference get extensionReference;

  Proto createMemberProto<T>(List<TypeAnnotation>? typeArguments, String name,
      T? member, Proto Function(T, String) memberToProto) {
    if (typeArguments != null) {
      return new UnresolvedAccess(
          new InvalidInstantiationProto(
              new ExtensionProto(extensionReference, this), typeArguments),
          name);
    } else if (member == null) {
      return new UnresolvedAccess(
          new ExtensionProto(extensionReference, this), name);
    } else {
      return memberToProto(member, name);
    }
  }
}

/// Base implementation for creating a [TypeDeclarationScope] for an extension
/// type.
abstract base class BaseExtensionTypeScope implements TypeDeclarationScope {
  ExtensionTypeReference get extensionTypeReference;

  Proto createConstructorProto(List<TypeAnnotation>? typeArguments,
      ConstructorReference constructorReference) {
    return new ConstructorProto(extensionTypeReference,
        typeArguments ?? const [], constructorReference);
  }

  Proto createMemberProto<T>(List<TypeAnnotation>? typeArguments, String name,
      T? member, Proto Function(T, String) memberToProto) {
    if (member == null) {
      if (typeArguments != null) {
        return new UnresolvedAccess(
            new GenericExtensionTypeProto(
                extensionTypeReference, this, typeArguments),
            name);
      } else {
        return new UnresolvedAccess(
            new ExtensionTypeProto(extensionTypeReference, this), name);
      }
    } else {
      if (typeArguments != null) {
        return new InvalidAccessProto(
            new GenericExtensionTypeProto(
                extensionTypeReference, this, typeArguments),
            name);
      } else {
        return memberToProto(member, name);
      }
    }
  }
}

/// Base implementation for creating a [TypeDeclarationScope] for an enum.
abstract base class BaseEnumScope implements TypeDeclarationScope {
  EnumReference get enumReference;

  // TODO(johnniwinther): Support constructor lookup to support full parsing.

  Proto createMemberProto<T>(List<TypeAnnotation>? typeArguments, String name,
      T? member, Proto Function(T, String) memberToProto) {
    if (member == null) {
      if (typeArguments != null) {
        return new UnresolvedAccess(
            new GenericEnumProto(enumReference, this, typeArguments), name);
      } else {
        return new UnresolvedAccess(new EnumProto(enumReference, this), name);
      }
    } else {
      if (typeArguments != null) {
        return new InvalidAccessProto(
            new GenericEnumProto(enumReference, this, typeArguments), name);
      } else {
        return memberToProto(member, name);
      }
    }
  }
}

/// Base implementation for creating a [TypeDeclarationScope] for a mixin.
abstract base class BaseMixinScope implements TypeDeclarationScope {
  MixinReference get mixinReference;

  Proto createMemberProto<T>(List<TypeAnnotation>? typeArguments, String name,
      T? member, Proto Function(T, String) memberToProto) {
    if (member == null) {
      if (typeArguments != null) {
        return new UnresolvedAccess(
            new GenericMixinProto(mixinReference, this, typeArguments), name);
      } else {
        return new UnresolvedAccess(new MixinProto(mixinReference, this), name);
      }
    } else {
      if (typeArguments != null) {
        return new InvalidAccessProto(
            new GenericMixinProto(mixinReference, this, typeArguments), name);
      } else {
        return memberToProto(member, name);
      }
    }
  }
}

/// Base implementation for creating a [TypeDeclarationScope] for a typedef.
abstract base class BaseTypedefScope implements TypeDeclarationScope {
  TypedefReference get typedefReference;

  Proto createConstructorProto(List<TypeAnnotation>? typeArguments,
      ConstructorReference constructorReference) {
    return new ConstructorProto(
        typedefReference, typeArguments ?? const [], constructorReference);
  }

  Proto createMemberProto(List<TypeAnnotation>? typeArguments, String name) {
    if (typeArguments != null) {
      return new UnresolvedAccess(
          new GenericTypedefProto(typedefReference, this, typeArguments), name);
    } else {
      return new UnresolvedAccess(
          new TypedefProto(typedefReference, this), name);
    }
  }
}
