using System.Runtime.CompilerServices;
using libplctag;

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

/// <summary>
///     Real <see cref="IAbCipTagEnumerator"/> that walks a Logix controller's symbol table by
///     reading the <c>@tags</c> pseudo-tag via libplctag + decoding the CIP Symbol Object
///     response with <see cref="CipSymbolObjectDecoder"/>.
/// </summary>
/// <remarks>
///     <para>libplctag's <c>Tag.GetBuffer()</c> returns the raw Symbol Object bytes when the
///     tag name is <c>@tags</c>. The decoder walks the concatenated entries + emits
///     <see cref="AbCipDiscoveredTag"/> records matching our driver surface.</para>
///
///     <para><see cref="EmptyAbCipTagEnumerator"/> remains available for tests that don't want
///     to touch the native library; the production factory default
///     (<see cref="LibplctagTagEnumeratorFactory"/>) wires this implementation in.</para>
/// </remarks>
internal sealed class LibplctagTagEnumerator : IAbCipTagEnumerator
{
    private Tag? _tag;

    /// <summary>Enumerates all tags in the controller symbol table.</summary>
    /// <param name="deviceParams">Device connection parameters including gateway and path.</param>
    /// <param name="cancellationToken">Cancellation token for the enumeration.</param>
    /// <returns>An async enumerable of discovered tags.</returns>
    public async IAsyncEnumerable<AbCipDiscoveredTag> EnumerateAsync(
        AbCipTagCreateParams deviceParams,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        // Build a tag specifically for the @tags pseudo — same gateway + path as the device,
        // distinguished by the name alone.
        _tag = new Tag
        {
            Gateway = deviceParams.Gateway,
            Path = deviceParams.CipPath,
            PlcType = MapPlcType(deviceParams.LibplctagPlcAttribute),
            Protocol = Protocol.ab_eip,
            Name = "@tags",
            Timeout = deviceParams.Timeout,
        };

        await _tag.InitializeAsync(cancellationToken).ConfigureAwait(false);
        await _tag.ReadAsync(cancellationToken).ConfigureAwait(false);

        var buffer = _tag.GetBuffer();
        foreach (var tag in CipSymbolObjectDecoder.Decode(buffer))
            yield return tag;
    }

    /// <summary>Disposes the enumerator and releases the underlying libplctag tag.</summary>
    public void Dispose() => _tag?.Dispose();

    private static PlcType MapPlcType(string attribute) => attribute switch
    {
        "controllogix" => PlcType.ControlLogix,
        "compactlogix" => PlcType.ControlLogix,
        "micro800" => PlcType.Micro800,
        _ => PlcType.ControlLogix,
    };
}

/// <summary>Factory for <see cref="LibplctagTagEnumerator"/>.</summary>
internal sealed class LibplctagTagEnumeratorFactory : IAbCipTagEnumeratorFactory
{
    /// <summary>Creates a new libplctag-based tag enumerator.</summary>
    /// <returns>A new tag enumerator instance.</returns>
    public IAbCipTagEnumerator Create() => new LibplctagTagEnumerator();
}
