using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;

/// <summary>
///     Translates a Galaxy object hierarchy (from <see cref="IGalaxyHierarchySource"/>) into
///     <see cref="IAddressSpaceBuilder"/> calls — folders for each gobject, variables for
///     each dynamic attribute. Alarm-bearing attributes get all five sub-attribute refs
///     populated via <see cref="AlarmRefBuilder"/> so the server-level alarm subsystem
///     (PR 2.2) can subscribe + ack without help from the driver.
/// </summary>
/// <remarks>
///     Hierarchy materialisation rules (mirror legacy <c>MxAccessGalaxyBackend.DiscoverAsync</c>):
///     <list type="bullet">
///       <item>Browse name = <c>contained_name</c> when present; falls back to <c>tag_name</c>.</item>
///       <item>Folder per gobject; variables placed inside their owner folder.</item>
///       <item>Variable's full reference = <c>tag_name.attribute_name</c> — the format MXAccess
///         expects for read/write addressing (translated from the contained-name browse path).</item>
///       <item>Hierarchy is rendered flat (one folder per gobject under the driver root) for
///         this PR. PR 4.W's address-space wiring revisits whether to nest under
///         <c>parent_gobject_id</c> for a true tree shape.</item>
///     </list>
/// </remarks>
public sealed class GalaxyDiscoverer
{
    private readonly IGalaxyHierarchySource _source;

    /// <summary>Initializes a new GalaxyDiscoverer with the specified hierarchy source.</summary>
    /// <param name="source">The Galaxy hierarchy source to use for discovery.</param>
    public GalaxyDiscoverer(IGalaxyHierarchySource source)
    {
        _source = source ?? throw new ArgumentNullException(nameof(source));
    }

    /// <summary>
    ///     Drive the supplied builder with one folder + N variables per Galaxy object the
    ///     gateway returns. Idempotent — caller can re-invoke after a redeploy event.
    /// </summary>
    /// <param name="builder">The address space builder to populate with discovery results.</param>
    /// <param name="cancellationToken">The cancellation token for the operation.</param>
    public async Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(builder);
        var objects = await _source.GetHierarchyAsync(cancellationToken).ConfigureAwait(false);

        foreach (var obj in objects)
        {
            var browseName = string.IsNullOrEmpty(obj.ContainedName) ? obj.TagName : obj.ContainedName;
            if (string.IsNullOrEmpty(browseName)) continue; // skip objects with no usable identity

            var folder = builder.Folder(browseName, browseName);

            foreach (var attr in obj.Attributes)
            {
                if (string.IsNullOrEmpty(attr.AttributeName)) continue;

                var fullReference = !string.IsNullOrEmpty(attr.FullTagReference)
                    ? StripArraySuffix(attr.FullTagReference)
                    : obj.TagName + "." + attr.AttributeName;

                var info = new DriverAttributeInfo(
                    FullName: fullReference,
                    DriverDataType: DataTypeMap.Map(attr.MxDataType),
                    IsArray: attr.IsArray,
                    ArrayDim: attr.IsArray && attr.ArrayDimensionPresent && attr.ArrayDimension > 0
                        ? (uint)attr.ArrayDimension
                        : null,
                    SecurityClass: SecurityMap.Map(attr.SecurityClassification),
                    IsHistorized: attr.IsHistorized,
                    IsAlarm: attr.IsAlarm);

                var handle = folder.Variable(attr.AttributeName, attr.AttributeName, info);

                // Alarm-bearing attributes ship the full sub-attribute ref set so the server's
                // AlarmConditionService can subscribe + ack-write without re-deriving the names.
                if (attr.IsAlarm)
                {
                    handle.MarkAsAlarmCondition(AlarmRefBuilder.Build(fullReference));
                }
            }
        }
    }

    // PR 5.W workaround for mxaccessgw GalaxyRepository.cs:173-175 — the gateway's
    // SQL appends `[]` to array-typed `full_tag_reference` values, but MxAccess COM
    // `IInstance.AddItem` doesn't accept `[]`-suffixed addresses (so any downstream
    // Subscribe/Read/Write through the worker would fail with the suffixed form).
    // Strip defensively here so the parity matrix can run today; remove once the
    // gw fix (mxaccessgw/requirements-array-suffix-fix.md) lands.
    private static string StripArraySuffix(string fullReference) =>
        fullReference.EndsWith("[]", StringComparison.Ordinal)
            ? fullReference[..^2]
            : fullReference;
}
