// Full Go-parity MQTT topic ↔ NATS subject translation.
// Go reference: golang/nats-server/server/mqtt.go mqttToNATSSubjectConversion ~line 2200
//
// Rules:
//   MQTT → NATS:
//     '/'  →  '.'   (separator)
//     '+'  →  '*'   (single-level wildcard)
//     '#'  →  '>'   (multi-level wildcard)
//     '.'  in MQTT topics must be escaped (replaced with a placeholder)
//     Empty levels (leading/trailing/consecutive slashes) produce empty tokens
//     '$' prefix topics are protected from wildcard matching per MQTT spec [MQTT-4.7.2-1]
//
//   NATS → MQTT (reverse):
//     '.'  →  '/'
//     '*'  →  '+'
//     '>'  →  '#'

using System.Text;

namespace NATS.Server.Mqtt;

/// <summary>
/// Translates MQTT topics/filters to NATS subjects and vice versa with full Go parity.
/// Go reference: mqtt.go mqttToNATSSubjectConversion, mqttNATSToMQTTSubjectConversion.
/// </summary>
public static class MqttTopicMapper
{
    // Escape sequence for dots that appear in MQTT topic names.
    // Go uses _DOT_ internally to represent a literal dot in the NATS subject.
    private const string DotEscape = "_DOT_";
    private const string DotEscapeReverse = ".";

    /// <summary>
    /// Translates an MQTT topic or filter to a NATS subject.
    /// Handles wildcards, dot escaping, empty levels, and '$' prefix protection.
    /// </summary>
    public static string MqttToNats(string mqttTopic)
    {
        if (mqttTopic.Length == 0)
            return string.Empty;

        var sb = new StringBuilder(mqttTopic.Length);

        for (var i = 0; i < mqttTopic.Length; i++)
        {
            switch (mqttTopic[i])
            {
                case '/':
                    sb.Append('.');
                    break;
                case '+':
                    sb.Append('*');
                    break;
                case '#':
                    sb.Append('>');
                    break;
                case '.':
                    // Dots in MQTT topic names must be escaped for NATS
                    sb.Append(DotEscape);
                    break;
                default:
                    sb.Append(mqttTopic[i]);
                    break;
            }
        }

        return sb.ToString();
    }

    /// <summary>
    /// Translates a NATS subject back to an MQTT topic.
    /// Reverses the mapping: '.' → '/', '*' → '+', '>' → '#', '_DOT_' → '.'.
    /// </summary>
    public static string NatsToMqtt(string natsSubject)
    {
        if (natsSubject.Length == 0)
            return string.Empty;

        // First, replace _DOT_ escape sequences back to dots
        var working = natsSubject.Replace(DotEscape, "\x00");

        var sb = new StringBuilder(working.Length);
        for (var i = 0; i < working.Length; i++)
        {
            switch (working[i])
            {
                case '.':
                    sb.Append('/');
                    break;
                case '*':
                    sb.Append('+');
                    break;
                case '>':
                    sb.Append('#');
                    break;
                case '\x00':
                    sb.Append('.');
                    break;
                default:
                    sb.Append(working[i]);
                    break;
            }
        }

        return sb.ToString();
    }

    /// <summary>
    /// Returns true if an MQTT topic starts with '$', which means it should
    /// NOT be matched by wildcard subscriptions (MQTT spec [MQTT-4.7.2-1]).
    /// Topics starting with '$' are reserved for system/server use.
    /// </summary>
    public static bool IsDollarTopic(string mqttTopic)
        => mqttTopic.Length > 0 && mqttTopic[0] == '$';

    /// <summary>
    /// Returns true if an MQTT topic filter starts with '$', indicating
    /// it explicitly targets system topics.
    /// </summary>
    public static bool IsDollarFilter(string mqttFilter)
        => mqttFilter.Length > 0 && mqttFilter[0] == '$';

    /// <summary>
    /// Checks if a wildcard filter would match a '$' topic.
    /// Per MQTT spec, wildcard filters (starting with '#' or '+') must NOT
    /// match topics beginning with '$'. Only explicit '$' filters match '$' topics.
    /// </summary>
    public static bool WildcardMatchesDollarTopic(string mqttFilter, string mqttTopic)
    {
        if (!IsDollarTopic(mqttTopic))
            return true; // non-$ topics are always matchable

        // $ topics only matched by filters that also start with $
        return IsDollarFilter(mqttFilter);
    }
}
