namespace ZB.MOM.WW.OtOpcUa.Driver.Mqtt;
///
/// The two shape rules an authored MQTT address must satisfy to be publishable, shared by
/// the deploy-time inspector () and the write encoder
/// () so the warning an operator is shown and the refusal the driver
/// makes can never disagree.
///
///
///
/// Neither rule gates the READ path, and that asymmetry is the point. A tag whose
/// subscription topic carries a wildcard still reads (ambiguously — hence
/// 's long-standing advisory), and a Sparkplug tag
/// with a malformed id still parses. What these rules decide is whether an outbound
/// message could legally be addressed at all, so they belong to the write encoder and to the
/// inspector, never to the mappers' accept gate — which must stay exactly as strict as the
/// runtime reader (Gitea #551).
///
///
/// Why a wildcard write destination is a refusal rather than a best effort. MQTT-3.3.2-2
/// forbids a wildcard in a PUBLISH topic, and a conformant broker answers a protocol violation by
/// closing the network connection — which for this driver is the one connection every
/// subscription lives on. Publishing anyway would turn one mis-authored tag into a whole-driver
/// outage; refusing costs that tag its write and nothing else.
///
///
internal static class MqttAddressRules
{
/// The MQTT wildcard characters — illegal in any concrete (published or subscribed) topic.
private static readonly char[] TopicWildcards = ['+', '#'];
///
/// Characters that cannot appear in a single MQTT topic segment, which is what a Sparkplug
/// group / edge-node / device id is. / joins the wildcards here because it would silently
/// re-shape the command topic into a different address rather than making it unpublishable — and
/// because a decoded incoming id can never contain one, so an authored id that does is a binding
/// that could never have resolved.
///
private static readonly char[] SegmentIllegalChars = ['/', '+', '#'];
///
/// Whether carries an MQTT wildcard (+ / #).
///
/// A topic string; a /empty one carries none.
/// when a wildcard is present.
internal static bool HasWildcard(string? topic) =>
!string.IsNullOrEmpty(topic) && topic.IndexOfAny(TopicWildcards) >= 0;
///
/// Whether is unusable as a single MQTT topic segment — i.e. as a Sparkplug
/// group, edge-node or device id.
///
/// The authored id; a /empty one is not judged here (absence
/// is handled by the mappers' own presence checks, and deviceId is legitimately absent).
/// when the id contains /, + or #.
///
/// Deliberately NOT applied to metricName: a metric name rides in the Sparkplug
/// payload, not in the topic, and Sparkplug's own canonical names use / (e.g.
/// Node Control/Rebirth). Applying it there would refuse every conformant node-control tag.
///
internal static bool IsIllegalTopicSegment(string? id) =>
!string.IsNullOrEmpty(id) && id.IndexOfAny(SegmentIllegalChars) >= 0;
}