using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

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

/// <summary>
///     Phase 6.4 Stream D: materializes the OPC 40010 Machinery companion-spec Identification
///     sub-folder under an Equipment node. Reads the nine documented columns off the
///     <see cref="Equipment"/> row and emits one property per non-null field.
/// </summary>
/// <remarks>
///     <para>Pure-function shape — testable without a real OPC UA node manager. The caller
///     passes the builder scoped to the Equipment node; this class handles the Identification
///     sub-folder creation + per-field <see cref="IAddressSpaceBuilder.AddProperty"/> calls.</para>
///
///     <para>ACL binding: the sub-folder + its properties inherit the Equipment scope's
///     grants (no new scope level). Phase 6.2's trie treats them as part of the Equipment
///     ScopeId — a user with Equipment-level grant reads Identification; a user without the
///     grant gets BadUserAccessDenied on both the Equipment node + its Identification variables.
///     See <c>docs/v2/acl-design.md</c> §Identification cross-reference.</para>
///
///     <para>The nine fields are exposed exactly when they carry a non-null
///     value. A row with all nine null produces no Identification sub-folder at all — the
///     caller can use <see cref="HasAnyFields(Equipment)"/> to skip the Folder call entirely
///     and avoid a pointless empty folder appearing in browse trees.</para>
/// </remarks>
public static class IdentificationFolderBuilder
{
    /// <summary>Browse + display name of the sub-folder — fixed per OPC 40010 convention.</summary>
    public const string FolderName = "Identification";

    /// <summary>
    ///     Canonical decision #139 field set exposed in the Identification sub-folder. Order
    ///     matches the decision-log entry so any browse-order reader can cross-reference
    ///     without re-sorting.
    /// </summary>
    public static IReadOnlyList<string> FieldNames { get; } = new[]
    {
        "Manufacturer", "Model", "SerialNumber",
        "HardwareRevision", "SoftwareRevision",
        "YearOfConstruction", "AssetLocation",
        "ManufacturerUri", "DeviceManualUri",
    };

    /// <summary>Checks whether the equipment row has at least one non-null Identification field.</summary>
    /// <param name="equipment">The equipment entity.</param>
    /// <returns>True if any Identification field is non-null; otherwise false.</returns>
    public static bool HasAnyFields(Equipment equipment)
    {
        ArgumentNullException.ThrowIfNull(equipment);
        return equipment.Manufacturer is not null
            || equipment.Model is not null
            || equipment.SerialNumber is not null
            || equipment.HardwareRevision is not null
            || equipment.SoftwareRevision is not null
            || equipment.YearOfConstruction is not null
            || equipment.AssetLocation is not null
            || equipment.ManufacturerUri is not null
            || equipment.DeviceManualUri is not null;
    }

    /// <summary>
    ///     Builds the Identification sub-folder under the given equipment builder. Returns the sub-folder builder
    ///     (or null when no fields are present) so callers can attach additional nodes if needed.
    /// </summary>
    /// <param name="equipmentBuilder">The equipment address space builder.</param>
    /// <param name="equipment">The equipment entity with identification fields.</param>
    /// <returns>The sub-folder builder, or null if no fields are present.</returns>
    public static IAddressSpaceBuilder? Build(IAddressSpaceBuilder equipmentBuilder, Equipment equipment)
    {
        ArgumentNullException.ThrowIfNull(equipmentBuilder);
        ArgumentNullException.ThrowIfNull(equipment);

        if (!HasAnyFields(equipment)) return null;

        var folder = equipmentBuilder.Folder(FolderName, FolderName);
        AddIfPresent(folder, "Manufacturer",       DriverDataType.String,   equipment.Manufacturer);
        AddIfPresent(folder, "Model",              DriverDataType.String,   equipment.Model);
        AddIfPresent(folder, "SerialNumber",       DriverDataType.String,   equipment.SerialNumber);
        AddIfPresent(folder, "HardwareRevision",   DriverDataType.String,   equipment.HardwareRevision);
        AddIfPresent(folder, "SoftwareRevision",   DriverDataType.String,   equipment.SoftwareRevision);
        AddIfPresent(folder, "YearOfConstruction", DriverDataType.Int32,
            equipment.YearOfConstruction is null ? null : (object)(int)equipment.YearOfConstruction.Value);
        AddIfPresent(folder, "AssetLocation",      DriverDataType.String,   equipment.AssetLocation);
        AddIfPresent(folder, "ManufacturerUri",    DriverDataType.String,   equipment.ManufacturerUri);
        AddIfPresent(folder, "DeviceManualUri",    DriverDataType.String,   equipment.DeviceManualUri);
        return folder;
    }

    private static void AddIfPresent(IAddressSpaceBuilder folder, string name, DriverDataType dataType, object? value)
    {
        if (value is null) return;
        folder.AddProperty(name, dataType, value);
    }
}
