using ZB.MOM.WW.OtOpcUa.Core.Abstractions;

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

/// <summary>
///     MTConnect Agent driver configuration. Bound from the driver's <c>DriverConfig</c> JSON at
///     <c>DriverHost.RegisterAsync</c>. The driver polls a remote MTConnect Agent's REST endpoints
///     (<c>/probe</c>, <c>/current</c>, <c>/sample</c>) rooted at <see cref="AgentUri"/> and maps
///     each returned DataItem into an OPC UA variable per <see cref="MTConnectTagDefinition"/>.
/// </summary>
public sealed class MTConnectDriverOptions
{
    /// <summary>
    ///     Gets the MTConnect Agent's base URI (e.g. <c>http://agent:5000</c>). Required — the
    ///     driver appends the standard Agent request paths (<c>/probe</c>, <c>/current</c>,
    ///     <c>/sample</c>) to this base.
    /// </summary>
    public required string AgentUri { get; init; }

    /// <summary>
    ///     Optional device-name scope. When set, requests are narrowed to
    ///     <c>{AgentUri}/{DeviceName}/...</c> so a multi-device Agent only serves one device's
    ///     DataItems through this driver instance. Default <c>null</c> = agent-wide (all devices).
    /// </summary>
    public string? DeviceName { get; init; }

    /// <summary>
    ///     Per-call HTTP deadline, in milliseconds, applied to every Agent request
    ///     (<c>/probe</c>, <c>/current</c>, <c>/sample</c>). Default <c>5000</c> — long enough for
    ///     a large device probe response over a LAN, short enough that a hung Agent surfaces as a
    ///     failed poll rather than wedging the driver.
    /// </summary>
    public int RequestTimeoutMs { get; init; } = 5000;

    /// <summary>
    ///     The <c>interval</c> query parameter (milliseconds) passed to the Agent's <c>/sample</c>
    ///     streaming request — the minimum time the Agent waits between successive chunks of the
    ///     response. Default <c>1000</c>; must be non-zero so the sample pump cannot spin the
    ///     connection into a busy-loop.
    /// </summary>
    public int SampleIntervalMs { get; init; } = 1000;

    /// <summary>
    ///     The <c>count</c> query parameter passed to the Agent's <c>/sample</c> request — the
    ///     maximum number of DataItem observations returned per chunk. Default <c>1000</c>, the
    ///     MTConnect Agent's own conventional default.
    /// </summary>
    public int SampleCount { get; init; } = 1000;

    /// <summary>
    ///     The <c>heartbeat</c> query parameter (milliseconds) passed to the Agent's <c>/sample</c>
    ///     streaming request — the Agent sends an empty chunk after this much idle time so the
    ///     client can detect a silently-stalled connection. Default <c>10000</c>, matching the
    ///     MTConnect Agent's own conventional default; must be non-zero or a quiet connection can
    ///     never be distinguished from a dead one.
    /// </summary>
    public int HeartbeatMs { get; init; } = 10000;

    /// <summary>
    ///     The authored tags this driver instance serves — one <see cref="MTConnectTagDefinition"/>
    ///     per tag, keyed by the DataItem <c>id</c> it binds
    ///     (<see cref="MTConnectTagDefinition.FullName"/>). The factory config DTO carries these and
    ///     builds them into the options; the driver indexes them to know each tag's target
    ///     <see cref="MTConnectTagDefinition.DriverDataType"/> when coercing an Agent observation
    ///     (whose wire form is always text) into a published value.
    ///     <para>
    ///         Defaults to <b>empty, never <c>null</c></b>: a driver instance may legitimately be
    ///         authored with no tags yet, and a null collection here would surface as a
    ///         NullReferenceException at deploy time from operator-authored config.
    ///     </para>
    /// </summary>
    public IReadOnlyList<MTConnectTagDefinition> Tags { get; init; } = [];

    /// <summary>
    ///     <b>The v3 data-plane binding: the authored raw tags the deploy artifact delivers.</b> Each
    ///     <see cref="RawTagEntry"/> pairs the tag's <b>RawPath</b> — the driver's wire reference for
    ///     read / subscribe / publish, and the key <c>DriverHostActor</c> routes published values by —
    ///     with the driver-specific <c>TagConfig</c> blob naming the Agent DataItem <c>id</c> it binds.
    ///     Injected into every driver's merged config by <c>DriverDeviceConfigMerger</c>, so this is the
    ///     collection a deployed instance actually serves from.
    ///     <para>
    ///         <b>Relationship to <see cref="Tags"/>.</b> The driver builds ONE observation index and
    ///         ONE <c>RawPath → dataItemId</c> table from both collections:
    ///         <list type="bullet">
    ///             <item>A tag in <c>RawTags</c> is reachable by its RawPath (production). Its coercion
    ///             type comes from the blob's <c>driverDataType</c>; failing that, from a
    ///             <see cref="Tags"/> entry naming the same DataItem id; failing that, from the Agent's
    ///             own <c>/probe</c> declaration (<see cref="MTConnectDataTypeInference"/>); failing
    ///             that, <see cref="DriverDataType.String"/> — the coercion that cannot fail.</item>
    ///             <item>A tag in <see cref="Tags"/> only is reachable by its DataItem <c>id</c>
    ///             (the driver CLI's authoring surface, and every pre-v3 test) and still contributes
    ///             its coercion type to the index.</item>
    ///         </list>
    ///         Both default to empty, so neither collection is required and a driver instance
    ///         authored with no tags yet starts cleanly.
    ///     </para>
    /// </summary>
    public IReadOnlyList<RawTagEntry> RawTags { get; init; } = [];

    /// <summary>
    ///     Background connectivity-probe settings. When <see cref="MTConnectProbeOptions.Enabled"/>
    ///     is true the driver periodically issues a cheap <c>/probe</c> request and raises
    ///     <c>OnHostStatusChanged</c> on Running ↔ Stopped transitions.
    /// </summary>
    public MTConnectProbeOptions Probe { get; init; } = new();

    /// <summary>
    ///     Reconnect backoff settings used after a failed Agent request or a dropped
    ///     <c>/sample</c> streaming connection.
    /// </summary>
    public MTConnectReconnectOptions Reconnect { get; init; } = new();
}

/// <summary>Background connectivity-probe knobs, mirroring <c>ModbusProbeOptions</c>.</summary>
public sealed class MTConnectProbeOptions
{
    /// <summary>Gets a value indicating whether probing is enabled.</summary>
    public bool Enabled { get; init; } = true;
    /// <summary>Gets the interval between probe requests.</summary>
    public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5);
    /// <summary>Gets the probe request timeout.</summary>
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2);
}

/// <summary>Geometric-backoff settings for the post-failure reconnect loop, mirroring <c>ModbusReconnectOptions</c>.</summary>
public sealed class MTConnectReconnectOptions
{
    /// <summary>Delay before the first reconnect attempt, in milliseconds. Default <c>0</c> = immediate.</summary>
    public int MinBackoffMs { get; init; } = 0;
    /// <summary>Upper bound on the geometric backoff sequence, in milliseconds.</summary>
    public int MaxBackoffMs { get; init; } = 30000;
    /// <summary>Multiplier applied each retry. Default <c>2.0</c> doubles each step.</summary>
    public double BackoffMultiplier { get; init; } = 2.0;
}
