namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;

/// <summary>
///     Parsed TwinCAT symbolic tag path. Handles global-variable-list (<c>GVL.Counter</c>),
///     program-variable (<c>MAIN.bStart</c>), structured member access
///     (<c>Motor1.Status.Running</c>), array subscripts (<c>Data[5]</c>), multi-dim arrays
///     (<c>Matrix[1,2]</c>), and bit-access (<c>Flags.0</c>).
/// </summary>
/// <remarks>
///     <para>TwinCAT's symbolic syntax mirrors IEC 61131-3 structured-text identifiers — so the
///     grammar maps cleanly onto the AbCip Logix path parser, but without Logix's
///     <c>Program:</c> scope prefix. The leading segment is the namespace (POU name /
///     GVL name) and subsequent segments walk into struct/array members.</para>
/// </remarks>
public sealed record TwinCATSymbolPath(
    IReadOnlyList<TwinCATSymbolSegment> Segments,
    int? BitIndex)
{
    /// <summary>Converts this path to an ADS symbol name.</summary>
    /// <returns>The ADS symbol name string.</returns>
    public string ToAdsSymbolName()
    {
        var buf = new System.Text.StringBuilder();
        for (var i = 0; i < Segments.Count; i++)
        {
            if (i > 0) buf.Append('.');
            var seg = Segments[i];
            buf.Append(seg.Name);
            if (seg.Subscripts.Count > 0)
                buf.Append('[').Append(string.Join(",", seg.Subscripts)).Append(']');
        }
        if (BitIndex is not null) buf.Append('.').Append(BitIndex.Value);
        return buf.ToString();
    }

    /// <summary>Attempts to parse a TwinCAT symbol path from the given string.</summary>
    /// <param name="value">The path string to parse.</param>
    /// <returns>A TwinCATSymbolPath if parsing succeeds; otherwise null.</returns>
    public static TwinCATSymbolPath? TryParse(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return null;
        var src = value.Trim();

        var parts = new List<string>();
        var depth = 0;
        var start = 0;
        for (var i = 0; i < src.Length; i++)
        {
            var c = src[i];
            if (c == '[') depth++;
            else if (c == ']') depth--;
            else if (c == '.' && depth == 0)
            {
                parts.Add(src[start..i]);
                start = i + 1;
            }
        }
        parts.Add(src[start..]);
        if (depth != 0 || parts.Any(string.IsNullOrEmpty)) return null;

        int? bitIndex = null;
        if (parts.Count >= 2 && int.TryParse(parts[^1], out var maybeBit)
            && maybeBit is >= 0 and <= 31
            && !parts[^1].Contains('['))
        {
            bitIndex = maybeBit;
            parts.RemoveAt(parts.Count - 1);
        }

        var segments = new List<TwinCATSymbolSegment>(parts.Count);
        foreach (var part in parts)
        {
            var bracketIdx = part.IndexOf('[');
            if (bracketIdx < 0)
            {
                if (!IsValidIdent(part)) return null;
                segments.Add(new TwinCATSymbolSegment(part, []));
                continue;
            }
            if (!part.EndsWith(']')) return null;
            var name = part[..bracketIdx];
            if (!IsValidIdent(name)) return null;
            var inner = part[(bracketIdx + 1)..^1];
            var subs = new List<int>();
            foreach (var tok in inner.Split(','))
            {
                if (!int.TryParse(tok, out var n) || n < 0) return null;
                subs.Add(n);
            }
            if (subs.Count == 0) return null;
            segments.Add(new TwinCATSymbolSegment(name, subs));
        }
        if (segments.Count == 0) return null;

        return new TwinCATSymbolPath(segments, bitIndex);
    }

    private static bool IsValidIdent(string s)
    {
        if (string.IsNullOrEmpty(s)) return false;
        if (!char.IsLetter(s[0]) && s[0] != '_') return false;
        for (var i = 1; i < s.Length; i++)
            if (!char.IsLetterOrDigit(s[i]) && s[i] != '_') return false;
        return true;
    }
}

public sealed record TwinCATSymbolSegment(string Name, IReadOnlyList<int> Subscripts);
