using Snake.Core;

namespace Snake.Avalonia.Views;

/// <summary>
/// Interpolation helpers for smooth snake animation.
/// </summary>
public static class AnimationHelper
{
    /// <summary>
    /// Ease-out cubic for smooth deceleration (head overshoot feel).
    /// </summary>
    public static double EaseOutCubic(double t)
    {
        t = Math.Clamp(t, 0.0, 1.0);
        return 1.0 - Math.Pow(1.0 - t, 3);
    }

    /// <summary>
    /// Ease-in-out quad for general smooth interpolation.
    /// </summary>
    public static double EaseInOutQuad(double t)
    {
        t = Math.Clamp(t, 0.0, 1.0);
        return t < 0.5 ? 2.0 * t * t : 1.0 - Math.Pow(-2.0 * t + 2.0, 2) / 2.0;
    }

    /// <summary>
    /// Interpolates between two positions with given t value.
    /// Returns the visual position for rendering.
    /// </summary>
    public static (double X, double Y) InterpolatePosition(
        Position from,
        Position to,
        double t,
        double cellSize,
        bool useEasing = false)
    {
        var eased = useEasing ? EaseOutCubic(t) : t;
        var x = from.X + (to.X - from.X) * eased;
        var y = from.Y + (to.Y - from.Y) * eased;
        return (x * cellSize + cellSize * 0.5, y * cellSize + cellSize * 0.5);
    }

    /// <summary>
    /// Maps a current segment index to its previous position for interpolation.
    /// PreviousSegments is the full snapshot before Move().
    /// Segments count may differ from PreviousSegments if the snake grew.
    /// </summary>
    public static Position GetPreviousPosition(
        int segmentIndex,
        IReadOnlyList<Position> currentSegments,
        IReadOnlyList<Position> previousSegments,
        Direction direction)
    {
        if (previousSegments.Count == 0)
            return currentSegments[segmentIndex];

        // Head (index 0) always interpolates from the old head
        if (segmentIndex == 0)
            return previousSegments[0];

        // For body segments, the mapping depends on whether snake grew
        var grew = currentSegments.Count > previousSegments.Count;

        if (grew)
        {
            // When growing: all old segments kept their positions,
            // new head was added at front. Body segments shifted by 1.
            // current[i] (i>=1) = old[i-1]
            var prevIdx = segmentIndex - 1;
            if (prevIdx < previousSegments.Count)
                return previousSegments[prevIdx];
        }
        else
        {
            // When not growing: tail removed, new head added.
            // current[0] = new head, current[1] = old head, … current[N-2] = old[N-3]
            // BUT the last segment (tail): it was old[N-1] (the removed tail's position)
            // because visually it slides from where the old tail was.
            var isLast = segmentIndex == currentSegments.Count - 1;
            var prevIdx = isLast ? segmentIndex : segmentIndex - 1;
            if (prevIdx < previousSegments.Count)
                return previousSegments[prevIdx];
        }

        // Fallback: return current position (no interpolation)
        return currentSegments[segmentIndex];
    }

    /// <summary>
    /// Computes a pulsating radius for food glow effect.
    /// </summary>
    public static double PulsateRadius(double baseRadius, double elapsedSeconds, double amplitude = 0.15, double frequency = 2.5)
    {
        return baseRadius * (1.0 + amplitude * Math.Sin(elapsedSeconds * Math.PI * 2.0 * frequency));
    }

    /// <summary>
    /// Computes opacity for score popup fade-out animation.
    /// </summary>
    public static double FadeOutOpacity(double elapsedSeconds, double durationSeconds = 1.0)
    {
        var t = Math.Clamp(elapsedSeconds / durationSeconds, 0.0, 1.0);
        return 1.0 - t;
    }
}
