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

/// <summary>
///     Result of an Agent <c>/probe</c> request: the full device hierarchy the Agent exposes.
///     Produced by <see cref="IMTConnectAgentClient.ProbeAsync"/>. This is the neutral shape both
///     the TrakHound-backed implementation and a hand-rolled XML fallback must produce — nothing
///     downstream of <see cref="IMTConnectAgentClient"/> ever sees a TrakHound type or an
///     <c>XElement</c>.
/// </summary>
/// <param name="Devices">Every device the Agent's probe response declares.</param>
public sealed record MTConnectProbeModel(IReadOnlyList<MTConnectDevice> Devices);

/// <summary>
///     Common shape shared by <see cref="MTConnectDevice"/> and <see cref="MTConnectComponent"/>:
///     each may directly own data items and/or nest further components, to arbitrary depth. Backs
///     the <see cref="MTConnectComponentTreeExtensions.AllDataItems"/> recursive walk.
/// </summary>
public interface IMTConnectComponentContainer
{
    /// <summary>Components nested directly under this device/component (may be empty).</summary>
    IReadOnlyList<MTConnectComponent> Components { get; }

    /// <summary>Data items declared directly on this device/component (may be empty).</summary>
    IReadOnlyList<MTConnectDataItem> DataItems { get; }
}

/// <summary>
///     One MTConnect device from the Agent's probe response — the root of a nested
///     component/data-item tree.
/// </summary>
/// <param name="Id">The device's <c>id</c> attribute.</param>
/// <param name="Name">The device's <c>name</c> attribute, when the Agent supplies one.</param>
/// <param name="Components">Child components declared directly under this device.</param>
/// <param name="DataItems">Data items declared directly on this device (outside any component).</param>
public sealed record MTConnectDevice(
    string Id,
    string? Name,
    IReadOnlyList<MTConnectComponent> Components,
    IReadOnlyList<MTConnectDataItem> DataItems) : IMTConnectComponentContainer;

/// <summary>
///     One MTConnect component (e.g. <c>Axes</c>, <c>Controller</c>, <c>Path</c>) from the Agent's
///     probe response. Components nest to arbitrary depth — a component may itself contain further
///     child components as well as data items.
/// </summary>
/// <param name="Id">The component's <c>id</c> attribute.</param>
/// <param name="Name">The component's <c>name</c> attribute, when the Agent supplies one.</param>
/// <param name="Components">Child components nested directly under this component.</param>
/// <param name="DataItems">Data items declared directly on this component.</param>
public sealed record MTConnectComponent(
    string Id,
    string? Name,
    IReadOnlyList<MTConnectComponent> Components,
    IReadOnlyList<MTConnectDataItem> DataItems) : IMTConnectComponentContainer;

/// <summary>
///     One MTConnect DataItem declaration from the Agent's probe response. This is pure metadata —
///     it carries no observed value; values arrive separately via
///     <see cref="IMTConnectAgentClient.CurrentAsync"/> / <see cref="IMTConnectAgentClient.SampleAsync"/>
///     and are correlated back to a data item by <see cref="Id"/>.
/// </summary>
/// <param name="Id">
///     The DataItem's <c>id</c> attribute — globally unique within the Agent's probe response and
///     the correlation key used against <see cref="MTConnectObservation.DataItemId"/>.
/// </param>
/// <param name="Name">The DataItem's <c>name</c> attribute, when the Agent supplies one.</param>
/// <param name="Category">The DataItem's <c>category</c> attribute — <c>SAMPLE</c>, <c>EVENT</c>, or <c>CONDITION</c>.</param>
/// <param name="Type">The DataItem's <c>type</c> attribute (e.g. <c>POSITION</c>, <c>EXECUTION</c>).</param>
/// <param name="SubType">The DataItem's <c>subType</c> attribute (e.g. <c>ACTUAL</c>, <c>COMMANDED</c>), when present.</param>
/// <param name="Units">The DataItem's <c>units</c> attribute (e.g. <c>MILLIMETER</c>, <c>REVOLUTION/MINUTE</c>), when present.</param>
/// <param name="Representation">
///     The DataItem's <c>representation</c> attribute (e.g. <c>TIME_SERIES</c>, <c>DISCRETE</c>);
///     absent means the MTConnect default (<c>VALUE</c>).
/// </param>
/// <param name="SampleCount">
///     The DataItem's <c>sampleCount</c> attribute — element count for a <c>TIME_SERIES</c>
///     representation. Present only on data items that carry one.
/// </param>
public sealed record MTConnectDataItem(
    string Id,
    string? Name,
    string Category,
    string Type,
    string? SubType,
    string? Units,
    string? Representation,
    int? SampleCount);

