using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Snake.Core;

namespace Snake.Avalonia.Views;

/// <summary>
/// Custom control that renders the snake, food, and grid using Avalonia's DrawingContext.
/// All rendering is done in the Render override for maximum performance at 60fps.
/// </summary>
public sealed class SnakeRenderer : Control
{
    // Grid
    public int BoardWidth { get; set; } = 20;
    public int BoardHeight { get; set; } = 15;
    public double CellSize { get; set; } = 28;

    // Snake state
    public IReadOnlyList<Position>? Segments { get; set; }
    public IReadOnlyList<Position>? PreviousSegments { get; set; }
    public Direction SnakeDirection { get; set; } = Direction.Right;

    // Food state
    public Position FoodPosition { get; set; }
    public bool FoodJustEaten { get; set; }

    // Animation
    public double InterpolationT { get; set; }
    public double TotalElapsedSeconds { get; set; }
    public double FoodEatenElapsed { get; set; }

    // Score popup
    public int ScorePopup { get; set; }
    public double ScorePopupElapsed { get; set; }
    public Position? ScorePopupPosition { get; set; }

    // Colors
    private static readonly IBrush BackgroundBrush = Brush.Parse("#0D1117");
    private static readonly IBrush GridDotBrush = Brush.Parse("#1E3A3A");
    private static readonly ISolidColorBrush SnakeHeadBrush = (ISolidColorBrush)Brush.Parse("#14B8A6");
    private static readonly ISolidColorBrush SnakeBodyBrush = (ISolidColorBrush)Brush.Parse("#0D9488");
    private static readonly ISolidColorBrush SnakeTailBrush = (ISolidColorBrush)Brush.Parse("#0F766E");
    private static readonly ISolidColorBrush EyeWhiteBrush = Brushes.White;
    private static readonly ISolidColorBrush EyePupilBrush = (ISolidColorBrush)Brush.Parse("#0D1117");
    private static readonly ISolidColorBrush FoodBrush = (ISolidColorBrush)Brush.Parse("#EF4444");
    private static readonly ISolidColorBrush FoodGlowBrush = (ISolidColorBrush)Brush.Parse("#F87171");
    private static readonly ISolidColorBrush FoodHighlightBrush = (ISolidColorBrush)Brush.Parse("#FCA5A5");
    private static readonly ISolidColorBrush ScorePopupBrush = (ISolidColorBrush)Brush.Parse("#FBBF24");
    private static readonly ISolidColorBrush TrailBrush = (ISolidColorBrush)Brush.Parse("#0F766E");
    private static readonly IPen GridDotPen = new Pen(GridDotBrush, 2);
    private static readonly IPen ScorePopupPen = new Pen(ScorePopupBrush, 1);

    static SnakeRenderer()
    {
        // We invalidate manually from the render timer, no AffectsRender needed
    }

    public override void Render(DrawingContext context)
    {
        var width = Bounds.Width;
        var height = Bounds.Height;

        // Background
        context.FillRectangle(BackgroundBrush, new Rect(0, 0, width, height));

        // Draw grid (subtle dots)
        DrawGrid(context, width, height);

        // Draw trail
        if (Segments != null && PreviousSegments != null && PreviousSegments.Count > 0)
            DrawTrail(context);

        // Draw food
        DrawFood(context);

        // Draw snake
        if (Segments != null && Segments.Count > 0)
            DrawSnake(context);

        // Draw score popup
        if (ScorePopup > 0 && ScorePopupElapsed < 1.5)
            DrawScorePopup(context);
    }

    private void DrawGrid(DrawingContext context, double width, double height)
    {
        for (var x = 0; x < BoardWidth; x++)
        {
            for (var y = 0; y < BoardHeight; y++)
            {
                var cx = x * CellSize + CellSize * 0.5;
                var cy = y * CellSize + CellSize * 0.5;
                context.DrawEllipse(GridDotBrush, null, new Point(cx, cy), 1.0, 1.0);
            }
        }

        // Subtle border
        var borderRect = new Rect(0, 0, BoardWidth * CellSize, BoardHeight * CellSize);
        context.DrawRectangle(new Pen(Brush.Parse("#1E3A3A"), 1), borderRect);
    }

