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

/// <summary>
///     Parsed PCCC file-based address: file letter + file number + word number, optionally a
///     sub-element (<c>.ACC</c> on a timer) or bit index (<c>/0</c> on a bit file).
/// </summary>
/// <remarks>
///     <para>Logix symbolic tags are parsed elsewhere (<see cref="AbLegacy"/> is for SLC / PLC-5 /
///     MicroLogix — no symbol table; everything is file-letter + file-number + word-number).</para>
///     <list type="bullet">
///         <item><c>N7:0</c> — integer file 7, word 0 (signed 16-bit).</item>
///         <item><c>N7:5</c> — integer file 7, word 5.</item>
///         <item><c>F8:0</c> — float file 8, word 0 (32-bit IEEE754).</item>
///         <item><c>B3:0/0</c> — bit file 3, word 0, bit 0.</item>
///         <item><c>ST9:0</c> — string file 9, string 0 (82-byte fixed-length + length word).</item>
///         <item><c>T4:0.ACC</c> — timer file 4, timer 0, accumulator sub-element.</item>
///         <item><c>C5:0.PRE</c> — counter file 5, counter 0, preset sub-element.</item>
///         <item><c>I:0/0</c> — input file, slot 0, bit 0 (no file-number for I/O).</item>
///         <item><c>O:1/2</c> — output file, slot 1, bit 2.</item>
///         <item><c>S:1</c> — status file, word 1.</item>
///         <item><c>L9:0</c> — long-integer file (SLC 5/05+, 32-bit).</item>
///     </list>
///     <para>Pass the original string straight through to libplctag's <c>name=...</c> attribute —
///     the PLC-side decoder handles the format. This parser only validates the shape + surfaces
///     the structural pieces for driver-side routing (e.g. deciding whether a tag needs
///     bit-level read-modify-write).</para>
/// </remarks>
public sealed record AbLegacyAddress(
    string FileLetter,
    int? FileNumber,
    int WordNumber,
    int? BitIndex,
    string? SubElement)
{
    public string ToLibplctagName()
    {
        var file = FileNumber is null ? FileLetter : $"{FileLetter}{FileNumber}";
        var wordPart = $"{file}:{WordNumber}";
        if (SubElement is not null) wordPart += $".{SubElement}";
        if (BitIndex is not null) wordPart += $"/{BitIndex}";
        return wordPart;
    }

    public static AbLegacyAddress? TryParse(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return null;
        var src = value.Trim();

        // BitIndex: trailing /N
        int? bitIndex = null;
        var slashIdx = src.IndexOf('/');
        if (slashIdx >= 0)
        {
            if (!int.TryParse(src[(slashIdx + 1)..], out var bit) || bit < 0 || bit > 31) return null;
            bitIndex = bit;
            src = src[..slashIdx];
        }

        // SubElement: trailing .NAME (ACC / PRE / EN / DN / TT / CU / CD / FD / etc.)
        string? subElement = null;
        var dotIdx = src.LastIndexOf('.');
        if (dotIdx >= 0)
        {
            var candidate = src[(dotIdx + 1)..];
            if (candidate.Length > 0 && candidate.All(char.IsLetter))
            {
                subElement = candidate.ToUpperInvariant();
                src = src[..dotIdx];
            }
        }

        var colonIdx = src.IndexOf(':');
        if (colonIdx <= 0) return null;
        var filePart = src[..colonIdx];
        var wordPart = src[(colonIdx + 1)..];
        if (!int.TryParse(wordPart, out var word) || word < 0) return null;

        // File letter + optional file number (single letter for I/O/S, letter+number otherwise).
        if (filePart.Length == 0 || !char.IsLetter(filePart[0])) return null;
        var letterEnd = 1;
        while (letterEnd < filePart.Length && char.IsLetter(filePart[letterEnd])) letterEnd++;

        var letter = filePart[..letterEnd].ToUpperInvariant();
        int? fileNumber = null;
        if (letterEnd < filePart.Length)
        {
            if (!int.TryParse(filePart[letterEnd..], out var fn) || fn < 0) return null;
            fileNumber = fn;
        }

        // Reject unknown file letters — these cover SLC/ML/PLC-5 canonical families.
        if (!IsKnownFileLetter(letter)) return null;

        return new AbLegacyAddress(letter, fileNumber, word, bitIndex, subElement);
    }

    private static bool IsKnownFileLetter(string letter) => letter switch
    {
        "N" or "F" or "B" or "L" or "ST" or "T" or "C" or "R" or "I" or "O" or "S" or "A" => true,
        _ => false,
    };
}
