// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using SkiaSharp;

namespace Microsoft.Maui.Platform;

/// <summary>
/// A view that supports swipe gestures to reveal actions.
/// </summary>
public class SkiaSwipeView : SkiaLayoutView
{
    private SkiaView? _content;
    private readonly List<SwipeItem> _leftItems = new();
    private readonly List<SwipeItem> _rightItems = new();
    private readonly List<SwipeItem> _topItems = new();
    private readonly List<SwipeItem> _bottomItems = new();

    private float _swipeOffset = 0f;
    private SwipeDirection _activeDirection = SwipeDirection.None;
    private bool _isSwiping = false;
    private float _swipeStartX;
    private float _swipeStartY;
    private float _swipeStartOffset;
    private bool _isOpen = false;

    private const float SwipeThreshold = 60f;
    private const float VelocityThreshold = 500f;
    private float _velocity;
    private DateTime _lastMoveTime;
    private float _lastMovePosition;

    /// <summary>
    /// Gets or sets the content view.
    /// </summary>
    public SkiaView? Content
    {
        get => _content;
        set
        {
            if (_content != value)
            {
                if (_content != null)
                {
                    RemoveChild(_content);
                }

                _content = value;

                if (_content != null)
                {
                    AddChild(_content);
                }

                InvalidateMeasure();
                Invalidate();
            }
        }
    }

    /// <summary>
    /// Gets the left swipe items.
    /// </summary>
    public IList<SwipeItem> LeftItems => _leftItems;

    /// <summary>
    /// Gets the right swipe items.
    /// </summary>
    public IList<SwipeItem> RightItems => _rightItems;

    /// <summary>
    /// Gets the top swipe items.
    /// </summary>
    public IList<SwipeItem> TopItems => _topItems;

    /// <summary>
    /// Gets the bottom swipe items.
    /// </summary>
    public IList<SwipeItem> BottomItems => _bottomItems;

    /// <summary>
    /// Gets or sets the swipe mode.
    /// </summary>
    public SwipeMode Mode { get; set; } = SwipeMode.Reveal;

    /// <summary>
    /// Gets or sets the left swipe threshold.
    /// </summary>
    public float LeftSwipeThreshold { get; set; } = 100f;

    /// <summary>
    /// Gets or sets the right swipe threshold.
    /// </summary>
    public float RightSwipeThreshold { get; set; } = 100f;

    /// <summary>
    /// Event raised when swipe is started.
    /// </summary>
    public event EventHandler<SwipeStartedEventArgs>? SwipeStarted;

    /// <summary>
    /// Event raised when swipe ends.
    /// </summary>
    public event EventHandler<SwipeEndedEventArgs>? SwipeEnded;

    /// <summary>
    /// Opens the swipe view in the specified direction.
    /// </summary>
    public void Open(SwipeDirection direction)
    {
        _activeDirection = direction;
        _isOpen = true;

        float targetOffset = direction switch
        {
            SwipeDirection.Left => -RightSwipeThreshold,
            SwipeDirection.Right => LeftSwipeThreshold,
            _ => 0
        };

        AnimateTo(targetOffset);
    }

    /// <summary>
    /// Closes the swipe view.
    /// </summary>
    public void Close()
    {
        _isOpen = false;
        AnimateTo(0);
    }

    private void AnimateTo(float target)
    {
        // Simple animation - in production would use proper animation
        _swipeOffset = target;
        Invalidate();
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        if (_content != null)
        {
            _content.Measure(new Size(availableSize.Width, availableSize.Height));
        }
        return availableSize;
    }

    protected override Rect ArrangeOverride(Rect bounds)
    {
        if (_content != null)
        {
            var contentBounds = new Rect(
                bounds.Left + _swipeOffset,
                bounds.Top,
                bounds.Width,
                bounds.Height);
            _content.Arrange(contentBounds);
        }
        return bounds;
    }

    protected override void OnDraw(SKCanvas canvas, SKRect bounds)
    {
        canvas.Save();
        canvas.ClipRect(bounds);

        // Draw swipe items behind content
        if (_swipeOffset > 0)
        {
            DrawSwipeItems(canvas, bounds, _leftItems, true);
        }
        else if (_swipeOffset < 0)
        {
            DrawSwipeItems(canvas, bounds, _rightItems, false);
        }

        // Draw content
        _content?.Draw(canvas);

        canvas.Restore();
    }