    private void DrawTrail(DrawingContext context)
    {
        if (Segments == null || PreviousSegments == null || PreviousSegments.Count == 0)
            return;

        // Draw a subtle trail for the last few segments behind the snake
        var trailLength = Math.Min(3, PreviousSegments.Count);
        for (var i = 0; i < trailLength; i++)
        {
            var prevIdx = PreviousSegments.Count - 1 - i;
            if (prevIdx < 0) break;

            var pos = PreviousSegments[prevIdx];
            var centerX = pos.X * CellSize + CellSize * 0.5;
            var centerY = pos.Y * CellSize + CellSize * 0.5;
            var radius = CellSize * 0.3 * (1.0 - i * 0.3);
            var opacity = 0.15 - i * 0.05;
            if (opacity <= 0) break;

            context.DrawEllipse(
                new SolidColorBrush(TrailBrush.Color, opacity),
                null,
                new Point(centerX, centerY),
                radius, radius);
        }
    }

    private void DrawSnake(DrawingContext context)
    {
        if (Segments == null || Segments.Count == 0) return;

        var t = InterpolationT;
        var segmentsList = Segments;
        var prevList = PreviousSegments;

        // Build path for body (all segments except head)
        var bodyGeometry = new StreamGeometry();
        using (var ctx = bodyGeometry.Open())
        {
            for (var i = segmentsList.Count - 1; i >= 1; i--)
            {
                var current = segmentsList[i];
                var (vx, vy) = GetVisualPosition(current, i, t);

                if (i == segmentsList.Count - 1 && segmentsList.Count > 2)
                {
                    ctx.BeginFigure(new Point(vx, vy), false);
                }
                else if (i == segmentsList.Count - 1)
                {
                    ctx.BeginFigure(new Point(vx, vy), false);
                }

                // Draw line segment
                if (i > 1)
                {
                    var (px, py) = GetVisualPosition(segmentsList[i - 1], i - 1, t);
                    ctx.LineTo(new Point(px, py));
                }
            }

            if (segmentsList.Count >= 2)
            {
                var (hx, hy) = GetVisualPosition(segmentsList[0], 0, t);
                ctx.LineTo(new Point(hx, hy));
            }
        }

        // Calculate body thickness based on segment position (tail thinner)
        var maxThickness = CellSize * 0.7;
        var minThickness = CellSize * 0.35;

        // Draw body as a thick stroke
        var bodyThickness = Math.Max(minThickness, maxThickness * 0.85);
        var bodyPen = new Pen(SnakeBodyBrush, bodyThickness, lineCap: PenLineCap.Round, lineJoin: PenLineJoin.Round);
        context.DrawGeometry(null, bodyPen, bodyGeometry);

        // Draw a slightly thinner inner stroke for gradient effect
        if (segmentsList.Count >= 2)
        {
            var innerPen = new Pen(
                new SolidColorBrush(SnakeHeadBrush.Color, 0.4),
                bodyThickness * 0.5,
                lineCap: PenLineCap.Round,
                lineJoin: PenLineJoin.Round);
            context.DrawGeometry(null, innerPen, bodyGeometry);
        }

        // Draw head
        DrawHead(context, t);

        // Draw eyes
        DrawEyes(context, t);
    }

