using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Theriapolis.Core;
using Theriapolis.Core.Entities;

namespace Theriapolis.Game.Rendering;

/// <summary>
/// Renders the player actor in either view. Both world-map and tactical use
/// the same camera (world-pixel space), but the sprite is counter-scaled by
/// 1/camera.Zoom so it stays a constant on-screen size at every zoom level.
/// Otherwise the marker would become tiny when zoomed out and screen-filling
/// at CAMERA_MAX_ZOOM.
/// </summary>
public sealed class PlayerSprite : IDisposable
{
    private readonly GraphicsDevice _gd;
    private Texture2D _arrow = null!;
    private Texture2D _ring  = null!;
    private bool _disposed;

    // Texture size = target on-screen size. Convenient because the
    // counter-scale formula reduces to (1 / camera.Zoom) at draw time.
    private const int MarkerPx = C.PLAYER_MARKER_SCREEN_PX;

    public PlayerSprite(GraphicsDevice gd)
    {
        _gd = gd;
        BuildTextures();
    }

    private void BuildTextures()
    {
        // Filled arrow — orientation comes from sprite rotation at draw time.
        int s = MarkerPx;
        var arrow = new Color[s * s];
        var ring  = new Color[s * s];
        float cx  = (s - 1) * 0.5f, cy = (s - 1) * 0.5f, r = s * 0.5f - 1f;
        for (int y = 0; y < s; y++)
        for (int x = 0; x < s; x++)
        {
            float nx = x - cx, ny = y - cy;
            float dist = MathF.Sqrt(nx * nx + ny * ny);
            // Filled disc (body) plus a small notch on the +X side to indicate facing.
            bool body  = dist <= r * 0.85f;
            bool notch = nx > 0 && MathF.Abs(ny) < (r - nx) * 0.6f;
            arrow[y * s + x] = body
                ? (notch ? new Color(255, 230, 180) : new Color(220, 80, 60))
                : Color.Transparent;
            // Outer ring drawn beneath the body so it remains visible against
            // similarly-coloured terrain at low zoom (settlement icons sit
            // close to the marker on the world map).
            ring[y * s + x] = (dist > r * 0.85f && dist <= r) ? new Color(0, 0, 0, 200) : Color.Transparent;
        }
        _arrow = new Texture2D(_gd, s, s); _arrow.SetData(arrow);
        _ring  = new Texture2D(_gd, s, s); _ring.SetData(ring);
    }

    public void Draw(SpriteBatch sb, Camera2D camera, Actor a)
    {
        sb.Begin(
            transformMatrix: camera.TransformMatrix,
            samplerState:    SamplerState.PointClamp,
            sortMode:        SpriteSortMode.Deferred,
            blendState:      BlendState.AlphaBlend);

        int s = MarkerPx;
        var origin = new Vector2(s * 0.5f, s * 0.5f);
        var pos    = new Vector2(a.Position.X, a.Position.Y);

        // Counter-scale by 1/Zoom so the camera transform's Zoom multiplier
        // cancels out, leaving a constant MarkerPx-pixel on-screen size.
        // Guard against zero just in case (shouldn't happen — clamped by C.CAMERA_MIN_ZOOM).
        float screenScale = camera.Zoom > 1e-6f ? 1f / camera.Zoom : 1f;

        sb.Draw(_ring,  pos, null, Color.White, 0f,                origin, screenScale, SpriteEffects.None, 0f);
        sb.Draw(_arrow, pos, null, Color.White, a.FacingAngleRad,  origin, screenScale, SpriteEffects.None, 0f);

        sb.End();
    }

    public void Dispose()
    {
        if (_disposed) return;
        _disposed = true;
        _arrow?.Dispose();
        _ring?.Dispose();
    }
}
