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

/// <summary>
///     Wire-layer abstraction over one connection to a TwinCAT AMS target. One instance per
///     <see cref="TwinCATAmsAddress"/>; reused across reads / writes / probes for the device.
///     Tests swap in a fake via <see cref="ITwinCATClientFactory"/>.
/// </summary>
/// <remarks>
///     Unlike libplctag-backed drivers where one native handle exists per tag, TwinCAT's
///     AdsClient is one connection per target with symbolic reads / writes issued against it.
///     The abstraction reflects that — single <see cref="ConnectAsync"/>, many
///     <see cref="ReadValueAsync"/> / <see cref="WriteValueAsync"/> calls.
/// </remarks>
public interface ITwinCATClient : IDisposable
{
    /// <summary>Establish the AMS connection. Idempotent — subsequent calls are no-ops when already connected.</summary>
    /// <param name="address">The target AMS address.</param>
    /// <param name="timeout">The connection timeout.</param>
    /// <param name="cancellationToken">Cancellation token for the connection attempt.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    Task ConnectAsync(TwinCATAmsAddress address, TimeSpan timeout, CancellationToken cancellationToken);

    /// <summary>True when the AMS router + target both accept commands.</summary>
    bool IsConnected { get; }

    /// <summary>
    ///     Raised when the client observes the ADS symbol-version-changed code
    ///     (<see cref="TwinCATStatusMapper.AdsSymbolVersionChanged"/> — <c>DeviceSymbolVersionInvalid</c>
    ///     1809 / 0x0711) on any read / write / notification — the signal that a PLC program
    ///     re-download has invalidated every symbol + notification handle. The driver forwards
    ///     this to <see cref="Core.Abstractions.IRediscoverable.OnRediscoveryNeeded"/> so Core
    ///     rebuilds the address space subtree (docs/v2/driver-specs.md §6).
    /// </summary>
    event EventHandler? OnSymbolVersionChanged;

    /// <summary>
    ///     Read a symbolic value. Returns a boxed .NET value matching the requested
    ///     <paramref name="type"/>, or <c>null</c> when the read produced no data; the
    ///     <c>status</c> tuple member carries the mapped OPC UA status (0 = Good).
    /// </summary>
    /// <param name="symbolPath">The ADS symbol path.</param>
    /// <param name="type">The target data type.</param>
    /// <param name="bitIndex">Optional bit index for bit extraction within a word.</param>
    /// <param name="arrayCount">When non-null, read a 1-D array of this many <paramref name="type"/>
    ///     elements; the boxed value is the element-typed CLR array (<c>int[]</c> / <c>float[]</c> /
    ///     <c>bool[]</c> / <c>string[]</c> / …). When null, read a scalar (Phase 4c).</param>
    /// <param name="cancellationToken">Cancellation token for the read operation.</param>
    /// <returns>The boxed value read (or <c>null</c>) together with the mapped OPC UA status.</returns>
    Task<(object? value, uint status)> ReadValueAsync(
        string symbolPath,
        TwinCATDataType type,
        int? bitIndex,
        int? arrayCount,
        CancellationToken cancellationToken);

    /// <summary>
    ///     Write a symbolic value. Returns the mapped OPC UA status for the operation
    ///     (0 = Good, non-zero = error mapped via <see cref="TwinCATStatusMapper"/>).
    /// </summary>
    /// <param name="symbolPath">The ADS symbol path.</param>
    /// <param name="type">The data type.</param>
    /// <param name="bitIndex">Optional bit index for bit manipulation within a word.</param>
    /// <param name="value">The value to write.</param>
    /// <param name="cancellationToken">Cancellation token for the write operation.</param>
    /// <returns>The mapped OPC UA status for the write (0 = Good).</returns>
    Task<uint> WriteValueAsync(
        string symbolPath,
        TwinCATDataType type,
        int? bitIndex,
        object? value,
        CancellationToken cancellationToken);

    /// <summary>
    ///     Cheap health probe — returns <c>true</c> when the target's AMS state is reachable.
    ///     Used by <see cref="Core.Abstractions.IHostConnectivityProbe"/>'s probe loop.
    /// </summary>
    /// <param name="cancellationToken">Cancellation token for the probe operation.</param>
    /// <returns><c>true</c> when the target is reachable; otherwise <c>false</c>.</returns>
    Task<bool> ProbeAsync(CancellationToken cancellationToken);