    private void DrawHead(DrawingContext context, double t)
    {
        if (Segments == null || Segments.Count == 0) return;

        var (cx, cy) = GetVisualPosition(Segments[0], 0, t);
        var dir = SnakeDirection;
        var headSize = CellSize * 0.45;

        // Rounded triangle/arrow pointing in movement direction
        var headGeo = new StreamGeometry();
        using (var ctx = headGeo.Open())
        {
            Point tip, left, right;
            switch (dir)
            {
                case Direction.Right:
                    tip = new Point(cx + headSize, cy);
                    left = new Point(cx - headSize * 0.7, cy - headSize * 0.7);
                    right = new Point(cx - headSize * 0.7, cy + headSize * 0.7);
                    break;
                case Direction.Left:
                    tip = new Point(cx - headSize, cy);
                    left = new Point(cx + headSize * 0.7, cy - headSize * 0.7);
                    right = new Point(cx + headSize * 0.7, cy + headSize * 0.7);
                    break;
                case Direction.Up:
                    tip = new Point(cx, cy - headSize);
                    left = new Point(cx + headSize * 0.7, cy + headSize * 0.7);
                    right = new Point(cx - headSize * 0.7, cy + headSize * 0.7);
                    break;
                case Direction.Down:
                    tip = new Point(cx, cy + headSize);
                    left = new Point(cx - headSize * 0.7, cy - headSize * 0.7);
                    right = new Point(cx + headSize * 0.7, cy - headSize * 0.7);
                    break;
                default:
                    tip = new Point(cx + headSize, cy);
                    left = new Point(cx - headSize * 0.7, cy - headSize * 0.7);
                    right = new Point(cx - headSize * 0.7, cy + headSize * 0.7);
                    break;
            }

            ctx.BeginFigure(tip, true);
            ctx.LineTo(left);
            ctx.LineTo(right);
            ctx.LineTo(tip); // Close back
        }

        // Fill head with gradient
        var headFill = new SolidColorBrush(SnakeHeadBrush.Color);
        context.DrawGeometry(headFill, null, headGeo);

        // Highlight edge
        var headEdgePen = new Pen(
            new SolidColorBrush(SnakeHeadBrush.Color, 0.8),
            1.5,
            lineCap: PenLineCap.Round,
            lineJoin: PenLineJoin.Round);
        context.DrawGeometry(null, headEdgePen, headGeo);
    }

    private void DrawEyes(DrawingContext context, double t)
    {
        if (Segments == null || Segments.Count == 0) return;

        var (cx, cy) = GetVisualPosition(Segments[0], 0, t);
        var dir = SnakeDirection;

        // Eye offset from center based on direction
        var eyeOffset = CellSize * 0.22;
        var eyeRadius = CellSize * 0.1;
        var pupilRadius = eyeRadius * 0.55;

        (double ex1, double ey1, double ex2, double ey2) = dir switch
        {
            Direction.Right => (cx + eyeOffset * 0.3, cy - eyeOffset, cx + eyeOffset * 0.3, cy + eyeOffset),
            Direction.Left => (cx - eyeOffset * 0.3, cy - eyeOffset, cx - eyeOffset * 0.3, cy + eyeOffset),
            Direction.Up => (cx - eyeOffset, cy - eyeOffset * 0.3, cx + eyeOffset, cy - eyeOffset * 0.3),
            Direction.Down => (cx - eyeOffset, cy + eyeOffset * 0.3, cx + eyeOffset, cy + eyeOffset * 0.3),
            _ => (cx + eyeOffset * 0.3, cy - eyeOffset, cx + eyeOffset * 0.3, cy + eyeOffset),
        };

        // White of eyes
        context.DrawEllipse(EyeWhiteBrush, null, new Point(ex1, ey1), eyeRadius, eyeRadius);
        context.DrawEllipse(EyeWhiteBrush, null, new Point(ex2, ey2), eyeRadius, eyeRadius);

        // Pupils (slightly offset towards movement direction)
        var pupilShiftX = dir switch { Direction.Right => 1.5, Direction.Left => -1.5, _ => 0 };
        var pupilShiftY = dir switch { Direction.Down => 1.5, Direction.Up => -1.5, _ => 0 };

        context.DrawEllipse(EyePupilBrush, null,
            new Point(ex1 + pupilShiftX, ey1 + pupilShiftY), pupilRadius, pupilRadius);
        context.DrawEllipse(EyePupilBrush, null,
            new Point(ex2 + pupilShiftX, ey2 + pupilShiftY), pupilRadius, pupilRadius);
    }

