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

using System;
using System.Windows.Input;
using Microsoft.Maui.Graphics;
using SkiaSharp;

namespace Microsoft.Maui.Platform;

/// <summary>
/// A pull-to-refresh container view.
/// </summary>
public class SkiaRefreshView : SkiaLayoutView
{
    private SkiaView? _content;
    private bool _isRefreshing = false;
    private float _pullDistance = 0f;
    private float _refreshThreshold = 80f;
    private bool _isPulling = false;
    private float _pullStartY;
    private float _spinnerRotation = 0f;
    private DateTime _lastSpinnerUpdate;

    // SKColor fields for rendering
    private SKColor _refreshColorSK = SkiaTheme.PrimarySK;
    private SKColor _refreshBackgroundColorSK = SKColors.White;

    // MAUI Color backing fields
    private Color _refreshColor = Color.FromRgb(33, 150, 243);
    private Color _refreshBackgroundColor = Colors.White;

    /// <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 or sets whether the view is currently refreshing.
    /// </summary>
    public bool IsRefreshing
    {
        get => _isRefreshing;
        set
        {
            if (_isRefreshing != value)
            {
                _isRefreshing = value;
                if (!value)
                {
                    _pullDistance = 0;
                }
                Invalidate();
            }
        }
    }

    /// <summary>
    /// Gets or sets the pull distance required to trigger refresh.
    /// </summary>
    public float RefreshThreshold
    {
        get => _refreshThreshold;
        set => _refreshThreshold = Math.Max(40, value);
    }

    /// <summary>
    /// Gets or sets the refresh indicator color.
    /// </summary>
    public Color RefreshColor
    {
        get => _refreshColor;
        set
        {
            _refreshColor = value;
            _refreshColorSK = value.ToSKColor();
            Invalidate();
        }
    }

    /// <summary>
    /// Gets or sets the background color of the refresh indicator.
    /// </summary>
    public Color RefreshBackgroundColor
    {
        get => _refreshBackgroundColor;
        set
        {
            _refreshBackgroundColor = value;
            _refreshBackgroundColorSK = value.ToSKColor();
            Invalidate();
        }
    }

    /// <summary>
    /// Gets or sets the command to execute when refresh is triggered.
    /// </summary>
    public ICommand? Command { get; set; }

    /// <summary>
    /// Gets or sets the command parameter.
    /// </summary>
    public object? CommandParameter { get; set; }

    /// <summary>
    /// Event raised when refresh is triggered.
    /// </summary>
    public event EventHandler? Refreshing;

    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)
        {
            float offset = _isRefreshing ? _refreshThreshold : _pullDistance;
            var contentBounds = new Rect(bounds.Left, bounds.Top + offset, bounds.Width, bounds.Height);
            _content.Arrange(contentBounds);
        }
        return bounds;
    }

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

        // Draw refresh indicator
        float indicatorY = bounds.Top + (_isRefreshing ? _refreshThreshold : _pullDistance) / 2;

        if (_pullDistance > 0 || _isRefreshing)
        {
            DrawRefreshIndicator(canvas, bounds.MidX, indicatorY);
        }

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

        canvas.Restore();
    }

    private void DrawRefreshIndicator(SKCanvas canvas, float x, float y)
    {
        float size = 36f;
        float progress = Math.Clamp(_pullDistance / _refreshThreshold, 0f, 1f);

        // Draw background circle
        using var bgPaint = new SKPaint
        {
            Color = _refreshBackgroundColorSK,
            Style = SKPaintStyle.Fill,
            IsAntialias = true
        };

        // Add shadow
        bgPaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 2, 4, 4, SkiaTheme.Shadow25SK);
        canvas.DrawCircle(x, y, size / 2, bgPaint);

        // Draw spinner
        using var spinnerPaint = new SKPaint
        {
            Color = _refreshColorSK,
            Style = SKPaintStyle.Stroke,
            StrokeWidth = 3,
            IsAntialias = true,
            StrokeCap = SKStrokeCap.Round
        };

        if (_isRefreshing)
        {
            // Animate spinner
            var now = DateTime.UtcNow;
            float elapsed = (float)(now - _lastSpinnerUpdate).TotalMilliseconds;
            _spinnerRotation += elapsed * 0.36f; // 360 degrees per second
            _lastSpinnerUpdate = now;

            canvas.Save();
            canvas.Translate(x, y);
            canvas.RotateDegrees(_spinnerRotation);

            // Draw spinning arc
            using var path = new SKPath();
            var rect = new SKRect(-size / 3, -size / 3, size / 3, size / 3);
            path.AddArc(rect, 0, 270);
            canvas.DrawPath(path, spinnerPaint);

            canvas.Restore();

            Invalidate(); // Continue animation
        }
        else
        {
            // Draw progress arc
            canvas.Save();
            canvas.Translate(x, y);

            using var path = new SKPath();
            var rect = new SKRect(-size / 3, -size / 3, size / 3, size / 3);
            float sweepAngle = 270 * progress;
            path.AddArc(rect, -90, sweepAngle);
            canvas.DrawPath(path, spinnerPaint);

            canvas.Restore();
        }
    }

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

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

        return this;
    }

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

        // Check if content is at top (can pull to refresh)
        bool canPull = true;
        if (_content is SkiaScrollView scrollView)
        {
            canPull = scrollView.ScrollY <= 0;
        }

        if (canPull)
        {
            _isPulling = true;
            _pullStartY = e.Y;
            _pullDistance = 0;
        }

        base.OnPointerPressed(e);
    }

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

        float delta = e.Y - _pullStartY;
        if (delta > 0)
        {
            // Apply resistance
            _pullDistance = delta * 0.5f;
            _pullDistance = Math.Min(_pullDistance, _refreshThreshold * 1.5f);
            Invalidate();
            e.Handled = true;
        }
        else
        {
            _pullDistance = 0;
        }

        base.OnPointerMoved(e);
    }

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

        _isPulling = false;

        if (_pullDistance >= _refreshThreshold)
        {
            _isRefreshing = true;
            _pullDistance = _refreshThreshold;
            _lastSpinnerUpdate = DateTime.UtcNow;
            Refreshing?.Invoke(this, EventArgs.Empty);
            Command?.Execute(CommandParameter);
        }
        else
        {
            _pullDistance = 0;
        }

        Invalidate();
        base.OnPointerReleased(e);
    }
}
