namespace NexusShell.Services;

/// <summary>
/// Semver comparison for Nexus Package versions (major.minor.patch with optional pre-release).
/// </summary>
public static class NexusPackageVersionComparer
{
    public static int Compare(string? left, string? right)
    {
        if (string.IsNullOrWhiteSpace(left) && string.IsNullOrWhiteSpace(right))
        {
            return 0;
        }

        if (string.IsNullOrWhiteSpace(left))
        {
            return -1;
        }

        if (string.IsNullOrWhiteSpace(right))
        {
            return 1;
        }

        var leftParts = Parse(left);
        var rightParts = Parse(right);

        for (var i = 0; i < 3; i++)
        {
            var cmp = leftParts.Numeric[i].CompareTo(rightParts.Numeric[i]);
            if (cmp != 0)
            {
                return cmp;
            }
        }

        var preReleaseCmp = ComparePreRelease(leftParts.PreRelease, rightParts.PreRelease);
        if (preReleaseCmp != 0)
        {
            return preReleaseCmp;
        }

        return string.Compare(leftParts.Build, rightParts.Build, StringComparison.OrdinalIgnoreCase);
    }

    public static bool IsLessThan(string left, string right) => Compare(left, right) < 0;

    public static bool IsGreaterThan(string left, string right) => Compare(left, right) > 0;

    public static bool IsEqual(string left, string right) => Compare(left, right) == 0;

    public static bool IsValidSemVer(string? version)
    {
        if (string.IsNullOrWhiteSpace(version))
        {
            return false;
        }

        try
        {
            Parse(version);
            return true;
        }
        catch
        {
            return false;
        }
    }

    private static VersionParts Parse(string version)
    {
        var trimmed = version.Trim();
        var buildIndex = trimmed.IndexOf('+');
        var build = buildIndex >= 0 ? trimmed[(buildIndex + 1)..] : string.Empty;
        var core = buildIndex >= 0 ? trimmed[..buildIndex] : trimmed;

        var dashIndex = core.IndexOf('-');
        var preRelease = dashIndex >= 0 ? core[(dashIndex + 1)..] : string.Empty;
        var numericPart = dashIndex >= 0 ? core[..dashIndex] : core;

        var segments = numericPart.Split('.', StringSplitOptions.RemoveEmptyEntries);
        if (segments.Length == 0)
        {
            throw new FormatException($"Invalid version: {version}");
        }

        var numeric = new int[3];
        for (var i = 0; i < 3; i++)
        {
            if (i >= segments.Length)
            {
                numeric[i] = 0;
                continue;
            }

            if (!int.TryParse(segments[i], out var value) || value < 0)
            {
                throw new FormatException($"Invalid version segment: {segments[i]}");
            }

            numeric[i] = value;
        }

        return new VersionParts(numeric, preRelease, build);
    }

    private static int ComparePreRelease(string left, string right)
    {
        var leftEmpty = string.IsNullOrEmpty(left);
        var rightEmpty = string.IsNullOrEmpty(right);

        if (leftEmpty && rightEmpty)
        {
            return 0;
        }

        if (leftEmpty)
        {
            return 1;
        }

        if (rightEmpty)
        {
            return -1;
        }

        var leftParts = left.Split('.');
        var rightParts = right.Split('.');
        var count = Math.Max(leftParts.Length, rightParts.Length);

        for (var i = 0; i < count; i++)
        {
            var l = i < leftParts.Length ? leftParts[i] : string.Empty;
            var r = i < rightParts.Length ? rightParts[i] : string.Empty;

            var leftIsNumeric = int.TryParse(l, out var leftNum);
            var rightIsNumeric = int.TryParse(r, out var rightNum);

            if (leftIsNumeric && rightIsNumeric)
            {
                var cmp = leftNum.CompareTo(rightNum);
                if (cmp != 0)
                {
                    return cmp;
                }

                continue;
            }

            if (leftIsNumeric != rightIsNumeric)
            {
                return leftIsNumeric ? -1 : 1;
            }

            var strCmp = string.Compare(l, r, StringComparison.OrdinalIgnoreCase);
            if (strCmp != 0)
            {
                return strCmp;
            }
        }

        return 0;
    }

    private sealed record VersionParts(int[] Numeric, string PreRelease, string Build);
}
