using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol;

namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;

/// <summary>
/// Sent from CentralUI to a specific site to enumerate the immediate children
/// of an OPC UA node on the live server backing the given data connection.
/// </summary>
/// <remarks>
/// Keyed by <see cref="ConnectionName"/> (not id) because the site-side
/// <c>DataConnectionManagerActor</c> indexes its children by connection name —
/// the central UI already has the connection name in scope (dropdown), so a
/// string carries no extra plumbing across the trust boundary. The central
/// <c>DataConnections</c> table's id is intentionally not exposed at the site.
/// </remarks>
/// <param name="ConnectionName">Name of the site-local data connection to browse against.</param>
/// <param name="ParentNodeId">Node to browse, or null to browse from the server root (ObjectsFolder).</param>
/// <param name="ContinuationToken">
/// Opaque adapter cursor for fetching the NEXT page of children under
/// <see cref="ParentNodeId"/>. Null on the first request; on a subsequent
/// request carry back the token from the prior <see cref="BrowseNodeResult.ContinuationToken"/>.
/// Additive (appended last) so positional construction stays source-compatible.
/// </param>
/// <param name="SiteIdentifier">
/// Target site identifier — REQUIRED when the command is invoked via the management
/// HTTP API/CLI (the actor must route it to a site); ignored on the site side, where
/// routing already happened. Additive per the message-contract evolution rules.
/// </param>
public record BrowseNodeCommand(
    string ConnectionName,
    string? ParentNodeId,
    string? ContinuationToken = null,
    string? SiteIdentifier = null);

/// <param name="Children">Immediate children resolved for the browsed node (this page only).</param>
/// <param name="Truncated">True when the result was clipped (frame-budget cap or adapter-reported truncation).</param>
/// <param name="Failure">Structured failure, or null on success.</param>
/// <param name="ContinuationToken">
/// Opaque adapter cursor for the NEXT page when more children remain, or null
/// when this is the final page. Surface it back in a follow-up
/// <see cref="BrowseNodeCommand.ContinuationToken"/>. Additive (appended last).
/// </param>
public record BrowseNodeResult(
    IReadOnlyList<BrowseNode> Children,
    bool Truncated,
    BrowseFailure? Failure,
    string? ContinuationToken = null);

/// <summary>
/// Sent from CentralUI to a specific site to run a bounded recursive search of
/// the address space on the live server backing the given data connection.
/// </summary>
/// <remarks>
/// The address-space analogue of <see cref="BrowseNodeCommand"/>: where browse
/// walks one level at a time on user demand, search walks the tree itself
/// (bounded by depth + result caps) and returns the nodes whose DisplayName or
/// root-relative path contains <see cref="Query"/>. Keyed by
/// <see cref="ConnectionName"/> for the same reason as browse — the site-side
/// <c>DataConnectionManagerActor</c> indexes its children by connection name and
/// the central <c>DataConnections</c> id is intentionally not exposed at the
/// site. Routed over the same cross-cluster path as browse and resolved by the
/// owning connection's <c>IAddressSpaceSearchable</c> adapter.
/// </remarks>
/// <param name="ConnectionName">Name of the site-local data connection to search against.</param>
/// <param name="Query">Case-insensitive substring matched against each node's DisplayName and root-relative path.</param>
/// <param name="MaxDepth">Maximum number of levels below the root to descend. Must be non-negative.</param>
/// <param name="MaxResults">Maximum number of matches to return; when reached the walk stops early and <see cref="SearchAddressSpaceResult.CapReached"/> is set.</param>
/// <param name="SiteIdentifier">
/// Target site identifier — REQUIRED when the command is invoked via the management
/// HTTP API/CLI (the actor must route it to a site); ignored on the site side, where
/// routing already happened. Additive per the message-contract evolution rules.
/// </param>
public record SearchAddressSpaceCommand(
    string ConnectionName,
    string Query,
    int MaxDepth,
    int MaxResults,
    string? SiteIdentifier = null);

/// <param name="Matches">The matched address-space nodes, in breadth-first discovery order (capped at <see cref="SearchAddressSpaceCommand.MaxResults"/>).</param>
/// <param name="CapReached">True when a bound (result cap or the adapter's node-visit ceiling) cut the walk short, so more matches may exist than were returned.</param>
/// <param name="Failure">Structured failure, or null on success. Reuses the browse <see cref="BrowseFailure"/> kinds (the search path mirrors browse exactly).</param>
public record SearchAddressSpaceResult(
    IReadOnlyList<AddressSpaceMatch> Matches,
    bool CapReached,
    BrowseFailure? Failure);

public record BrowseFailure(
    BrowseFailureKind Kind,
    string Message);

public enum BrowseFailureKind
{
    ConnectionNotFound,
    ConnectionNotConnected,
    NotBrowsable,
    Timeout,
    ServerError
}
