namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;

/// <summary>
///     Process-singleton registry of driver types known to this OtOpcUa instance.
///     Per-driver assemblies register their type metadata at startup; the Core uses
///     the registry to validate <c>DriverInstance.DriverType</c> values from the central config DB.
/// </summary>
/// <remarks>
///     Per <c>docs/v2/plan.md</c> decisions on JSON content validation happening in the Admin app
///     (not SQL CLR), and on driver type → namespace kind mapping being enforced by
///     <c>sp_ValidateDraft</c>. The registry is the source of truth for both checks.
///
///     Thread-safety: registration is typically single-threaded at startup; lookups happen on
///     every config-apply (multi-threaded). The check-then-act inside <see cref="Register"/> is
///     guarded by a private lock so concurrent registrations are atomic — the "registered only
///     once per process" guarantee holds even if two callers race. Readers operate against the
///     volatile snapshot reference produced by the last successful <see cref="Register"/> and
///     never block.
/// </remarks>
public sealed class DriverTypeRegistry
{
    private readonly Lock _writeLock = new();

    private IReadOnlyDictionary<string, DriverTypeMetadata> _types =
        new Dictionary<string, DriverTypeMetadata>(StringComparer.OrdinalIgnoreCase);

    /// <summary>Register a driver type. Throws if the type name is already registered.</summary>
    /// <remarks>
    ///     The check-then-act (duplicate check → copy-on-write rebuild → swap) is performed under
    ///     <see cref="_writeLock"/> so concurrent <see cref="Register"/> calls cannot silently
    ///     discard each other's registrations.
    /// </remarks>
    /// <param name="metadata">The driver type metadata to register.</param>
    public void Register(DriverTypeMetadata metadata)
    {
        ArgumentNullException.ThrowIfNull(metadata);

        lock (_writeLock)
        {
            var snapshot = _types;
            if (snapshot.ContainsKey(metadata.TypeName))
            {
                throw new InvalidOperationException(
                    $"Driver type '{metadata.TypeName}' is already registered. " +
                    $"Each driver type may be registered only once per process.");
            }

            var next = new Dictionary<string, DriverTypeMetadata>(snapshot, StringComparer.OrdinalIgnoreCase)
            {
                [metadata.TypeName] = metadata,
            };
            _types = next;
        }
    }

    /// <summary>Look up a driver type by name. Throws if unknown.</summary>
    /// <param name="driverType">The driver type name to look up.</param>
    /// <returns>The registered metadata for the driver type.</returns>
    public DriverTypeMetadata Get(string driverType)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(driverType);

        if (_types.TryGetValue(driverType, out var metadata))
            return metadata;

        throw new KeyNotFoundException(
            $"Driver type '{driverType}' is not registered. " +
            $"Known types: {string.Join(", ", _types.Keys)}.");
    }

    /// <summary>Try to look up a driver type by name. Returns null if unknown (no exception).</summary>
    /// <param name="driverType">The driver type name to look up.</param>
    /// <returns>The registered metadata, or <see langword="null"/> if the type is not registered.</returns>
    public DriverTypeMetadata? TryGet(string driverType)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(driverType);
        return _types.GetValueOrDefault(driverType);
    }

    /// <summary>Snapshot of all registered driver types.</summary>
    /// <returns>A snapshot collection of all registered driver type metadata.</returns>
    public IReadOnlyCollection<DriverTypeMetadata> All() => _types.Values.ToList();
}

/// <summary>Per-driver-type metadata used by the Core, validator, and Admin UI.</summary>
/// <param name="TypeName">Driver type name (matches <c>DriverInstance.DriverType</c> column values).</param>
/// <param name="DriverConfigJsonSchema">JSON Schema (Draft 2020-12) the driver's <c>DriverConfig</c> column must validate against.</param>
/// <param name="DeviceConfigJsonSchema">JSON Schema for <c>DeviceConfig</c> (multi-device drivers); null if the driver has no device layer.</param>
/// <param name="TagConfigJsonSchema">JSON Schema for <c>TagConfig</c>; required for every driver since every driver has tags.</param>
/// <param name="Tier">
///     Stability tier per <c>docs/v2/driver-stability.md</c> §2-4 and the tiering decisions in
///     <c>docs/v2/plan.md</c>. Drives the shared resilience pipeline defaults
///     (<see cref="Tier"/> × capability → <c>CapabilityPolicy</c>), the <c>MemoryTracking</c>
///     hybrid-formula constants, and whether process-level <c>MemoryRecycle</c> / scheduled-
///     recycle protections apply (Tier C only). Every registered driver type must declare one.
/// </param>
// v3: AllowedNamespaceKinds retired with the Namespace entity — the two OPC UA namespaces (Raw/UNS)
// are implicit, so a driver type no longer declares a namespace-kind compatibility bitmask.
public sealed record DriverTypeMetadata(
    string TypeName,
    string DriverConfigJsonSchema,
    string? DeviceConfigJsonSchema,
    string TagConfigJsonSchema,
    DriverTier Tier);
