using libplctag;

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

/// <summary>
///     libplctag-backed <see cref="IAbCipTemplateReader"/>. Opens the <c>@udt/{templateId}</c>
///     pseudo-tag libplctag exposes for Template Object reads, issues a <c>Read Template</c>
///     internally via a normal read call, + returns the raw byte buffer so
///     <see cref="CipTemplateObjectDecoder"/> can decode it.
/// </summary>
/// <remarks>
///     Driver.AbCip-012 — by design each <c>FetchUdtShapeAsync</c> call creates one fresh
///     <see cref="Tag"/>, pays one CIP connection handshake, reads, and disposes. Per-type
///     connect cost is accepted because (a) template reads are a low-frequency discovery path
///     (one-shot per UDT type, then the decoded shape is cached in
///     <see cref="AbCipTemplateCache"/>), (b) libplctag pools its underlying CIP connections per
///     gateway+path so the underlying TCP/EIP session is reused even when individual
///     <see cref="Tag"/> instances are torn down, and (c) pooling at the wrapper layer here would
///     buy a single Forward Open per device per discovery run — small relative to the rest of a
///     bulk-tag-walk discovery. If telemetry ever shows discovery latency dominated by
///     template-read connects, revisit by holding one <c>@udt</c>-capable <see cref="Tag"/> per
///     device for the duration of a discovery run.
/// </remarks>
internal sealed class LibplctagTemplateReader : IAbCipTemplateReader
{
    private Tag? _tag;

    /// <summary>Reads a template object from the PLC asynchronously.</summary>
    /// <param name="deviceParams">The device connection parameters.</param>
    /// <param name="templateInstanceId">The template instance ID to read.</param>
    /// <param name="cancellationToken">Cancellation token for the operation.</param>
    /// <returns>A task representing the asynchronous read operation.</returns>
    public async Task<byte[]> ReadAsync(
        AbCipTagCreateParams deviceParams,
        uint templateInstanceId,
        CancellationToken cancellationToken)
    {
        _tag?.Dispose();
        _tag = new Tag
        {
            Gateway = deviceParams.Gateway,
            Path = deviceParams.CipPath,
            PlcType = MapPlcType(deviceParams.LibplctagPlcAttribute),
            Protocol = Protocol.ab_eip,
            Name = $"@udt/{templateInstanceId}",
            Timeout = deviceParams.Timeout,
        };
        await _tag.InitializeAsync(cancellationToken).ConfigureAwait(false);
        await _tag.ReadAsync(cancellationToken).ConfigureAwait(false);
        return _tag.GetBuffer();
    }

    /// <inheritdoc />
    public void Dispose() => _tag?.Dispose();

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

internal sealed class LibplctagTemplateReaderFactory : IAbCipTemplateReaderFactory
{
    /// <summary>Creates a new instance of the libplctag template reader.</summary>
    /// <returns>A new instance of <see cref="LibplctagTemplateReader"/>.</returns>
    public IAbCipTemplateReader Create() => new LibplctagTemplateReader();
}
