using System.Globalization;

namespace ZB.MOM.WW.ScadaBridge.Commons.Types.DataConnections;

/// <summary>
/// Pure, protocol-package-free classification of an OPC UA node-reference string's
/// <i>form</i> — used by authoring UI to nudge operators toward index-durable bindings.
/// </summary>
/// <remarks>
/// A bare <c>ns=&lt;index&gt;;…</c> reference hard-codes a namespace <i>index</i>, which is
/// only meaningful relative to one server's namespace table. The OtOpcUa v3.0 raw/uns
/// split makes this actively dangerous: v2's sole custom namespace and v3's <c>raw</c>
/// tree both land at <c>ns=2</c>, so a stored <c>ns=2;s=…</c> resolves without error but
/// silently now means a raw-tree node. The durable form is <c>nsu=&lt;uri&gt;;…</c>,
/// resolved against the live namespace table at use time by
/// <c>OpcUaNodeReference.Resolve</c>. This helper only classifies the string; it does not
/// parse or resolve NodeIds (that stays in the DataConnectionLayer seam, which owns the
/// <c>Opc.Ua</c> dependency).
/// </remarks>
public static class OpcUaReferenceForm
{
    /// <summary>
    /// True when <paramref name="reference"/> is index-durable — the durable
    /// <c>nsu=&lt;uri&gt;;…</c> form, spec-fixed <c>ns=0</c>, a short form with no namespace
    /// prefix, or empty/whitespace (nothing to warn about). False only for a bare
    /// server-namespace index (<c>ns=&lt;n&gt;;…</c> with n ≥ 1).
    /// </summary>
    public static bool IsDurable(string? reference)
    {
        if (string.IsNullOrWhiteSpace(reference))
            return true;

        var trimmed = reference.Trim();

        // nsu=<uri>;… is the durable form (and its "ns" prefix must be checked before the
        // bare "ns=" branch below, since "nsu=" also starts with "ns").
        if (trimmed.StartsWith("nsu=", StringComparison.OrdinalIgnoreCase))
            return true;

        // Not a bare namespace-index reference at all → short forms (i=85, s=Foo) that
        // imply namespace 0, which is spec-fixed and already durable.
        if (!trimmed.StartsWith("ns=", StringComparison.OrdinalIgnoreCase))
            return true;

        // ns=<digits>;…  — parse the index; ns=0 is spec-fixed (durable), n ≥ 1 is bare.
        var semicolon = trimmed.IndexOf(';');
        var digits = semicolon >= 0 ? trimmed[3..semicolon] : trimmed[3..];
        if (int.TryParse(digits, NumberStyles.None, CultureInfo.InvariantCulture, out var index))
            return index == 0;

        // Malformed "ns=" with non-numeric index — not a recognisable bare index; leave
        // the real parse/reject to OpcUaNodeReference.Resolve. Don't warn on it here.
        return true;
    }
}