    private void DrawSwipeItems(SKCanvas canvas, SKRect bounds, List<SwipeItem> items, bool isLeft)
    {
        if (items.Count == 0) return;

        float revealWidth = Math.Abs(_swipeOffset);
        float itemWidth = revealWidth / items.Count;

        for (int i = 0; i < items.Count; i++)
        {
            var item = items[i];
            float x = isLeft ? bounds.Left + i * itemWidth : bounds.Right - (items.Count - i) * itemWidth;

            var itemBounds = new SKRect(
                x,
                bounds.Top,
                x + itemWidth,
                bounds.Bottom);

            // Draw background
            using var bgPaint = new SKPaint
            {
                Color = item.GetBackgroundColorSK(),
                Style = SKPaintStyle.Fill
            };
            canvas.DrawRect(itemBounds, bgPaint);

            // Draw icon or text
            if (!string.IsNullOrEmpty(item.Text))
            {
                using var textPaint = new SKPaint
                {
                    Color = item.GetTextColorSK(),
                    TextSize = 14f,
                    IsAntialias = true,
                    TextAlign = SKTextAlign.Center
                };

                float textY = itemBounds.MidY + 5;
                canvas.DrawText(item.Text, itemBounds.MidX, textY, textPaint);
            }
        }
    }

    public override SkiaView? HitTest(float x, float y)
    {
        if (!IsVisible || !Bounds.Contains(x, y)) return null;

        // Check if hit is on swipe items
        if (_isOpen)
        {
            if (_swipeOffset > 0 && x < Bounds.Left + _swipeOffset)
            {
                return this; // Hit on left items
            }
            else if (_swipeOffset < 0 && x > Bounds.Right + _swipeOffset)
            {
                return this; // Hit on right items
            }
        }

        if (_content != null)
        {
            var hit = _content.HitTest(x, y);
            if (hit != null) return hit;
        }

        return this;
    }

    public override void OnPointerPressed(PointerEventArgs e)
    {
        if (!IsEnabled) return;

        // Check for swipe item tap when open
        if (_isOpen)
        {
            SwipeItem? tappedItem = null;

            if (_swipeOffset > 0)
            {
                int index = (int)((e.X - Bounds.Left) / (_swipeOffset / _leftItems.Count));
                if (index >= 0 && index < _leftItems.Count)
                {
                    tappedItem = _leftItems[index];
                }
            }
            else if (_swipeOffset < 0)
            {
                float itemWidth = Math.Abs(_swipeOffset) / _rightItems.Count;
                int index = (int)((e.X - (Bounds.Right + _swipeOffset)) / itemWidth);
                if (index >= 0 && index < _rightItems.Count)
                {
                    tappedItem = _rightItems[index];
                }
            }

            if (tappedItem != null)
            {
                tappedItem.OnInvoked();
                Close();
                e.Handled = true;
                return;
            }
        }

        _isSwiping = true;
        _swipeStartX = e.X;
        _swipeStartY = e.Y;
        _swipeStartOffset = _swipeOffset;
        _lastMovePosition = e.X;
        _lastMoveTime = DateTime.UtcNow;
        _velocity = 0;

        base.OnPointerPressed(e);
    }

    public override void OnPointerMoved(PointerEventArgs e)
    {
        if (!_isSwiping) return;

        float deltaX = e.X - _swipeStartX;
        float deltaY = e.Y - _swipeStartY;

        // Determine swipe direction
        if (_activeDirection == SwipeDirection.None)
        {
            if (Math.Abs(deltaX) > 10)
            {
                _activeDirection = deltaX > 0 ? SwipeDirection.Right : SwipeDirection.Left;
                SwipeStarted?.Invoke(this, new SwipeStartedEventArgs(_activeDirection));
            }
        }

        if (_activeDirection == SwipeDirection.Right || _activeDirection == SwipeDirection.Left)
        {
            _swipeOffset = _swipeStartOffset + deltaX;

            // Clamp offset based on available items
            float maxRight = _leftItems.Count > 0 ? LeftSwipeThreshold : 0;
            float maxLeft = _rightItems.Count > 0 ? -RightSwipeThreshold : 0;
            _swipeOffset = Math.Clamp(_swipeOffset, maxLeft, maxRight);

            // Calculate velocity
            var now = DateTime.UtcNow;
            float timeDelta = (float)(now - _lastMoveTime).TotalSeconds;
            if (timeDelta > 0)
            {
                _velocity = (e.X - _lastMovePosition) / timeDelta;
            }
            _lastMovePosition = e.X;
            _lastMoveTime = now;

            Invalidate();
            e.Handled = true;
        }

        base.OnPointerMoved(e);
    }

    public override void OnPointerReleased(PointerEventArgs e)
    {
        if (!_isSwiping) return;

        _isSwiping = false;

        // Determine final state
        bool shouldOpen = false;

        if (Math.Abs(_velocity) > VelocityThreshold)
        {
            // Use velocity
            shouldOpen = (_velocity > 0 && _leftItems.Count > 0) || (_velocity < 0 && _rightItems.Count > 0);
        }
        else
        {
            // Use threshold
            shouldOpen = Math.Abs(_swipeOffset) > SwipeThreshold;
        }

        if (shouldOpen)
        {
            if (_swipeOffset > 0)
            {
                Open(SwipeDirection.Right);
            }
            else
            {
                Open(SwipeDirection.Left);
            }
        }
        else
        {
            Close();
        }

        SwipeEnded?.Invoke(this, new SwipeEndedEventArgs(_activeDirection, _isOpen));
        _activeDirection = SwipeDirection.None;

        base.OnPointerReleased(e);
    }
}
