import '../runtime_interfaces.dart';
import '../exceptions.dart';
import '../interpreter_visitor.dart';
import 'registration.dart' hide BridgedMethodCallable;
import '../callable.dart';

/// Represents a natively defined class but accessible to the interpreter.
class BridgedClass implements RuntimeType {
  final Type nativeType; // Keep nativeType for bridge logic
  @override
  final String name;

  /// Additional native class names that should map to this bridged class.
  /// This is essential for mapping Dart's internal implementation classes to their public interfaces.
  ///
  /// For example, Stream has many internal implementations like '_MultiStream', '_ControllerStream',
  /// '_BroadcastStream', etc. When the interpreter encounters these native objects at runtime,
  /// it needs to know they should be treated as 'Stream' instances.
  ///
  /// Without nativeNames:
  /// - runtime error: "No registered bridged class found for native type _MultiStream"
  ///
  /// With nativeNames: ['_MultiStream', '_ControllerStream', ...]:
  /// - The environment can map these internal types to the Stream bridge
  /// - Methods like toList(), listen(), etc. become available on these objects
  ///
  /// Used by Environment.toBridgedClass() to perform fallback lookups when
  /// the exact nativeType doesn't match any registered bridge.
  final List<String>? nativeNames;

  /// A function that determines if the current runtime type is a subtype of another runtime type.
  ///
  /// This function is used to perform subtype checking at runtime, which is essential
  /// for type safety and polymorphism in the bridge system.
  ///
  /// Parameters:
  /// - [other]: The runtime type to check against for subtype relationship
  /// - [value]: Optional value that can be used for additional context during subtype checking
  ///
  /// Returns:
  /// - `true` if this type is a subtype of [other]
  /// - `false` if this type is not a subtype of [other]
  ///
  /// The function can be null if subtype checking is not supported or not needed
  /// for this particular runtime type.
  final bool Function(BridgedClass other, {Object? value})? isSubtypeOfFunc;
  // Number of expected type parameters
  final int typeParameterCount;

  // Support for mixin usage
  final bool canBeUsedAsMixin;

  // Adapters for constructors
  Map<String, BridgedConstructorCallable> constructors = {};
  // Adapters for instance methods
  Map<String, BridgedMethodAdapter> methods = {};
  // Adapters for static members and getters/setters
  Map<String, BridgedStaticMethodAdapter> staticMethods = {};
  Map<String, BridgedStaticGetterAdapter> staticGetters = {};
  Map<String, BridgedStaticSetterAdapter> staticSetters = {};
  Map<String, BridgedInstanceGetterAdapter> getters = {};
  Map<String, BridgedInstanceSetterAdapter> setters = {};

  BridgedClass(
      {required this.nativeType,
      required this.name,
      this.nativeNames,
      this.typeParameterCount = 0,
      this.canBeUsedAsMixin = false,
      this.constructors = const {},
      this.staticMethods = const {},
      this.staticGetters = const {},
      this.staticSetters = const {},
      this.methods = const {},
      this.getters = const {},
      this.setters = const {},
      this.isSubtypeOfFunc});

  @override
  bool isSubtypeOf(RuntimeType other, {Object? value}) {
    if (other is BridgedClass) {
      if (isSubtypeOfFunc != null) {
        return isSubtypeOfFunc!.call(other, value: value);
      }
      if (name == 'num') {
        final isSubtype = switch (other.name) {
          'num' => true,
          'int' => true,
          'double' => true,
          _ => false,
        };
        return isSubtype;
      }

      return nativeType == other.nativeType;
    }

    return false;
  }

  // Method to find a constructor adapter
  BridgedConstructorCallable? findConstructorAdapter(String name) {
    return constructors[name];
  }

  // Method to find an instance method adapter
  BridgedMethodAdapter? findInstanceMethodAdapter(String name) {
    return methods[name];
  }

  // Finders for other adapters
  BridgedStaticMethodAdapter? findStaticMethodAdapter(String name) {
    return staticMethods[name];
  }

  BridgedStaticGetterAdapter? findStaticGetterAdapter(String name) {
    return staticGetters[name];
  }

  BridgedStaticSetterAdapter? findStaticSetterAdapter(String name) {
    return staticSetters[name];
  }

  BridgedInstanceGetterAdapter? findInstanceGetterAdapter(String name) {
    return getters[name];
  }

  BridgedInstanceSetterAdapter? findInstanceSetterAdapter(String name) {
    return setters[name];
  }
}

/// Represents an instance of a bridged native class.
class BridgedInstance<T extends Object> implements RuntimeValue {
  final BridgedClass bridgedClass;
  final T nativeObject;
  // Stores the type arguments provided at creation
  final List<RuntimeType> typeArguments;

  // Main constructor
  BridgedInstance(this.bridgedClass, this.nativeObject,
      {this.typeArguments = const []}); // Removed local initialization

  @override
  RuntimeType get valueType => bridgedClass;

  @override
  Object? get(String name) {
    // 1. Check if it's a BRIDGED instance METHOD
    final methodAdapter = bridgedClass.findInstanceMethodAdapter(name);
    if (methodAdapter != null) {
      // Return a Callable bound to this instance and the adapter
      return BridgedMethodCallable(this, methodAdapter, name);
    }

    // This should be handled by visitors (PrefixedIdentifier, PropertyAccess)
    // for them to have access to the visitor if necessary.
    // The logic here is simplified and could be incorrect if a getter
    // would need to be returned as a value.

    // 3. If neither method nor getter found, throw an error
    throw RuntimeError(
        "Undefined property or method '$name' on bridged instance of '${bridgedClass.name}'");
  }

  @override
  void set(String name, Object? value, [InterpreterVisitor? visitor]) {
    // Visitor is optional
    throw UnimplementedError(
        "set('$name', ...) not implemented for BridgedInstance of '${bridgedClass.name}'");
  }

  @override
  String toString() {
    // Delegate to the native toString() method? Or just a representation?
    try {
      return nativeObject.toString();
    } catch (_) {
      return "Instance of native '${bridgedClass.name}'";
    }
  }
}

/// Represents a generic type parameter like T, U, etc.
class TypeParameter implements RuntimeType {
  @override
  final String name;
  final RuntimeType?
      bound; // The extends clause if any (e.g., T extends Object)

  TypeParameter(this.name, {this.bound});

  @override
  bool isSubtypeOf(RuntimeType other, {Object? value}) {
    // For now, type parameters accept any type as a subtype
    // This is because we don't have full generic type inference yet
    // In a real type system, this would be more sophisticated
    return true;
  }

  @override
  String toString() => name;
}
