using TeamsISO.Engine.Domain;

namespace TeamsISO.Engine.Discovery;

/// <summary>
/// Parses NDI source strings emitted by Microsoft Teams.
///
/// Teams has shipped two on-the-wire naming conventions for its NDI broadcast
/// outputs. Both are recognized.
///
/// Legacy (older Teams desktop):
///   "MACHINE (Teams)"                — auto-mixed active-speaker output
///   "MACHINE (Teams Audio)"          — audio-only mix
///   "MACHINE (Teams Screen Share)"   — screen share
///   "MACHINE (Teams - Display Name)" — per-participant
///
/// Current (the new Microsoft Teams desktop client, observed 2026):
///   "MACHINE (MS Teams - Active Speaker)" — auto-mixed active-speaker output
///   "MACHINE (MS Teams - Audio Mix)"      — audio-only mix
///   "MACHINE (MS Teams - Screen Share)"   — screen share
///   "MACHINE (MS Teams - (Local))"        — the local user's own preview
///   "MACHINE (MS Teams - Display Name)"   — per-participant
///
/// "Microsoft Teams" is also accepted as a defensive future-proof brand prefix.
/// </summary>
public static class NdiSourceParser
{
    /// <summary>
    /// Brand prefixes we recognize, ordered LONGEST FIRST so that "MS Teams"
    /// is matched before the shorter "Teams" substring it contains.
    /// </summary>
    private static readonly string[] BrandPrefixes = { "Microsoft Teams", "MS Teams", "Teams" };

    /// <summary>
    /// Reserved suffix tokens that classify a source as a non-participant kind.
    /// Includes both legacy spellings ("Audio") and current ("Audio Mix").
    /// </summary>
    private static readonly Dictionary<string, NdiSourceKind> ReservedSuffixes = new(StringComparer.Ordinal)
    {
        ["Active Speaker"] = NdiSourceKind.ActiveSpeaker,
        ["Audio"] = NdiSourceKind.Audio,
        ["Audio Mix"] = NdiSourceKind.Audio,
        ["Screen Share"] = NdiSourceKind.ScreenShare,
    };

    public static NdiSource? Parse(string fullName)
    {
        if (string.IsNullOrWhiteSpace(fullName)) return null;
        if (!fullName.EndsWith(')')) return null;

        // Locate "<MACHINE> (<BRAND>" by scanning for " (BRAND" with the longest
        // brand first. Anchoring this way (rather than to the last '(') means
        // machine names that themselves contain parens won't truncate the boundary,
        // and display names that contain parens (e.g. "(Local)") survive intact
        // because we extract them by string length, not by re-scanning for ')'.
        var openParen = -1;
        string? brand = null;
        foreach (var candidate in BrandPrefixes)
        {
            var needle = " (" + candidate;
            var i = fullName.IndexOf(needle, StringComparison.Ordinal);
            if (i > 0)
            {
                openParen = i + 1;
                brand = candidate;
                break;
            }
        }
        if (openParen < 0 || brand is null) return null;

        var machine = fullName[..openParen].TrimEnd();
        if (machine.Length == 0) return null;

        // inner is the text between '(' and the trailing ')'.
        var inner = fullName.Substring(openParen + 1, fullName.Length - openParen - 2).Trim();
        if (!inner.StartsWith(brand, StringComparison.Ordinal)) return null;

        var rest = inner[brand.Length..].TrimStart();

        // Legacy "MACHINE (Teams)" — bare brand, no suffix → active speaker.
        if (rest.Length == 0)
            return new NdiSource(fullName, machine, NdiSourceKind.ActiveSpeaker, DisplayName: null);

        // Legacy: brand followed by a reserved word with no dash, e.g. "Teams Audio".
        if (ReservedSuffixes.TryGetValue(rest, out var legacyKind))
            return new NdiSource(fullName, machine, legacyKind, DisplayName: null);

        // Current: brand followed by " - <suffix>".
        if (rest.StartsWith('-'))
        {
            var afterDash = rest[1..].TrimStart();
            if (afterDash.Length == 0) return null;
            if (ReservedSuffixes.TryGetValue(afterDash, out var taggedKind))
                return new NdiSource(fullName, machine, taggedKind, DisplayName: null);
            return new NdiSource(fullName, machine, NdiSourceKind.Participant, afterDash);
        }

        return null;
    }
}
