using System.ComponentModel.DataAnnotations;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

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

/// <summary>
///     TwinCAT ADS driver configuration. One instance supports N targets (each identified by
///     an AMS Net ID + port). Compiles + runs without a local AMS router but every wire call
///     fails with <c>BadCommunicationError</c> until a router is reachable. The tags the driver
///     serves are delivered as authored raw tags in <see cref="RawTags"/>; the driver maps each
///     through <see cref="TwinCATTagDefinitionFactory.FromTagConfig"/> to build its
///     RawPath → definition table, routing each to its device by the entry's
///     <see cref="RawTagEntry.DeviceName"/> (multi-device).
/// </summary>
public sealed class TwinCATDriverOptions
{
    /// <summary>Gets the list of TwinCAT devices to connect to.</summary>
    public IReadOnlyList<TwinCATDeviceOptions> Devices { get; init; } = [];

    /// <summary>
    ///     Authored raw tags this driver serves. The deploy artifact hands each authored raw
    ///     <c>Tag</c> as a <see cref="RawTagEntry"/> (RawPath identity + driver <c>TagConfig</c> blob +
    ///     WriteIdempotent flag + owning <see cref="RawTagEntry.DeviceName"/>); the driver maps each
    ///     through <see cref="TwinCATTagDefinitionFactory.FromTagConfig"/> into its RawPath → definition
    ///     table and routes it to the matching <see cref="Devices"/> entry by DeviceName.
    /// </summary>
    public IReadOnlyList<RawTagEntry> RawTags { get; init; } = [];
    /// <summary>Gets the probe options for TwinCAT connection probing.</summary>
    public TwinCATProbeOptions Probe { get; init; } = new();
    /// <summary>Gets the default communication timeout.</summary>
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);

    /// <summary>
    ///     When <c>true</c> (default), <c>SubscribeAsync</c> registers native ADS notifications
    ///     via <c>AddDeviceNotificationExAsync</c> — the PLC pushes changes on its own cycle
    ///     rather than the driver polling. Strictly better for latency + CPU when the target
    ///     supports it (TC2 + TC3 PLC runtimes always do; some soft-PLC / third-party ADS
    ///     implementations may not). When <c>false</c>, the driver falls through to the shared
    ///     <c>PollGroupEngine</c> — same semantics as the other
    ///     libplctag-backed drivers. Set <c>false</c> for deployments where the AMS router has
    ///     notification limits you can't raise.
    /// </summary>
    public bool UseNativeNotifications { get; init; } = true;

    /// <summary>
    ///     When <c>true</c>, <c>DiscoverAsync</c> walks each device's symbol table via the
    ///     TwinCAT <c>SymbolLoaderFactory</c> (flat mode) + surfaces controller-resident
    ///     globals / program locals under a <c>Discovered/</c> sub-folder. Authored raw tags
    ///     from <see cref="RawTags"/> always emit regardless. Default <c>false</c> to preserve
    ///     the strict-config path for deployments where only declared tags should appear.
    /// </summary>
    public bool EnableControllerBrowse { get; init; }

    /// <summary>
    ///     Maximum batching delay in milliseconds for ADS device notifications. Passed to
    ///     <c>NotificationSettings</c> as the max-delay value: TwinCAT may coalesce notifications
    ///     up to this delay before pushing them. <c>0</c> (default) = no batching, push
    ///     immediately. Useful for high-churn signals where the OPC UA subscriber tolerates a
    ///     small delay in exchange for fewer wire round-trips. Listed in <c>docs/v2/driver-specs.md</c>
    ///     section 6 — was previously hard-coded to 0.
    /// </summary>
    public int NotificationMaxDelayMs { get; init; }

    /// <summary>
    ///     Timeout for the AdminUI Test Connect probe, in seconds. The AdminUI clamps to a
    ///     60s server-side maximum; this default is what the form pre-fills for new instances.
    /// </summary>
    [Display(Name = "Probe timeout (seconds)", Description = "Connection test timeout. Default 10s.", GroupName = "Diagnostics")]
    [Range(1, 60)]
    public int ProbeTimeoutSeconds { get; init; } = 10;
}

/// <summary>
///     One TwinCAT target. <paramref name="HostAddress"/> must parse via
///     <c>TwinCATAmsAddress.TryParse</c>; misconfigured devices fail driver initialisation.
/// </summary>
public sealed record TwinCATDeviceOptions(
    string HostAddress,
    string? DeviceName = null);

/// <summary>
///     One TwinCAT-backed OPC UA variable. <paramref name="SymbolPath"/> is the full TwinCAT
///     symbolic name (e.g. <c>MAIN.bStart</c>, <c>GVL.Counter</c>, <c>Motor1.Status.Running</c>).
/// </summary>
/// <param name="ArrayLength">
///     When non-null, this tag is a 1-D array of <paramref name="ArrayLength"/> elements of
///     <paramref name="DataType"/>. Drives <c>IsArray</c>/<c>ArrayDim</c> at discovery and a
///     native ADS array read at runtime (Phase 4c). <c>null</c> = scalar (the default).
/// </param>
public sealed record TwinCATTagDefinition(
    string Name,
    string DeviceHostAddress,
    string SymbolPath,
    TwinCATDataType DataType,
    bool Writable = true,
    bool WriteIdempotent = false,
    int? ArrayLength = null);

/// <summary>Probe options for TwinCAT connection monitoring.</summary>
public sealed class TwinCATProbeOptions
{
    /// <summary>Gets a value indicating whether probing is enabled.</summary>
    public bool Enabled { get; init; } = true;
    /// <summary>Gets the probe interval.</summary>
    public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5);
    /// <summary>Gets the probe timeout.</summary>
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
}
