using Lantean.QBTMud.Interop;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using MudBlazor;
using MudBlazor.Utilities;
namespace Lantean.QBTMud.Components.UI
{
    // This is a very hacky approach but works for now.
    // This needs to inherit from MudMenu because MudMenuItem needs a MudMenu passed to it to control the close of the menu when an item is clicked.
    // MudPopover isn't ideal for this because that is designed to be used relative to an activator which in these cases it isn't.
    // Ideally this should be changed to use something like the way the DialogService works.
    // Or - rework this to have a hidden MudMenu and hook into the OpenChanged event to monitor when the MudMenuItem closes it.
    public partial class ContextMenu : MudComponentBase
    {
        private bool _open;
        private bool _showChildren;
        private string? _popoverStyle;
        private string? _id;
        private double _x;
        private double _y;
        private bool _isResized = false;
        private const double _diff = 64;
        private string Id
        {
            get
            {
                _id ??= Guid.NewGuid().ToString();
                return _id;
            }
        }
        [Inject]
        public IJSRuntime JSRuntime { get; set; } = default!;
        [Inject]
        public IPopoverService PopoverService { get; set; } = default!;
        /// 
        /// If true, compact vertical padding will be applied to all menu items.
        /// 
        [Parameter]
        [Category(CategoryTypes.Menu.PopupAppearance)]
        public bool Dense { get; set; }
        /// 
        /// Set to true if you want to prevent page from scrolling when the menu is open
        /// 
        [Parameter]
        [Category(CategoryTypes.Menu.PopupAppearance)]
        public bool LockScroll { get; set; }
        /// 
        /// If true, the list menu will be same width as the parent.
        /// 
        [Parameter]
        [Category(CategoryTypes.Menu.PopupAppearance)]
        public bool FullWidth { get; set; }
        /// 
        /// Sets the max height the menu can have when open.
        /// 
        [Parameter]
        [Category(CategoryTypes.Menu.PopupAppearance)]
        public int? MaxHeight { get; set; }
        /// 
        /// Set the anchor origin point to determine where the popover will open from.
        /// 
        [Parameter]
        [Category(CategoryTypes.Menu.PopupAppearance)]
        public Origin AnchorOrigin { get; set; } = Origin.TopLeft;
        /// 
        /// Sets the transform origin point for the popover.
        /// 
        [Parameter]
        [Category(CategoryTypes.Menu.PopupAppearance)]
        public Origin TransformOrigin { get; set; } = Origin.TopLeft;
        /// 
        /// If true, menu will be disabled.
        /// 
        [Parameter]
        [Category(CategoryTypes.Menu.Behavior)]
        public bool Disabled { get; set; }
        /// 
        /// Gets or sets whether to show a ripple effect when the user clicks the button. Default is true.
        /// 
        [Parameter]
        [Category(CategoryTypes.Menu.Appearance)]
        public bool Ripple { get; set; } = true;
        /// 
        /// Determines whether the component has a drop-shadow. Default is true
        /// 
        [Parameter]
        [Category(CategoryTypes.Menu.Appearance)]
        public bool DropShadow { get; set; } = true;
        /// 
        /// Add menu items here
        /// 
        [Parameter]
        [Category(CategoryTypes.Menu.PopupBehavior)]
        public RenderFragment? ChildContent { get; set; }
        /// 
        /// Fired when the menu  property changes.
        /// 
        [Parameter]
        [Category(CategoryTypes.Menu.PopupBehavior)]
        public EventCallback OpenChanged { get; set; }
        [Parameter]
        public int AdjustmentX { get; set; }
        [Parameter]
        public int AdjustmentY { get; set; }
        protected MudMenu? FakeMenu { get; set; }
        protected void FakeOpenChanged(bool value)
        {
            if (!value)
            {
                _open = false;
            }
            StateHasChanged();
        }
        /// 
        /// Opens the menu.
        /// 
        /// 
        /// The arguments of the calling mouse/pointer event.
        /// 
        public async Task OpenMenuAsync(EventArgs args)
        {
            if (Disabled)
            {
                return;
            }
            // long press on iOS triggers selection, so clear it
            await JSRuntime.ClearSelection();
            if (args is not LongPressEventArgs)
            {
                _showChildren = true;
            }
            _open = true;
            _isResized = false;
            StateHasChanged();
            var (x, y) = GetPositionFromArgs(args);
            _x = x;
            _y = y;
            SetPopoverStyle(x, y);
            StateHasChanged();
            await OpenChanged.InvokeAsync(_open);
            // long press on iOS triggers selection, so clear it
            await JSRuntime.ClearSelection();
            if (args is LongPressEventArgs)
            {
                await Task.Delay(1000);
                _showChildren = true;
            }
        }
        /// 
        /// Closes the menu.
        /// 
        public Task CloseMenuAsync()
        {
            _open = false;
            _popoverStyle = null;
            StateHasChanged();
            return OpenChanged.InvokeAsync(_open);
        }
        private void SetPopoverStyle(double x, double y)
        {
            _popoverStyle = $"margin-top: {y.ToPx()}; margin-left: {x.ToPx()};";
        }
        /// 
        /// Toggle the visibility of the menu.
        /// 
        public async Task ToggleMenuAsync(EventArgs args)
        {
            if (Disabled)
            {
                return;
            }
            if (_open)
            {
                await CloseMenuAsync();
            }
            else
            {
                await OpenMenuAsync(args);
            }
        }
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (!_isResized)
            {
                await DeterminePosition();
            }
        }
        private async Task DeterminePosition()
        {
            var mainContentSize = await JSRuntime.GetInnerDimensions(".mud-main-content");
            double? contextMenuHeight = null;
            double? contextMenuWidth = null;
            var popoverHolder = PopoverService.ActivePopovers.FirstOrDefault(p => p.UserAttributes.ContainsKey("tracker") && (string?)p.UserAttributes["tracker"] == Id);
            var popoverSize = await JSRuntime.GetBoundingClientRect($"#popovercontent-{popoverHolder?.Id}");
            if (popoverSize.Height > 0)
            {
                contextMenuHeight = popoverSize.Height;
                contextMenuWidth = popoverSize.Width;
            }
            else
            {
                return;
            }
            // the bottom position of the popover will be rendered off screen
            if (_y - _diff + contextMenuHeight.Value >= mainContentSize.Height)
            {
                // adjust the top of the context menu
                var overshoot = Math.Abs(mainContentSize.Height - (_y - _diff + contextMenuHeight.Value));
                _y -= overshoot;
                if (_y - _diff + contextMenuHeight >= mainContentSize.Height)
                {
                    MaxHeight = (int)(mainContentSize.Height - _y + _diff);
                }
            }
            if (_x + contextMenuWidth.Value > mainContentSize.Width)
            {
                var overshoot = Math.Abs(mainContentSize.Width - (_x + contextMenuWidth.Value));
                _x -= overshoot;
            }
            SetPopoverStyle(_x, _y);
            _isResized = true;
            await InvokeAsync(StateHasChanged);
        }
        private (double x, double y) GetPositionFromArgs(EventArgs eventArgs)
        {
            double x, y;
            if (eventArgs is MouseEventArgs mouseEventArgs)
            {
                x = mouseEventArgs.ClientX;
                y = mouseEventArgs.ClientY;
            }
            else if (eventArgs is LongPressEventArgs longPressEventArgs)
            {
                x = longPressEventArgs.ClientX;
                y = longPressEventArgs.ClientY;
            }
            else
            {
                throw new NotSupportedException("Invalid eventArgs type.");
            }
            return (x + AdjustmentX, y + AdjustmentY);
        }
    }
}