using System.Runtime.InteropServices;

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

/// <summary>
///     <see cref="SafeHandle"/> wrapper around a libplctag native tag handle (an <c>int32</c>
///     returned from <c>plc_tag_create_ex</c>). Owns lifetime of the native allocation so a
///     leaked / GC-collected <see cref="PlcTagHandle"/> still calls <c>plc_tag_destroy</c>
///     during finalization — necessary because native libplctag allocations are opaque to
///     the driver's <see cref="Core.Abstractions.IDriver.GetMemoryFootprint"/>.
/// </summary>
/// <remarks>
///     <para>Risk documented in driver-specs.md §3 ("Operational Stability Notes"): the CLR
///     allocation tracker doesn't see libplctag's native heap, only whole-process RSS can.
///     Every handle leaked past its useful life is a direct contributor to the Tier-B recycle
///     trigger, so owning lifetime via SafeHandle is non-negotiable.</para>
///
///     <para><see cref="IsInvalid"/> is <c>true</c> when the native ID is &lt;= 0 — libplctag
///     returns negative <c>PLCTAG_ERR_*</c> codes on <c>plc_tag_create_ex</c> failure, which
///     we surface as an invalid handle rather than a disposable one (destroying a negative
///     handle would be undefined behavior in the native library).</para>
///
///     <para>The actual <c>DllImport</c> for <c>plc_tag_destroy</c> is deferred to PR 3 when
///     the driver first makes wire calls — PR 2 ships the lifetime scaffold + tests only.
///     Until the P/Invoke lands, <see cref="ReleaseHandle"/> is a no-op; the finalizer still
///     runs so the integration is correct as soon as the import is added.</para>
/// </remarks>
public sealed class PlcTagHandle : SafeHandle
{
    /// <summary>Construct an invalid handle placeholder (use <see cref="FromNative"/> once created).</summary>
    public PlcTagHandle() : base(invalidHandleValue: IntPtr.Zero, ownsHandle: true) { }

    private PlcTagHandle(int nativeId) : base(invalidHandleValue: IntPtr.Zero, ownsHandle: true)
    {
        SetHandle(new IntPtr(nativeId));
    }

    /// <summary>Handle is invalid when the native ID is zero or negative (libplctag error).</summary>
    public override bool IsInvalid => handle.ToInt32() <= 0;

    /// <summary>Integer ID libplctag issued on <c>plc_tag_create_ex</c>.</summary>
    public int NativeId => handle.ToInt32();

    /// <summary>Wrap a native tag ID returned from libplctag.</summary>
    public static PlcTagHandle FromNative(int nativeId) => new(nativeId);

    /// <summary>
    ///     Destroy the native tag. No-op for PR 2 (the wire P/Invoke lands in PR 3). The base
    ///     <see cref="SafeHandle"/> machinery still guarantees this runs exactly once per
    ///     handle — either during <see cref="SafeHandle.Dispose()"/> or during finalization
    ///     if the owner was GC'd without explicit Dispose.
    /// </summary>
    protected override bool ReleaseHandle()
    {
        if (IsInvalid) return true;
        // PR 3: wire up plc_tag_destroy(handle.ToInt32()) once the DllImport lands.
        return true;
    }
}
