using Avalonia.Controls; using dotnetCampus.Ipc.CompilerServices.Attributes; using Microsoft.Extensions.DependencyInjection; namespace LanMountainDesktop.AirAppSdk; public static class AirAppServiceCollectionExtensions { public static IServiceCollection AddAirAppSettingsSection( this IServiceCollection services, string id, string titleLocalizationKey, Action configure, string? descriptionLocalizationKey = null, string iconKey = "PuzzlePiece", int sortOrder = 0) { ArgumentNullException.ThrowIfNull(services); ArgumentNullException.ThrowIfNull(configure); var builder = new AirAppSettingsSectionBuilder( id, titleLocalizationKey, descriptionLocalizationKey, iconKey, sortOrder); configure(builder); services.AddSingleton(builder.Build()); return services; } /// /// Registers a plugin settings section with a custom AXAML view. /// The host application will display directly /// in the settings window, allowing the plugin to use any Fluent Avalonia controls /// and custom layouts — just like built-in settings pages. /// /// A subclass that defines the settings UI using AXAML. public static IServiceCollection AddAirAppSettingsSection( this IServiceCollection services, string id, string titleLocalizationKey, string? descriptionLocalizationKey = null, string iconKey = "PuzzlePiece", int sortOrder = 0) where TView : AirAppSettingsPageBase { ArgumentNullException.ThrowIfNull(services); var builder = new AirAppSettingsSectionBuilder( id, titleLocalizationKey, descriptionLocalizationKey, iconKey, sortOrder); builder.SetCustomView(); services.AddSingleton(builder.Build()); return services; } public static IServiceCollection AddAirAppComponent( this IServiceCollection services, AirAppComponentOptions options) where TControl : Control { ArgumentNullException.ThrowIfNull(services); ArgumentNullException.ThrowIfNull(options); services.AddSingleton(new AirAppComponentRegistration( (provider, context) => ActivatorUtilities.CreateInstance(provider, context), options)); return services; } public static IServiceCollection AddAirAppComponentEditor( this IServiceCollection services, string componentId, double preferredWidth = 720d, double preferredHeight = 540d, double minScale = 0.85d, double maxScale = 1.45d) where TControl : Control { ArgumentNullException.ThrowIfNull(services); services.AddSingleton(new AirAppComponentEditorRegistration( componentId, (provider, context) => ActivatorUtilities.CreateInstance(provider, context), preferredWidth, preferredHeight, minScale, maxScale)); return services; } /// /// Registers a window AirApp entry. /// The host resolves the window by and hosts its /// content inside the AirAppHost window shell. /// /// A class implementing . /// Unique window identifier. /// Display name. public static IServiceCollection AddAirAppWindow( this IServiceCollection services, string id, string name) where TWindow : class, IAirAppWindow { ArgumentNullException.ThrowIfNull(services); services.AddTransient(); services.AddSingleton(new AirAppWindowRegistration(id, name, typeof(TWindow))); return services; } public static IServiceCollection AddAirAppExport(this IServiceCollection services) where TContract : class where TImplementation : class, TContract { ArgumentNullException.ThrowIfNull(services); EnsureSingletonRegistration(services); if (!services.Any(descriptor => descriptor.ServiceType == typeof(AirAppServiceExportRegistration) && descriptor.ImplementationInstance is AirAppServiceExportRegistration existing && existing.ContractType == typeof(TContract) && existing.ImplementationType == typeof(TImplementation))) { services.AddSingleton(new AirAppServiceExportRegistration(typeof(TContract), typeof(TImplementation))); } return services; } public static IServiceCollection AddAirAppPublicIpc( this IServiceCollection services, string? objectId = null, params string[] notifyIds) where TContract : class where TImplementation : class, TContract { ArgumentNullException.ThrowIfNull(services); EnsurePublicIpcContract(typeof(TContract)); EnsureSingletonRegistration(services); if (!services.Any(descriptor => descriptor.ServiceType == typeof(AirAppPublicIpcServiceRegistration) && descriptor.ImplementationInstance is AirAppPublicIpcServiceRegistration existing && existing.ContractType == typeof(TContract) && string.Equals(existing.ObjectId, objectId, StringComparison.Ordinal))) { services.AddSingleton(new AirAppPublicIpcServiceRegistration( typeof(TContract), objectId, notifyIds ?? [])); } return services; } public static IServiceCollection AddAirAppPublicIpcContributor(this IServiceCollection services) where TContributor : class, IAirAppPublicIpcContributor { ArgumentNullException.ThrowIfNull(services); services.AddSingleton(); return services; } private static void EnsurePublicIpcContract(Type contractType) { if (!contractType.IsInterface) { throw new InvalidOperationException( $"Public IPC contract '{contractType.FullName}' must be an interface."); } if (!Attribute.IsDefined(contractType, typeof(IpcPublicAttribute), inherit: false)) { throw new InvalidOperationException( $"Public IPC contract '{contractType.FullName}' must be marked with '{nameof(IpcPublicAttribute)}'."); } } private static void EnsureSingletonRegistration(IServiceCollection services) where TContract : class where TImplementation : class, TContract { var contractDescriptor = services.LastOrDefault(descriptor => descriptor.ServiceType == typeof(TContract)); if (contractDescriptor is null) { services.AddSingleton(); return; } if (contractDescriptor.Lifetime != ServiceLifetime.Singleton) { throw new InvalidOperationException( $"Exported contract '{typeof(TContract).FullName}' must be registered as Singleton."); } } }