    private void DrawFood(DrawingContext context)
    {
        var pos = FoodPosition;
        var cx = pos.X * CellSize + CellSize * 0.5;
        var cy = pos.Y * CellSize + CellSize * 0.5;
        var baseRadius = CellSize * 0.38;

        // Pulsating glow
        var glowRadius = AnimationHelper.PulsateRadius(baseRadius * 1.6, TotalElapsedSeconds, 0.12, 2.5);
        var glowBrush = new SolidColorBrush(FoodGlowBrush.Color, 0.25);
        context.DrawEllipse(glowBrush, null, new Point(cx, cy), glowRadius, glowRadius);

        // Outer glow ring
        var outerGlow = new SolidColorBrush(FoodGlowBrush.Color, 0.15);
        context.DrawEllipse(outerGlow, null, new Point(cx, cy), glowRadius * 1.3, glowRadius * 1.3);

        // Main food circle
        var foodRadius = AnimationHelper.PulsateRadius(baseRadius, TotalElapsedSeconds, 0.06, 2.5);
        context.DrawEllipse(FoodBrush, null, new Point(cx, cy), foodRadius, foodRadius);

        // Highlight shine
        var highlightX = cx - foodRadius * 0.35;
        var highlightY = cy - foodRadius * 0.35;
        var highlightRadius = foodRadius * 0.3;
        context.DrawEllipse(FoodHighlightBrush, null,
            new Point(highlightX, highlightY), highlightRadius, highlightRadius);

        // Small green leaf/stem on top
        var stemBaseX = cx;
        var stemBaseY = cy - foodRadius;
        var stemTipX = cx + foodRadius * 0.15;
        var stemTipY = cy - foodRadius - CellSize * 0.18;

        var stemGeo = new StreamGeometry();
        using (var ctx = stemGeo.Open())
        {
            ctx.BeginFigure(new Point(stemBaseX - 1.5, stemBaseY), false);
            ctx.LineTo(new Point(stemTipX, stemTipY));
            ctx.LineTo(new Point(stemBaseX + 2, stemBaseY + 1));
            ctx.LineTo(new Point(stemBaseX - 1, stemBaseY + 2));
        }
        context.DrawGeometry(Brush.Parse("#22C55E"), null, stemGeo);

        // Small leaf
        var leafGeo = new StreamGeometry();
        using (var ctx = leafGeo.Open())
        {
            ctx.BeginFigure(new Point(stemTipX, stemTipY), false);
            ctx.LineTo(new Point(stemTipX + CellSize * 0.1, stemTipY - CellSize * 0.06));
            ctx.LineTo(new Point(stemTipX + CellSize * 0.05, stemTipY + CellSize * 0.02));
        }
        context.DrawGeometry(Brush.Parse("#22C55E"), null, leafGeo);
    }

    private void DrawScorePopup(DrawingContext context)
    {
        if (!ScorePopupPosition.HasValue) return;

        var pos = ScorePopupPosition.Value;
        var cx = pos.X * CellSize + CellSize * 0.5;
        var cy = pos.Y * CellSize + CellSize * 0.5;
        var opacity = AnimationHelper.FadeOutOpacity(ScorePopupElapsed, 1.2);
        if (opacity <= 0) return;

        var offsetY = ScorePopupElapsed * CellSize * -2.0; // Float upward
        var y = cy + offsetY;

        var text = $"+{ScorePopup}";
        var formattedText = new FormattedText(
            text,
            System.Globalization.CultureInfo.InvariantCulture,
            FlowDirection.LeftToRight,
            new Typeface("Inter, Arial, sans-serif"),
            CellSize * 0.7,
            new SolidColorBrush(ScorePopupBrush.Color, opacity * 0.9));

        var textX = cx - formattedText.Width / 2;
        context.DrawText(formattedText, new Point(textX, y - formattedText.Height / 2));
    }

    private (double X, double Y) GetVisualPosition(Position pos, int segmentIndex, double t)
    {
        if (Segments == null)
            return (pos.X * CellSize + CellSize * 0.5, pos.Y * CellSize + CellSize * 0.5);

        var from = PreviousSegments != null && PreviousSegments.Count > 0
            ? AnimationHelper.GetPreviousPosition(segmentIndex, Segments, PreviousSegments, SnakeDirection)
            : pos;

        var useEasing = segmentIndex == 0; // Head gets easing for snappy feel
        return AnimationHelper.InterpolatePosition(from, pos, t, CellSize, useEasing);
    }
}
