using System.Globalization;
using System.Text.RegularExpressions;

static class DimensionalToleranceParser
{
    static readonly Regex FitDesignationRegex = new(
        @"(?<![A-Za-z])(?<letters>[A-Za-z]{1,2})\s*(?<grade>01|0|1[0-8]|[1-9])(?!\d)",
        RegexOptions.Compiled | RegexOptions.CultureInvariant);

    static readonly Regex ExplicitDeviationRegex = new(
        @"\(\s*(?<upper>[+\-−]?\s*\d+(?:[.,]\d+)?)\s*/\s*(?<lower>[+\-−]?\s*\d+(?:[.,]\d+)?)\s*\)",
        RegexOptions.Compiled | RegexOptions.CultureInvariant);

    static readonly Regex PlusMinusRegex = new(
        @"[±]\s*(?<value>\d+(?:[.,]\d+)?)",
        RegexOptions.Compiled | RegexOptions.CultureInvariant);

    public static Dictionary<string, object?> Parse(
        string text,
        string rawText = "",
        double? measurement = null,
        string objectName = "")
    {
        var normalized = Normalize(text);
        var dimensionKind = DetectDimensionKind(normalized, objectName);
        var nominal = ExtractNominalSize(normalized, measurement, dimensionKind);
        var designations = ExtractFitDesignations(normalized);
        var deviations = ExtractDeviations(normalized);
        var warnings = new List<string>();

        if (dimensionKind == "radius" && designations.Count > 0)
            warnings.Add("fit_designation_applied_to_radius_dimension");
        if (designations.Count == 1)
            warnings.Add("single_tolerance_designation_does_not_define_complete_hole_shaft_fit");
        if (nominal == null)
            warnings.Add("nominal_size_not_parsed");

        var hole = designations.FirstOrDefault(item => Equals(item["role"], "hole"));
        var shaft = designations.FirstOrDefault(item => Equals(item["role"], "shaft"));
        var fitCode = string.Join("/", designations.Select(item => Convert.ToString(item["designation"], CultureInfo.InvariantCulture)));

        return new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
        {
            ["dimension_kind"] = dimensionKind,
            ["nominal_size_mm"] = nominal,
            ["interface_diameter_mm"] = nominal == null ? null : dimensionKind == "radius" ? nominal * 2 : nominal,
            ["fit_code"] = string.IsNullOrWhiteSpace(fitCode) ? null : fitCode,
            ["fit_grade"] = string.IsNullOrWhiteSpace(fitCode) ? null : fitCode,
            ["fit_designations"] = designations,
            ["hole_tolerance_designation"] = hole?["designation"],
            ["shaft_tolerance_designation"] = shaft?["designation"],
            ["fit_completeness"] = hole != null && shaft != null ? "hole_and_shaft_pair" : designations.Count > 0 ? "single_designation" : "none",
            ["upper_deviation_mm"] = deviations.Upper,
            ["lower_deviation_mm"] = deviations.Lower,
            ["deviation_source"] = deviations.Source,
            ["raw_text"] = rawText,
            ["text"] = normalized,
            ["parse_warnings"] = warnings
        };
    }

    public static bool LooksLikeTolerance(string rawText, string text)
    {
        var merged = rawText + " " + text;
        return merged.Contains(@"\S", StringComparison.OrdinalIgnoreCase) ||
               FitDesignationRegex.IsMatch(Normalize(merged)) ||
               Regex.IsMatch(merged, @"[+\-±]\s*\d+(?:\.\d+)?", RegexOptions.CultureInvariant);
    }

    static List<Dictionary<string, object?>> ExtractFitDesignations(string text)
    {
        var result = new List<Dictionary<string, object?>>();
        foreach (Match match in FitDesignationRegex.Matches(text))
        {
            var letters = match.Groups["letters"].Value;
            var grade = match.Groups["grade"].Value;
            var role = letters == letters.ToUpperInvariant()
                ? "hole"
                : letters == letters.ToLowerInvariant()
                    ? "shaft"
                    : "unknown";
            result.Add(new Dictionary<string, object?>
            {
                ["designation"] = letters + grade,
                ["fundamental_deviation"] = letters,
                ["it_grade"] = grade,
                ["role"] = role
            });
        }
        return result;
    }

    static string DetectDimensionKind(string text, string objectName)
    {
        if (Regex.IsMatch(text, @"^\s*R\s*\d", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) ||
            objectName.Contains("RadialDimension", StringComparison.OrdinalIgnoreCase))
            return "radius";
        if (text.IndexOfAny(['Φ', 'φ', '⌀', 'Ø']) >= 0 ||
            objectName.Contains("DiametricDimension", StringComparison.OrdinalIgnoreCase))
            return "diameter";
        return "linear_or_unspecified";
    }

    static double? ExtractNominalSize(string text, double? measurement, string dimensionKind)
    {
        var pattern = dimensionKind switch
        {
            "radius" => @"^\s*R\s*(?<value>\d+(?:[.,]\d+)?)",
            "diameter" => @"[Φφ⌀Ø]\s*(?<value>\d+(?:[.,]\d+)?)",
            _ => @"(?<value>\d+(?:[.,]\d+)?)"
        };
        var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
        if (match.Success && TryParseNumber(match.Groups["value"].Value, out var parsed))
            return parsed;
        return measurement is > 0 ? measurement : null;
    }

    static (double? Upper, double? Lower, string? Source) ExtractDeviations(string text)
    {
        var explicitMatch = ExplicitDeviationRegex.Match(text);
        if (explicitMatch.Success &&
            TryParseNumber(explicitMatch.Groups["upper"].Value, out var upper) &&
            TryParseNumber(explicitMatch.Groups["lower"].Value, out var lower))
            return (upper, lower, "explicit_upper_lower");

        var plusMinusMatch = PlusMinusRegex.Match(text);
        if (plusMinusMatch.Success && TryParseNumber(plusMinusMatch.Groups["value"].Value, out var value))
            return (value, -value, "explicit_plus_minus");

        return (null, null, null);
    }

    static bool TryParseNumber(string text, out double value) =>
        double.TryParse(
            text.Replace(" ", "", StringComparison.Ordinal).Replace('−', '-').Replace(',', '.'),
            NumberStyles.Float,
            CultureInfo.InvariantCulture,
            out value);

    static string Normalize(string text)
    {
        var value = text ?? "";
        value = Regex.Replace(value, @"\\S([^;]+)\^([^;]*);", "$1/$2", RegexOptions.CultureInvariant);
        value = Regex.Replace(value, @"\\S([^;]+);", "$1", RegexOptions.CultureInvariant);
        value = Regex.Replace(value, @"\\[AH][^;]*;", "", RegexOptions.CultureInvariant);
        value = value.Replace("{", "", StringComparison.Ordinal).Replace("}", "", StringComparison.Ordinal);
        return Regex.Replace(value, @"\s+", " ").Trim();
    }
}
