using System;
using Avalonia.Diagnostics.Models;
using Avalonia.Diagnostics.Views;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.VisualTree;

namespace Avalonia.Diagnostics.ViewModels
{
    internal class EventTreeNode : EventTreeNodeBase
    {
        private readonly EventsPageViewModel _parentViewModel;
        private bool _isRegistered;
        private FiredEvent? _currentEvent;

        public EventTreeNode(EventOwnerTreeNode parent, RoutedEvent @event, EventsPageViewModel vm)
            : base(parent, @event.Name)
        {
            Event = @event ?? throw new ArgumentNullException(nameof(@event));
            _parentViewModel = vm ?? throw new ArgumentNullException(nameof(vm));
        }

        public RoutedEvent Event { get; }

        public override bool? IsEnabled
        {
            get => base.IsEnabled;
            set
            {
                if (base.IsEnabled != value)
                {
                    base.IsEnabled = value;
                    UpdateTracker();
                    if (Parent != null && _updateParent)
                    {
                        try
                        {
                            Parent._updateChildren = false;
                            Parent.UpdateChecked();
                        }
                        finally
                        {
                            Parent._updateChildren = true;
                        }
                    }
                }
            }
        }

        private void UpdateTracker()
        {
            if (IsEnabled.GetValueOrDefault() && !_isRegistered)
            {
                var allRoutes = RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble;

                // FIXME: This leaks event handlers.
                Event.AddClassHandler(typeof(object), HandleEvent, allRoutes, handledEventsToo: true);
                Event.RouteFinished.Subscribe(HandleRouteFinished);
                
                _isRegistered = true;
            }
        }

        private void HandleEvent(object? sender, RoutedEventArgs e)
        {
            if (!_isRegistered || IsEnabled == false)
                return;
            if (sender is IVisual v && BelongsToDevTool(v))
                return;

            var s = sender!;
            var handled = e.Handled;
            var route = e.Route;

            void handler()
            {
                if (_currentEvent == null || !_currentEvent.IsPartOfSameEventChain(e))
                {
                    _currentEvent = new FiredEvent(e, new EventChainLink(s, handled, route));

                    _parentViewModel.RecordedEvents.Add(_currentEvent);

                    while (_parentViewModel.RecordedEvents.Count > 100)
                        _parentViewModel.RecordedEvents.RemoveAt(0);
                }
                else
                {
                    _currentEvent.AddToChain(new EventChainLink(s, handled, route));
                }
            };

            if (!Dispatcher.UIThread.CheckAccess())
                Dispatcher.UIThread.Post(handler);
            else
                handler();
        }
        
        private void HandleRouteFinished(RoutedEventArgs e)
        {
            if (!_isRegistered || IsEnabled == false)
                return;
            if (e.Source is IVisual v && BelongsToDevTool(v))
                return;

            var s = e.Source;
            var handled = e.Handled;
            var route = e.Route;

            void handler()
            {
                if (_currentEvent != null && handled)
                {
                    var linkIndex = _currentEvent.EventChain.Count - 1;
                    var link = _currentEvent.EventChain[linkIndex];

                    link.Handled = true;
                    _currentEvent.HandledBy = link;
                }
            }

            if (!Dispatcher.UIThread.CheckAccess())
                Dispatcher.UIThread.Post(handler);
            else
                handler();
        }

        private static bool BelongsToDevTool(IVisual v)
        {
            var current = v;

            while (current != null)
            {
                if (current is MainView || current is MainWindow)
                {
                    return true;
                }

                current = current.VisualParent;
            }

            return false;
        }
    }
}
