using FrameworkBLL.ReportOrchestration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Reflection;
namespace FrameworkSL.ReportOrchestration
{
///
/// Singleton registry populated at startup by scanning module assemblies for
/// ReportEndpointBase subclasses decorated with [ReportEndpoint].
/// Implements IReportEndpointRegistry (defined in FrameworkBLL) so the
/// QueryOrchestrator can use it without depending on FrameworkSL.
/// Thread-safe after initial population (write-once, read-many).
///
public sealed class ReportEndpointRegistry : IReportEndpointRegistry
{
private readonly IServiceProvider _sp;
private readonly ILogger _logger;
private readonly Dictionary _types
= new(StringComparer.OrdinalIgnoreCase);
public ReportEndpointRegistry(
IServiceProvider sp,
ILogger logger)
{
_sp = sp;
_logger = logger;
}
///
/// Scans the assembly for ReportEndpointBase subclasses with [ReportEndpoint]
/// and registers them by their ReportCode.
/// Called once at startup from AddReportOrchestration().
///
public void ScanAssembly(Assembly assembly)
{
var endpointTypes = assembly.GetTypes()
.Where(t => !t.IsAbstract
&& t.IsClass
&& t.IsSubclassOf(typeof(ReportEndpointBase)));
foreach (var type in endpointTypes)
{
var attr = type.GetCustomAttribute(inherit: true);
if (attr is null || string.IsNullOrWhiteSpace(attr.ReportCode))
continue;
if (_types.TryAdd(attr.ReportCode, type))
{
_logger.LogInformation(
"ReportEndpointRegistry: registered {Type} for ReportCode='{Code}'",
type.Name, attr.ReportCode);
}
else
{
_logger.LogWarning(
"ReportEndpointRegistry: duplicate ReportCode='{Code}' — {Existing} kept, {New} ignored.",
attr.ReportCode, _types[attr.ReportCode].Name, type.Name);
}
}
}
public IReportEndpoint? Resolve(string reportCode)
{
if (!_types.TryGetValue(reportCode, out var type))
{
_logger.LogWarning(
"ReportEndpointRegistry: no endpoint registered for ReportCode='{Code}'.",
reportCode);
return null;
}
// Create a new scope so Scoped dependencies (DAL, BLL, connections) are
// properly lifetime-managed per resolve call.
using var scope = _sp.CreateScope();
return scope.ServiceProvider.GetRequiredService(type) as IReportEndpoint;
}
public IReadOnlyList GetRegisteredReportCodes()
=> _types.Keys.ToList();
}
}