using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using Avalonia.VisualTree;
using OF_DL.Gui.ViewModels;

namespace OF_DL.Gui.Controls;

public class FileNameFormatOverlayTextBlock : TextBlock
{
    public static readonly StyledProperty<IEnumerable<FileNameFormatSegmentViewModel>?> SegmentsProperty =
        AvaloniaProperty.Register<FileNameFormatOverlayTextBlock, IEnumerable<FileNameFormatSegmentViewModel>?>(nameof(Segments));

    public static readonly StyledProperty<TextBox?> SourceTextBoxProperty =
        AvaloniaProperty.Register<FileNameFormatOverlayTextBlock, TextBox?>(nameof(SourceTextBox));

    private INotifyCollectionChanged? _segmentsCollection;
    private TextBox? _attachedTextBox;
    private ScrollViewer? _attachedScrollViewer;

    static FileNameFormatOverlayTextBlock()
    {
        SegmentsProperty.Changed.AddClassHandler<FileNameFormatOverlayTextBlock>(OnSegmentsChanged);
        SourceTextBoxProperty.Changed.AddClassHandler<FileNameFormatOverlayTextBlock>(OnSourceTextBoxChanged);
    }

    public IEnumerable<FileNameFormatSegmentViewModel>? Segments
    {
        get => GetValue(SegmentsProperty);
        set => SetValue(SegmentsProperty, value);
    }

    public TextBox? SourceTextBox
    {
        get => GetValue(SourceTextBoxProperty);
        set => SetValue(SourceTextBoxProperty, value);
    }

    protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
    {
        base.OnAttachedToVisualTree(e);
        DetachSegmentsCollection();
        AttachSegmentsCollection(Segments);
        AttachSourceTextBox(SourceTextBox);
        RebuildInlines();
    }

    protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
    {
        DetachSourceTextBox();
        DetachSegmentsCollection();
        base.OnDetachedFromVisualTree(e);
    }

    private static void OnSegmentsChanged(
        FileNameFormatOverlayTextBlock sender,
        AvaloniaPropertyChangedEventArgs e)
    {
        sender.DetachSegmentsCollection();
        sender.AttachSegmentsCollection(e.NewValue as IEnumerable<FileNameFormatSegmentViewModel>);
        sender.RebuildInlines();
    }

    private static void OnSourceTextBoxChanged(
        FileNameFormatOverlayTextBlock sender,
        AvaloniaPropertyChangedEventArgs e)
    {
        sender.AttachSourceTextBox(e.NewValue as TextBox);
    }

    private void AttachSegmentsCollection(IEnumerable<FileNameFormatSegmentViewModel>? segments)
    {
        _segmentsCollection = segments as INotifyCollectionChanged;
        if (_segmentsCollection is not null)
        {
            _segmentsCollection.CollectionChanged += OnSegmentsCollectionChanged;
        }
    }

    private void DetachSegmentsCollection()
    {
        if (_segmentsCollection is null)
        {
            return;
        }

        _segmentsCollection.CollectionChanged -= OnSegmentsCollectionChanged;
        _segmentsCollection = null;
    }

    private void OnSegmentsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        RebuildInlines();
    }

    private void AttachSourceTextBox(TextBox? textBox)
    {
        if (ReferenceEquals(_attachedTextBox, textBox))
        {
            UpdateOverlayOffset();
            return;
        }

        DetachSourceTextBox();
        _attachedTextBox = textBox;
        if (_attachedTextBox is null)
        {
            UpdateOverlayOffset();
            return;
        }

        _attachedTextBox.TemplateApplied += OnSourceTextBoxTemplateApplied;
        AttachScrollViewer(FindSourceScrollViewer(_attachedTextBox));
        UpdateOverlayOffset();
    }

    private void DetachSourceTextBox()
    {
        if (_attachedTextBox is not null)
        {
            _attachedTextBox.TemplateApplied -= OnSourceTextBoxTemplateApplied;
        }

        if (_attachedScrollViewer is not null)
        {
            _attachedScrollViewer.ScrollChanged -= OnSourceScrollChanged;
            _attachedScrollViewer = null;
        }

        _attachedTextBox = null;
        UpdateOverlayOffset();
    }

    private void OnSourceTextBoxTemplateApplied(object? sender, TemplateAppliedEventArgs e)
    {
        ScrollViewer? scrollViewer = e.NameScope.Find<ScrollViewer>("PART_ScrollViewer");
        AttachScrollViewer(scrollViewer ?? FindSourceScrollViewer(_attachedTextBox));
        UpdateOverlayOffset();
    }

    private void OnSourceScrollChanged(object? sender, ScrollChangedEventArgs e)
    {
        UpdateOverlayOffset();
    }

    private void AttachScrollViewer(ScrollViewer? scrollViewer)
    {
        if (ReferenceEquals(_attachedScrollViewer, scrollViewer))
        {
            return;
        }

        if (_attachedScrollViewer is not null)
        {
            _attachedScrollViewer.ScrollChanged -= OnSourceScrollChanged;
        }

        _attachedScrollViewer = scrollViewer;
        if (_attachedScrollViewer is not null)
        {
            _attachedScrollViewer.ScrollChanged += OnSourceScrollChanged;
        }
    }

    private void UpdateOverlayOffset()
    {
        if (_attachedScrollViewer is null)
        {
            RenderTransform = null;
            return;
        }

        RenderTransform = new TranslateTransform(-_attachedScrollViewer.Offset.X, -_attachedScrollViewer.Offset.Y);
    }

    private static ScrollViewer? FindSourceScrollViewer(TextBox? textBox)
    {
        if (textBox is null)
        {
            return null;
        }

        return textBox.GetVisualDescendants().OfType<ScrollViewer>().FirstOrDefault();
    }

    private void RebuildInlines()
    {
        if (Inlines is null)
        {
            return;
        }

        Inlines.Clear();
        if (Segments is null)
        {
            return;
        }

        foreach (FileNameFormatSegmentViewModel segment in Segments)
        {
            Run run = new()
            {
                Text = segment.Text ?? string.Empty,
                Foreground = ParseForegroundBrush(segment.Foreground)
            };
            Inlines.Add(run);
        }
    }

    private IBrush ParseForegroundBrush(string? value)
    {
        if (!string.IsNullOrWhiteSpace(value) && Color.TryParse(value, out Color color))
        {
            return new SolidColorBrush(color);
        }

        return Foreground ?? Brushes.Transparent;
    }
}