/// <summary>
///     Result of an Agent <c>/current</c> request, or of a single multipart chunk from a
///     <c>/sample</c> stream. Produced by <see cref="IMTConnectAgentClient.CurrentAsync"/> and
///     <see cref="IMTConnectAgentClient.SampleAsync"/>.
/// </summary>
/// <param name="InstanceId">
///     The Agent's current instance id (its MTConnectStreams header <c>instanceId</c>). Changes
///     whenever the Agent restarts or its underlying device model changes; the driver watches this
///     for change to raise rediscovery rather than trusting a stale observation snapshot.
/// </param>
/// <param name="NextSequence">
///     The header <c>nextSequence</c> — the sequence number to resume a subsequent
///     <see cref="IMTConnectAgentClient.SampleAsync"/> call <c>from</c>.
/// </param>
/// <param name="FirstSequence">
///     The header <c>firstSequence</c> — the oldest sequence number still held in the Agent's
///     circular buffer. Load-bearing for sequence-gap detection: a caller that requested
///     <c>from</c> a sequence older than this chunk's <see cref="FirstSequence"/> has fallen out of
///     the Agent's buffer and must re-baseline via <see cref="IMTConnectAgentClient.CurrentAsync"/>.
/// </param>
/// <param name="Observations">
///     The observations carried in this snapshot/chunk, in Agent-supplied order. Never <c>null</c>;
///     empty when the chunk carries no new observations (e.g. a heartbeat).
/// </param>
public sealed record MTConnectStreamsResult(
    long InstanceId,
    long NextSequence,
    long FirstSequence,
    IReadOnlyList<MTConnectObservation> Observations);

/// <summary>
///     One observed value for a single DataItem, as reported in a <c>/current</c> snapshot or a
///     <c>/sample</c> stream chunk. Deliberately dumb: <see cref="Value"/> is carried as the raw
///     string the Agent reported (including the literal <c>UNAVAILABLE</c>) with no interpretation
///     applied here — coercing <c>UNAVAILABLE</c> to a bad-quality status and converting the string
///     to the tag's <c>DriverDataType</c> are the observation index's job, not this layer's.
/// </summary>
/// <param name="DataItemId">
///     The reporting DataItem's <c>id</c> — correlates back to <see cref="MTConnectDataItem.Id"/>
///     from the probe model.
/// </param>
/// <param name="Value">
///     The observed value exactly as the Agent reported it, or the literal string
///     <c>"UNAVAILABLE"</c> when the Agent has no current value for the data item. Never
///     pre-parsed into a nullable or an enum at this layer.
/// </param>
/// <param name="TimestampUtc">
///     The observation's Agent-reported timestamp, normalized to UTC (<see cref="DateTime.Kind"/>
///     is always <see cref="DateTimeKind.Utc"/>). Becomes the OPC UA variable's SourceTimestamp.
/// </param>
/// <param name="IsStructured">
///     <c>true</c> when the Agent carried this observation's real content in <b>child elements</b>
///     rather than as text — an MTConnect 2.0 <c>DATA_SET</c> / <c>TABLE</c> observation, whose
///     content is a list of <c>&lt;Entry key="…"&gt;</c> elements.
///     <para>
///         <b>Why the flag exists, and why only the parser can set it.</b> <see cref="Value"/> is
///         the element's concatenated descendant text, so a two-entry data set reads as the single
///         token <c>"12"</c> — keys discarded, values run together. The observation index cannot
///         detect that after the fact: neither this record nor the tag definition carries the
///         DataItem's <c>representation</c>, and no value-shape heuristic can work, because
///         concatenated entries are indistinguishable from a legitimate space-bearing EVENT such as
///         a <c>Message</c> reading <c>"Coolant level low"</c>. The one reliable discriminator —
///         that the observation element had element children — exists only while the XML is still
///         XML.
///     </para>
///     <para>
///         Deliberately a <b>neutral fact about the wire shape</b>, not a status: mapping it to a
///         quality code is the observation index's job (it codes these
///         <c>BadNotSupported</c> rather than publishing concatenated noise as Good). Defaults to
///         <c>false</c>, the shape of every ordinary scalar observation.
///     </para>
/// </param>
public sealed record MTConnectObservation(
    string DataItemId,
    string Value,
    DateTime TimestampUtc,
    bool IsStructured = false);

/// <summary>
///     Recursive helpers over the <see cref="IMTConnectComponentContainer"/> device/component tree.
/// </summary>
public static class MTConnectComponentTreeExtensions
{
    /// <summary>
    ///     Enumerates every data item owned by this device or component, plus every data item owned
    ///     by any component nested beneath it, to arbitrary depth. Order is depth-first: a node's
    ///     own data items first, then each child component's data items in turn.
    /// </summary>
    /// <param name="container">The device or component to walk.</param>
    public static IEnumerable<MTConnectDataItem> AllDataItems(this IMTConnectComponentContainer container)
    {
        foreach (var dataItem in container.DataItems)
        {
            yield return dataItem;
        }

        foreach (var child in container.Components)
        {
            foreach (var dataItem in child.AllDataItems())
            {
                yield return dataItem;
            }
        }
    }
}
