using Microsoft.UI;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using System;
using System.Text.Json;
using Windows.UI;

namespace Sonex.Client.Controls;

public sealed partial class JsonProperty : UserControl
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        WriteIndented = true
    };

    private static readonly Color PropertyNameColor = Color.FromArgb(255, 0, 0, 255);
    private static readonly Color StringColor = Color.FromArgb(255, 163, 21, 21);
    private static readonly Color NumberColor = Color.FromArgb(255, 9, 134, 88);
    private static readonly Color LiteralColor = Color.FromArgb(255, 0, 0, 255);
    private static readonly Color NullColor = Color.FromArgb(255, 0, 0, 255);
    private static readonly Color FallbackTextColor = Color.FromArgb(255, 30, 30, 30);

    public JsonProperty()
    {
        InitializeComponent();
        ApplyReadOnlyState();
        UpdateDocument();
    }

    public string Header
    {
        get => (string)GetValue(HeaderProperty);
        set => SetValue(HeaderProperty, value);
    }

    public static readonly DependencyProperty HeaderProperty =
        DependencyProperty.Register(
            nameof(Header),
            typeof(string),
            typeof(JsonProperty),
            new PropertyMetadata(string.Empty));

    public string Value
    {
        get => (string)GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register(
            nameof(Value),
            typeof(string),
            typeof(JsonProperty),
            new PropertyMetadata(string.Empty, OnValueChanged));

    public bool IsReadOnly
    {
        get => (bool)GetValue(IsReadOnlyProperty);
        set => SetValue(IsReadOnlyProperty, value);
    }

    public static readonly DependencyProperty IsReadOnlyProperty =
        DependencyProperty.Register(
            nameof(IsReadOnly),
            typeof(bool),
            typeof(JsonProperty),
            new PropertyMetadata(true, OnIsReadOnlyChanged));

    public double EditorHeight
    {
        get => (double)GetValue(EditorHeightProperty);
        set => SetValue(EditorHeightProperty, value);
    }

    public static readonly DependencyProperty EditorHeightProperty =
        DependencyProperty.Register(
            nameof(EditorHeight),
            typeof(double),
            typeof(JsonProperty),
            new PropertyMetadata(120d));

    public TextWrapping TextWrapping
    {
        get => (TextWrapping)GetValue(TextWrappingProperty);
        set => SetValue(TextWrappingProperty, value);
    }

    public static readonly DependencyProperty TextWrappingProperty =
        DependencyProperty.Register(
            nameof(TextWrapping),
            typeof(TextWrapping),
            typeof(JsonProperty),
            new PropertyMetadata(TextWrapping.NoWrap));

    public ScrollBarVisibility HorizontalScrollBarVisibility
    {
        get => (ScrollBarVisibility)GetValue(HorizontalScrollBarVisibilityProperty);
        set => SetValue(HorizontalScrollBarVisibilityProperty, value);
    }

    public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty =
        DependencyProperty.Register(
            nameof(HorizontalScrollBarVisibility),
            typeof(ScrollBarVisibility),
            typeof(JsonProperty),
            new PropertyMetadata(ScrollBarVisibility.Auto));

    public ScrollBarVisibility VerticalScrollBarVisibility
    {
        get => (ScrollBarVisibility)GetValue(VerticalScrollBarVisibilityProperty);
        set => SetValue(VerticalScrollBarVisibilityProperty, value);
    }

    public static readonly DependencyProperty VerticalScrollBarVisibilityProperty =
        DependencyProperty.Register(
            nameof(VerticalScrollBarVisibility),
            typeof(ScrollBarVisibility),
            typeof(JsonProperty),
            new PropertyMetadata(ScrollBarVisibility.Auto));

    public GridLength HeaderWidth
    {
        get => (GridLength)GetValue(HeaderWidthProperty);
        set => SetValue(HeaderWidthProperty, value);
    }

    public static readonly DependencyProperty HeaderWidthProperty =
        DependencyProperty.Register(
            nameof(HeaderWidth),
            typeof(GridLength),
            typeof(JsonProperty),
            new PropertyMetadata(new GridLength(150)));

    private static void OnValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs _)
    {
        if (dependencyObject is JsonProperty property)
        {
            property.UpdateDocument();
        }
    }

    private static void OnIsReadOnlyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs _)
    {
        if (dependencyObject is JsonProperty property)
        {
            property.ApplyReadOnlyState();
        }
    }

    private void ApplyReadOnlyState()
    {
        Editor.IsReadOnly = IsReadOnly;
    }

    private void UpdateDocument()
    {
        var text = FormatJson(Value);
        var wasReadOnly = Editor.IsReadOnly;

        try
        {
            Editor.IsReadOnly = false;
            Editor.Document.SetText(TextSetOptions.None, text);
            Editor.Document.GetText(TextGetOptions.None, out var documentText);
            ApplyHighlighting(documentText);
        }
        finally
        {
            Editor.IsReadOnly = wasReadOnly;
        }
    }

    private static string FormatJson(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        try
        {
            using var document = JsonDocument.Parse(value);
            return JsonSerializer.Serialize(document.RootElement, JsonOptions);
        }
        catch (JsonException)
        {
            return value.Trim();
        }
    }

    private void ApplyHighlighting(string text)
    {
        if (string.IsNullOrEmpty(text))
        {
            return;
        }

        var defaultColor = ResolveTextColor();
        SetColor(0, text.Length, defaultColor);

        for (var i = 0; i < text.Length;)
        {
            var current = text[i];
            if (char.IsWhiteSpace(current))
            {
                i++;
                continue;
            }

            if (current == '"')
            {
                var end = ReadStringEnd(text, i);
                SetColor(i, end, IsPropertyName(text, end) ? PropertyNameColor : StringColor);
                i = end;
                continue;
            }

            if (current == '-' || char.IsDigit(current))
            {
                var end = ReadNumberEnd(text, i);
                SetColor(i, end, NumberColor);
                i = end;
                continue;
            }

            if (StartsWith(text, i, "true") || StartsWith(text, i, "false"))
            {
                var end = i + (text[i] == 't' ? 4 : 5);
                SetColor(i, end, LiteralColor);
                i = end;
                continue;
            }

            if (StartsWith(text, i, "null"))
            {
                SetColor(i, i + 4, NullColor);
                i += 4;
                continue;
            }

            if ("{}[]:,".IndexOf(current) >= 0)
            {
                SetColor(i, i + 1, defaultColor);
            }

            i++;
        }
    }

    private void SetColor(int start, int end, Color color)
    {
        if (start < 0 || end <= start)
        {
            return;
        }

        var range = Editor.Document.GetRange(start, end);
        range.CharacterFormat.ForegroundColor = color;
    }

    private static int ReadStringEnd(string text, int start)
    {
        var escaped = false;
        for (var i = start + 1; i < text.Length; i++)
        {
            if (escaped)
            {
                escaped = false;
                continue;
            }

            if (text[i] == '\\')
            {
                escaped = true;
                continue;
            }

            if (text[i] == '"')
            {
                return i + 1;
            }
        }

        return text.Length;
    }

    private static int ReadNumberEnd(string text, int start)
    {
        var i = start + 1;
        while (i < text.Length && "0123456789+-.eE".IndexOf(text[i]) >= 0)
        {
            i++;
        }

        return i;
    }

    private static bool IsPropertyName(string text, int stringEnd)
    {
        for (var i = stringEnd; i < text.Length; i++)
        {
            if (char.IsWhiteSpace(text[i]))
            {
                continue;
            }

            return text[i] == ':';
        }

        return false;
    }

    private static bool StartsWith(string text, int start, string value)
    {
        return start + value.Length <= text.Length
            && string.Compare(text, start, value, 0, value.Length, StringComparison.Ordinal) == 0;
    }

    private static Color ResolveTextColor()
    {
        return Application.Current.Resources.TryGetValue("TextFillColorPrimaryBrush", out var value)
            && value is SolidColorBrush brush
                ? brush.Color
                : FallbackTextColor;
    }
}
