using Microsoft.Extensions.Logging;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
namespace ZB.MOM.WW.OtOpcUa.Driver.MTConnect;
///
/// Static factory registration helper for . The Host registers it
/// once at startup; the bootstrapper then materialises MTConnect DriverInstance rows from
/// the deployed configuration into live driver instances. Mirrors
/// ModbusDriverFactoryExtensions / FocasDriverFactoryExtensions.
///
///
///
/// There is exactly one parser for the config document. This factory does not carry a
/// config DTO of its own — it delegates to , which
/// the driver itself must own because the runtime delivers a config change to a live
/// instance (DriverInstanceActor assigns its driver once and calls
/// ReinitializeAsync(newJson) thereafter). A second DTO here would drift from that
/// one silently, and the drift would first show up at deploy time as an operator's edit
/// being applied differently by the two paths — or not at all.
///
///
/// Construction opens nothing. The Wave-0 universal discovery browser builds a
/// throwaway instance through this factory purely to read
/// ITagDiscovery.SupportsOnlineDiscovery, and never initializes it — so a factory
/// that dialled the Agent (or that pre-built an ) would
/// open a connection per AdminUI browse render against a possibly-unreachable Agent. The
/// agent-client factory is therefore left null: builds the
/// production client inside InitializeAsync.
///
///
/// The instance is returned as its concrete type. The runtime resolves every optional
/// capability (, ,
/// , ,
/// ) by pattern-matching the object
/// hands back, so nothing here may narrow it — a
/// narrowed return is how a capability ends up implemented but never dispatched. There is
/// deliberately no IWritable: the MTConnect Agent surface is read-only.
///
///
public static class MTConnectDriverFactoryExtensions
{
///
/// The DriverInstance.DriverType value this factory answers to.
///
///
/// Aliased to , not a literal. The registry key,
/// the instance's self-reported , the probe's
/// DriverType, and the constant every dispatch map keys off are therefore the same
/// symbol — the repo-wide fix for the historical TwinCat/Focas drift, where a
/// driver's own spelling silently diverged from the constant and dispatch maps missed it.
/// Retained as a named const (rather than deleted in favour of the constant) so the existing
/// factory callers and the sibling *DriverFactoryExtensions.DriverTypeName convention
/// keep working; it is now an alias with no independent value.
///
public const string DriverTypeName = DriverTypeNames.MTConnect;
///
/// Register the MTConnect factory with the driver registry. The optional
/// is captured at registration time and used to construct
/// an per driver instance — without it the driver runs
/// with the null logger (tests and standalone callers stay unchanged).
///
/// The driver factory registry to register with.
/// Optional logger factory for creating loggers per driver instance.
public static void Register(DriverFactoryRegistry registry, ILoggerFactory? loggerFactory = null)
{
ArgumentNullException.ThrowIfNull(registry);
registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory));
}
/// Public for the Server-side bootstrapper + test consumers.
/// The unique identifier for the driver instance.
/// The driver's DriverConfig JSON.
/// The constructed instance.
public static MTConnectDriver CreateInstance(string driverInstanceId, string driverConfigJson)
=> CreateInstance(driverInstanceId, driverConfigJson, loggerFactory: null);
/// Logger-aware overload — used by 's closure when wired through DI.
/// The unique identifier for the driver instance.
/// The driver's DriverConfig JSON.
/// Optional logger factory for creating loggers per driver instance.
/// The constructed instance.
///
/// or is null or blank.
///
///
/// The config document is unparseable, deserialises to null, omits the required
/// agentUri, or carries an unauthorable enum/tag value. Throwing is correct at this
/// seam: a deployment carrying such a row must fail rather than register a driver pointed at
/// nothing, and the discovery browser's CanBrowse catches factory exceptions so a
/// half-authored config merely disables the address picker.
///
public static MTConnectDriver CreateInstance(
string driverInstanceId, string driverConfigJson, ILoggerFactory? loggerFactory)
{
ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson);
// The single config authority — see the class remarks for why this is not a second DTO.
// Note the asymmetry with the driver's own lifecycle path: an empty ("{}" / "[]") document
// there means "no config supplied, keep the constructor options", but this factory HAS no
// constructor options to keep, so an empty document is simply a config with no agentUri and
// must fault.
var options = MTConnectDriver.ParseOptions(driverInstanceId, driverConfigJson);
// Named arguments deliberately: the ctor's trailing parameters are all optional, so a
// future insertion could silently re-bind a positional argument to the wrong one.
return new MTConnectDriver(
options,
driverInstanceId,
agentClientFactory: null,
logger: loggerFactory?.CreateLogger());
}
}