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;

    /// <inheritdoc />
    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();
        // The decoder already surfaces each struct tag's CIP template instance id on
        // AbCipDiscoveredTag.TemplateInstanceId (from the lower 12 bits of the symbol type), so
        // re-yielding the decoded records as-is carries it through to DiscoverAsync's UDT fan-out.
        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
{
    /// <inheritdoc />
    public IAbCipTagEnumerator Create() => new LibplctagTagEnumerator();
}