    /// <summary>
    ///     Register a cyclic / on-change ADS notification for a symbol. Returns a handle whose
    ///     <see cref="IDisposable.Dispose"/> tears the notification down. Callback fires on the
    ///     thread libplctag / AdsClient uses for notifications — consumers should marshal to
    ///     their own scheduler before doing work of any size.
    /// </summary>
    /// <param name="symbolPath">ADS symbol path (e.g. <c>MAIN.bStart</c>).</param>
    /// <param name="type">Declared type; drives the native layout + callback value boxing.</param>
    /// <param name="bitIndex">For BOOL-within-word tags — the bit to extract from the parent word.</param>
    /// <param name="cycleTime">Minimum interval between change notifications (native-floor depends on target).</param>
    /// <param name="maxDelayMs">Maximum batching delay in milliseconds — TwinCAT may coalesce
    ///     notifications up to this delay before pushing them. <c>0</c> = no batching, push
    ///     immediately.</param>
    /// <param name="onChange">Invoked with <c>(symbolPath, boxedValue)</c> per notification.</param>
    /// <param name="cancellationToken">Cancels the initial registration; does not tear down an established notification.</param>
    /// <returns>A handle whose disposal tears the registered notification down.</returns>
    Task<ITwinCATNotificationHandle> AddNotificationAsync(
        string symbolPath,
        TwinCATDataType type,
        int? bitIndex,
        TimeSpan cycleTime,
        int maxDelayMs,
        Action<string, object?> onChange,
        CancellationToken cancellationToken);

    /// <summary>
    ///     Walk the target's symbol table via the TwinCAT <c>SymbolLoaderFactory</c> (flat mode)
    ///     and expand all struct / UDT / function-block symbols to their atomic member leaves.
    ///     The implementation uses <see cref="TwinCATSymbolExpander.ExpandLeaves"/> to recursively
    ///     descend each struct/UDT/FB-instance symbol into its addressable atomic members,
    ///     yielding only atomic leaves (scalars or 1-D arrays with a non-null
    ///     <see cref="TwinCATDiscoveredSymbol.DataType"/>) and dropping struct/UDT/FB containers.
    ///     Callers receive fully-qualified <c>InstancePath</c>s (e.g. <c>MAIN.Motor1.Speed</c>)
    ///     and never see struct-typed entries — expansion is performed here, not in the caller.
    /// </summary>
    /// <param name="cancellationToken">Cancellation token for the enumeration operation.</param>
    /// <returns>An asynchronous stream of the target's atomic-leaf symbols.</returns>
    IAsyncEnumerable<TwinCATDiscoveredSymbol> BrowseSymbolsAsync(CancellationToken cancellationToken);
}

/// <summary>Opaque handle for a registered ADS notification. <see cref="IDisposable.Dispose"/> tears it down.</summary>
public interface ITwinCATNotificationHandle : IDisposable { }

/// <summary>
///     One symbol yielded by <see cref="ITwinCATClient.BrowseSymbolsAsync"/> — full instance
///     path + detected <see cref="TwinCATDataType"/> + read-only flag.
/// </summary>
/// <param name="InstancePath">Full dotted symbol path (e.g. <c>MAIN.bStart</c>, <c>GVL.Counter</c>).</param>
/// <param name="DataType">Mapped <see cref="TwinCATDataType"/> of the (element) type; <c>null</c>
///     when the symbol's type doesn't map onto our supported atomic surface (UDTs, pointers,
///     function blocks). For an array symbol this is the ELEMENT type, with <paramref name="ArrayLength"/>
///     carrying the dimension.</param>
/// <param name="ReadOnly"><c>true</c> when the symbol's AccessRights flag forbids writes.</param>
/// <param name="ArrayLength">When non-null, the symbol is a 1-D array of this many
///     <paramref name="DataType"/> elements. <c>null</c> = scalar. Multi-dimensional arrays are
///     reported as <c>null</c> (treated as scalar/unsupported) — only 1-D is in scope for Phase 4c.</param>
public sealed record TwinCATDiscoveredSymbol(
    string InstancePath,
    TwinCATDataType? DataType,
    bool ReadOnly,
    int? ArrayLength = null);

/// <summary>Factory for <see cref="ITwinCATClient"/>s. One client per device.</summary>
public interface ITwinCATClientFactory
{
    /// <summary>Creates a new TwinCAT client instance.</summary>
    /// <returns>A new, not-yet-connected <see cref="ITwinCATClient"/>.</returns>
    ITwinCATClient Create();
}
