using ZB.MOM.WW.OtOpcUa.Commons.Types;

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

/// <summary>
///     v3 dual-namespace NodeId + namespace-URI authority. Two OPC UA namespaces expose the same
///     underlying values under two identity schemes:
///     <list type="bullet">
///       <item><b>Raw</b> (<see cref="RawNamespaceUri"/>): the device-oriented tree
///         Folder→Driver→Device→TagGroup→Tag. A node's <c>s=</c> identifier IS its
///         <see cref="RawPaths">RawPath</see> — folders/drivers/devices/groups included (each keys
///         on its own RawPath prefix).</item>
///       <item><b>Uns</b> (<see cref="UnsNamespaceUri"/>): the equipment-oriented tree
///         Area→Line→Equipment→signal. A node's <c>s=</c> identifier is the slash-joined
///         <c>Area/Line/Equipment[/EffectiveName]</c>.</item>
///     </list>
///     These replace the single <c>https://zb.com/otopcua/ns</c> namespace and the retired
///     <c>EquipmentNodeIds</c> ({equipmentId}/{folderPath}/{name}) scheme. Realm travels alongside
///     the identifier as an <see cref="AddressSpaceRealm"/> at every sink seam — the namespace is
///     never parsed back out of the id string. Both schemes share <see cref="RawPaths.Separator"/>
///     and its ordinal, case-sensitive segment charset so identity is enforced in one place.
/// </summary>
public static class V3NodeIds
{
    /// <summary>The Raw namespace URI (device-oriented subtree).</summary>
    public const string RawNamespaceUri = "https://zb.com/otopcua/raw";

    /// <summary>The UNS namespace URI (equipment-oriented subtree).</summary>
    public const string UnsNamespaceUri = "https://zb.com/otopcua/uns";

    /// <summary>The namespace URI for a realm.</summary>
    /// <param name="realm">The address-space realm.</param>
    /// <returns>The realm's namespace URI.</returns>
    /// <exception cref="ArgumentOutOfRangeException">Unknown realm.</exception>
    public static string NamespaceUri(AddressSpaceRealm realm) => realm switch
    {
        AddressSpaceRealm.Raw => RawNamespaceUri,
        AddressSpaceRealm.Uns => UnsNamespaceUri,
        _ => throw new ArgumentOutOfRangeException(nameof(realm), realm, "Unknown address-space realm."),
    };

    // ----- Raw realm -----

    /// <summary>
    ///     The Raw-namespace <c>s=</c> identifier for a raw node — identical to its
    ///     <see cref="RawPaths">RawPath</see> (folders, drivers, devices, groups, and tags all key
    ///     on their own RawPath). A pass-through seam so call sites read intent rather than a bare
    ///     string; the argument is expected to already be a RawPaths-built path.
    /// </summary>
    /// <param name="rawPath">The (already-built) RawPath.</param>
    /// <returns>The Raw-namespace <c>s=</c> identifier (== <paramref name="rawPath"/>).</returns>
    public static string Raw(string rawPath)
    {
        ArgumentException.ThrowIfNullOrEmpty(rawPath);
        return rawPath;
    }

    // ----- Uns realm -----

    /// <summary>
    ///     The UNS-namespace <c>s=</c> identifier for a folder or variable, slash-joined from
    ///     ordered segments (Area, then Line, then Equipment, then an optional EffectiveName).
    ///     Every segment is validated via <see cref="RawPaths.ValidateSegment"/> (no embedded
    ///     separator, no leading/trailing whitespace) so a UNS id round-trips like a RawPath.
    /// </summary>
    /// <param name="segments">The ordered, non-empty segments (Area → leaf).</param>
    /// <returns>The slash-joined UNS <c>s=</c> identifier.</returns>
    /// <exception cref="ArgumentException">A segment is invalid, or the sequence is empty.</exception>
    public static string Uns(params string[] segments) => UnsFromSegments(segments);

    /// <summary>Build a UNS <c>s=</c> identifier from ordered segments. See <see cref="Uns(string[])"/>.</summary>
    /// <param name="segments">The ordered, non-empty segments (Area → leaf).</param>
    /// <returns>The slash-joined UNS <c>s=</c> identifier.</returns>
    public static string Uns(IEnumerable<string> segments) => UnsFromSegments(segments);

    /// <summary>
    ///     Append a child segment (a Line under an Area, an Equipment under a Line, an
    ///     EffectiveName under an Equipment) to an already-built UNS path.
    /// </summary>
    /// <param name="parentUnsPath">The parent UNS path (assumed already valid).</param>
    /// <param name="childSegment">The child segment to append.</param>
    /// <returns>The combined UNS <c>s=</c> identifier.</returns>
    /// <exception cref="ArgumentException">The child segment is invalid, or the parent is blank.</exception>
    public static string UnsChild(string parentUnsPath, string childSegment)
    {
        ArgumentException.ThrowIfNullOrEmpty(parentUnsPath);
        var error = RawPaths.ValidateSegment(childSegment);
        if (error is not null)
            throw new ArgumentException($"Invalid UNS NodeId segment ('{childSegment}'): {error}", nameof(childSegment));

        return parentUnsPath + RawPaths.Separator + childSegment;
    }

    private static string UnsFromSegments(IEnumerable<string> segments)
    {
        ArgumentNullException.ThrowIfNull(segments);
        var list = segments as IReadOnlyList<string> ?? segments.ToList();
        if (list.Count == 0)
            throw new ArgumentException("A UNS NodeId must have at least one segment.", nameof(segments));

        for (var i = 0; i < list.Count; i++)
        {
            var error = RawPaths.ValidateSegment(list[i]);
            if (error is not null)
                throw new ArgumentException($"Invalid UNS NodeId segment at index {i} ('{list[i]}'): {error}", nameof(segments));
        }

        return string.Join(RawPaths.Separator, list);
    }
}
