part of "arcane_service.dart";

/// A provider that makes a list of `ArcaneService` instances available to the widget tree.
///
/// This class extends `InheritedNotifier` and allows `ArcaneService` instances to be
/// accessed throughout the widget tree by descendant widgets. It should be used to
/// provide service instances that are shared across the application.
///
/// Example:
/// ```dart
/// ArcaneServiceProvider(
///   serviceInstances: [myService],
///   child: MyApp(),
/// );
/// ```
/// To access the provided services:
/// ```dart
/// final provider = ArcaneServiceProvider.of(context);
/// final myService = ArcaneServiceProvider.serviceOfType<MyService>(context);
/// ```
class ArcaneServiceProvider
    extends InheritedNotifier<ValueNotifier<List<ArcaneService>>> {
  /// A list of `ArcaneService` instances available through the provider.
  List<ArcaneService> get registeredServices =>
      List<ArcaneService>.from([...?notifier?.value]);

  /// Creates an `ArcaneServiceProvider` that provides [serviceInstances] to the widget tree.
  ///
  /// If [serviceNotifier] is provided, it will be used as the backing notifier for the provider.
  /// Otherwise, a new notifier will be created from [serviceInstances].
  ArcaneServiceProvider({
    required super.child,
    List<ArcaneService> serviceInstances = const [],
    ValueNotifier<List<ArcaneService>>? serviceNotifier,
    super.key,
  }) : super(
          notifier: serviceNotifier ??
              ValueNotifier<List<ArcaneService>>(serviceInstances),
        );

  /// Retrieves the nearest `ArcaneServiceProvider` in the widget tree.
  ///
  /// Returns null if no provider is found.
  ///
  /// Example:
  /// ```dart
  /// final provider = ArcaneServiceProvider.maybeOf(context);
  /// ```
  static ArcaneServiceProvider? maybeOf(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ArcaneServiceProvider>();
  }

  /// Retrieves the nearest `ArcaneServiceProvider` in the widget tree.
  ///
  /// Throws an assertion error if no provider is found.
  ///
  /// Example:
  /// ```dart
  /// final provider = ArcaneServiceProvider.of(context);
  /// ```
  static ArcaneServiceProvider of(BuildContext context) {
    final provider = maybeOf(context);
    assert(provider != null, "No ArcaneServiceProvider found in context");
    return provider!;
  }

  /// Retrieves a service of type `T` from the nearest provider.
  ///
  /// Returns null if no service of type `T` is found or if no provider exists.
  ///
  /// Example:
  /// ```dart
  /// final myService = ArcaneServiceProvider.serviceOfType<MyService>(context);
  /// ```
  static T? serviceOfType<T extends ArcaneService>(BuildContext context) {
    final provider = maybeOf(context);
    if (provider == null) return null;

    return provider.registeredServices.whereType<T>().firstOrNull;
  }

  /// Retrieves a service of type `T` from the nearest provider.
  ///
  /// Throws an exception if no service of type `T` is found or if no provider exists.
  ///
  /// Example:
  /// ```dart
  /// final myService = ArcaneServiceProvider.requiredServiceOfType<MyService>(context);
  /// ```
  static T requiredServiceOfType<T extends ArcaneService>(
    BuildContext context,
  ) {
    final provider = maybeOf(context);
    assert(provider != null, "No ArcaneServiceProvider found");

    final T? service = provider!.registeredServices.whereType<T>().firstOrNull;

    assert(service != null, "No service of type $T found");

    return service!;
  }

  /// Updates the service instances in this provider.
  ///
  /// This will trigger a rebuild of all widgets that depend on this provider.
  void setServices(List<ArcaneService> newServices) {
    notifier?.value = newServices;
  }

  /// Adds a new service to this provider.
  ///
  /// If a service of the same type already exists, it will be replaced.
  void addService(ArcaneService service) {
    final int existingIndex = registeredServices.indexWhere(
      (s) => s.runtimeType == service.runtimeType,
    );

    final List<ArcaneService> newList =
        List<ArcaneService>.from(registeredServices);

    if (existingIndex >= 0) {
      newList[existingIndex] = service;
    } else {
      newList.add(service);
    }

    notifier?.value = newList;
  }

  /// Removes all services of the specified type from the registry.
  /// Returns true if any services were removed, false otherwise.
  bool removeService<T extends ArcaneService>() {
    final List<ArcaneService> newList =
        List<ArcaneService>.from(registeredServices);
    final int originalLength = newList.length;

    newList.removeWhere((service) => service.runtimeType == T);

    if (newList.length == originalLength) {
      return false;
    }

    notifier?.value = newList;
    return true;
  }
}

/// Typed service lookup entrypoint for `Arcane.service`.
class ArcaneServiceLookup {
  const ArcaneServiceLookup();

  /// Returns a service of type `T` from the closest provider in [context].
  ///
  /// Falls back to Arcane's built-in services when no provider instance exists.
  T? ofType<T extends ArcaneService>(BuildContext context) =>
      ArcaneService.ofType<T>(context);

  /// Returns a service of type `T` from the closest provider in [context].
  ///
  /// Throws an assertion error if no matching service is available.
  T requiredOfType<T extends ArcaneService>(BuildContext context) =>
      ArcaneService.requiredOfType<T>(context);
}
