using System.Globalization;
using System.Text.Json;

namespace Ehwrj.Core.Models;

public sealed class MapInfo
{
    public static MapInfo Empty { get; } = new(null, null, null, null, null, null, null);

    public MapInfo(
        double? minX,
        double? maxX,
        double? minY,
        double? maxY,
        double? gridStepX,
        double? gridStepY,
        string? raw)
    {
        MinX = minX;
        MaxX = maxX;
        MinY = minY;
        MaxY = maxY;
        GridStepX = gridStepX;
        GridStepY = gridStepY;
        Raw = raw;
    }

    public double? MinX { get; }
    public double? MaxX { get; }
    public double? MinY { get; }
    public double? MaxY { get; }
    public double? GridStepX { get; }
    public double? GridStepY { get; }
    public string? Raw { get; }

    public static MapInfo FromJson(string json)
    {
        using var document = JsonDocument.Parse(json);
        var root = document.RootElement;

        var minX = ReadNumber(root, "min_x", "xmin", "map_min_x");
        var maxX = ReadNumber(root, "max_x", "xmax", "map_max_x");
        var minY = ReadNumber(root, "min_y", "ymin", "map_min_y");
        var maxY = ReadNumber(root, "max_y", "ymax", "map_max_y");

        if (root.TryGetProperty("map_min", out var mapMin) && mapMin.ValueKind == JsonValueKind.Array)
        {
            minX ??= ReadArrayNumber(mapMin, 0);
            minY ??= ReadArrayNumber(mapMin, 1);
        }

        if (root.TryGetProperty("map_max", out var mapMax) && mapMax.ValueKind == JsonValueKind.Array)
        {
            maxX ??= ReadArrayNumber(mapMax, 0);
            maxY ??= ReadArrayNumber(mapMax, 1);
        }

        if (root.TryGetProperty("bounds", out var bounds) &&
            bounds.ValueKind == JsonValueKind.Array &&
            bounds.GetArrayLength() >= 2 &&
            bounds[0].ValueKind == JsonValueKind.Array &&
            bounds[1].ValueKind == JsonValueKind.Array)
        {
            minX ??= ReadArrayNumber(bounds[0], 0);
            minY ??= ReadArrayNumber(bounds[0], 1);
            maxX ??= ReadArrayNumber(bounds[1], 0);
            maxY ??= ReadArrayNumber(bounds[1], 1);
        }

        if (root.TryGetProperty("grid_size", out var gridSize) && gridSize.ValueKind == JsonValueKind.Array)
        {
            minX ??= 0;
            minY ??= 0;
            maxX ??= ReadArrayNumber(gridSize, 0);
            maxY ??= ReadArrayNumber(gridSize, 1);
        }

        double? gridStepX = null;
        double? gridStepY = null;
        if (root.TryGetProperty("grid_steps", out var gridSteps) && gridSteps.ValueKind == JsonValueKind.Array)
        {
            gridStepX = ReadArrayNumber(gridSteps, 0);
            gridStepY = ReadArrayNumber(gridSteps, 1);
        }

        return new MapInfo(minX, maxX, minY, maxY, gridStepX, gridStepY, json);
    }

    private static double? ReadNumber(JsonElement element, params string[] names)
    {
        foreach (var name in names)
        {
            if (element.ValueKind == JsonValueKind.Object &&
                element.TryGetProperty(name, out var value))
            {
                var number = ReadNumber(value);
                if (number.HasValue) return number;
            }
        }

        return null;
    }

    private static double? ReadNumber(JsonElement value)
    {
        return value.ValueKind switch
        {
            JsonValueKind.Number when value.TryGetDouble(out var n) => n,
            JsonValueKind.String => ParseNumber(value.GetString()),
            _ => null
        };
    }

    private static double? ReadArrayNumber(JsonElement array, int index)
    {
        return array.ValueKind == JsonValueKind.Array && array.GetArrayLength() > index
            ? ReadNumber(array[index])
            : null;
    }

    private static double? ParseNumber(string? text)
    {
        if (string.IsNullOrWhiteSpace(text))
        {
            return null;
        }

        if (double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var value))
        {
            return value;
        }

        var normalized = text.Replace(',', '.');
        return double.TryParse(normalized, NumberStyles.Float, CultureInfo.InvariantCulture, out value)
            ? value
            : null;
    }
}
