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

/// <summary>
///     Swappable scanner that walks a controller's symbol table (via libplctag's
///     <c>@tags</c> pseudo-tag or the CIP Symbol Object class 0x6B) and yields the tags it
///     finds. Production default is <see cref="LibplctagTagEnumeratorFactory"/> which speaks
///     to the live controller; <see cref="EmptyAbCipTagEnumeratorFactory"/> is available for
///     tests / strict-config deployments where only pre-declared tags should appear.
/// </summary>
public interface IAbCipTagEnumerator : IDisposable
{
    /// <summary>
    ///     Enumerate the controller's tags for one device. Callers iterate asynchronously so
    ///     large symbol tables don't require buffering the entire list.
    /// </summary>
    /// <param name="deviceParams">Parameters for creating device tags.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <returns>An asynchronous sequence of the tags discovered for the device.</returns>
    IAsyncEnumerable<AbCipDiscoveredTag> EnumerateAsync(
        AbCipTagCreateParams deviceParams,
        CancellationToken cancellationToken);
}

/// <summary>Factory for per-driver enumerators.</summary>
public interface IAbCipTagEnumeratorFactory
{
    /// <summary>
    /// Creates a new tag enumerator instance.
    /// </summary>
    /// <returns>A new <see cref="IAbCipTagEnumerator"/> instance.</returns>
    IAbCipTagEnumerator Create();
}

/// <summary>One tag yielded by <see cref="IAbCipTagEnumerator.EnumerateAsync"/>.</summary>
/// <param name="Name">Logix symbolic name as returned by the Symbol Object.</param>
/// <param name="ProgramScope">Program name if the tag is program-scoped; <c>null</c> for controller scope.</param>
/// <param name="DataType">Detected data type; <see cref="AbCipDataType.Structure"/> when the tag
///     is UDT-typed — per-member expansion runs against the cached
///     <see cref="AbCipUdtShape"/> via <see cref="AbCipTemplateCache"/>.</param>
/// <param name="ReadOnly"><c>true</c> when the Symbol Object's External Access attribute forbids writes.</param>
/// <param name="IsSystemTag">Hint from the enumerator that this is a system / infrastructure tag;
///     the driver applies <see cref="AbCipSystemTagFilter"/> on top so the enumerator is not the
///     single source of truth.</param>
/// <param name="ElementCount">Phase 4c — libplctag <c>elem_count</c> reported by the Symbol
///     Object's array-dimension fields. Defaults to 1 (scalar).</param>
/// <param name="IsArray">Review I-1 — the EXPLICIT array signal: <c>true</c> ⟺ the Symbol Object
///     reported a 1-D array (even of length 1). Surfaces the tag as an OPC UA array node at
///     discovery; <see cref="ElementCount"/> alone can't distinguish a scalar from a 1-element
///     array.</param>
/// <param name="TemplateInstanceId">The CIP template instance id for a discovered Structure tag
///     (<c>null</c> for non-structs / when unknown). Used to fetch the UDT's Template Object shape
///     during discovery so the struct can be fanned out into addressable member variables.</param>
public sealed record AbCipDiscoveredTag(
    string Name,
    string? ProgramScope,
    AbCipDataType DataType,
    bool ReadOnly,
    bool IsSystemTag = false,
    int ElementCount = 1,
    bool IsArray = false,
    uint? TemplateInstanceId = null);

/// <summary>
///     No-op enumerator returning an empty sequence. Useful for tests + strict-config
///     deployments where <see cref="AbCipDriverOptions.EnableControllerBrowse"/> is set but the
///     operator wants only pre-declared tags to surface. Production drivers use
///     <see cref="LibplctagTagEnumeratorFactory"/> instead.
/// </summary>
internal sealed class EmptyAbCipTagEnumerator : IAbCipTagEnumerator
{
    /// <inheritdoc />
    public async IAsyncEnumerable<AbCipDiscoveredTag> EnumerateAsync(
        AbCipTagCreateParams deviceParams,
        [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
    {
        await Task.CompletedTask;
        yield break;
    }

    /// <summary>
    /// Releases resources (no-op for this implementation).
    /// </summary>
    public void Dispose() { }
}

/// <summary>Factory for <see cref="EmptyAbCipTagEnumerator"/>.</summary>
internal sealed class EmptyAbCipTagEnumeratorFactory : IAbCipTagEnumeratorFactory
{
    /// <inheritdoc />
    public IAbCipTagEnumerator Create() => new EmptyAbCipTagEnumerator();
}
