using Microsoft.Xna.Framework;

namespace Theriapolis.Game.Rendering;

public enum ViewMode { WorldMap, Tactical }

/// <summary>
/// 2D orthographic camera. Position is in world-pixel space.
/// Both WorldMap and Tactical views share the same camera; only the renderer changes.
/// </summary>
public sealed class Camera2D
{
    private readonly GraphicsDeviceWrapper _gd;

    /// <summary>Camera position in world-pixel space (top-left of view at zoom 1).</summary>
    public Vector2 Position { get; set; } = Vector2.Zero;

    /// <summary>Zoom level. 1.0 = 1 world pixel per screen pixel.</summary>
    public float Zoom { get; private set; } = 1f / Theriapolis.Core.C.WORLD_TILE_PIXELS;

    public ViewMode Mode { get; set; } = ViewMode.WorldMap;

    // Re-exports of the canonical zoom constants in C.* so existing call sites
    // (Camera2D.MinZoom, etc.) keep working without churn.
    public const float MinZoom           = Theriapolis.Core.C.CAMERA_MIN_ZOOM;
    public const float MaxZoom           = Theriapolis.Core.C.CAMERA_MAX_ZOOM;
    public const float TacticalThreshold = Theriapolis.Core.C.CAMERA_TACTICAL_THRESHOLD;

    public Camera2D(GraphicsDeviceWrapper gd)
    {
        _gd = gd;
    }

    public int ScreenWidth  => _gd.Width;
    public int ScreenHeight => _gd.Height;

    /// <summary>SpriteBatch transform matrix for this camera.</summary>
    public Matrix TransformMatrix =>
        Matrix.CreateTranslation(-Position.X, -Position.Y, 0f)
        * Matrix.CreateScale(Zoom, Zoom, 1f)
        * Matrix.CreateTranslation(ScreenWidth * 0.5f, ScreenHeight * 0.5f, 0f);

    public Vector2 WorldToScreen(Vector2 world)
    {
        var v = Vector2.Transform(world, TransformMatrix);
        return v;
    }

    public Vector2 ScreenToWorld(Vector2 screen)
    {
        var inv = Matrix.Invert(TransformMatrix);
        return Vector2.Transform(screen, inv);
    }

    public void AdjustZoom(float delta, Vector2 screenFocus)
    {
        // Keep the world point under screenFocus stationary
        var worldFocus = ScreenToWorld(screenFocus);
        Zoom = Math.Clamp(Zoom * (1f + delta), MinZoom, MaxZoom);
        var newScreen = WorldToScreen(worldFocus);
        Position += (screenFocus - newScreen) / Zoom;

        // Update view mode based on zoom threshold
        Mode = Zoom >= TacticalThreshold ? ViewMode.Tactical : ViewMode.WorldMap;
    }

    public void Pan(Vector2 worldDelta)
    {
        Position += worldDelta;
    }

    /// <summary>
    /// Returns the visible rectangle in world-tile coordinates.
    /// </summary>
    public (int x0, int y0, int x1, int y1) VisibleTileRect()
    {
        var tl = ScreenToWorld(Vector2.Zero);
        var br = ScreenToWorld(new Vector2(ScreenWidth, ScreenHeight));
        int px = Theriapolis.Core.C.WORLD_TILE_PIXELS;
        int x0 = Math.Max(0, (int)MathF.Floor(tl.X / px));
        int y0 = Math.Max(0, (int)MathF.Floor(tl.Y / px));
        int x1 = Math.Min(Theriapolis.Core.C.WORLD_WIDTH_TILES  - 1, (int)MathF.Ceiling(br.X / px));
        int y1 = Math.Min(Theriapolis.Core.C.WORLD_HEIGHT_TILES - 1, (int)MathF.Ceiling(br.Y / px));
        return (x0, y0, x1, y1);
    }
}

/// <summary>Thin wrapper so Camera2D doesn't reference the MonoGame GraphicsDevice directly.</summary>
public sealed class GraphicsDeviceWrapper
{
    private readonly Microsoft.Xna.Framework.Graphics.GraphicsDevice _device;
    public int Width  => _device.Viewport.Width;
    public int Height => _device.Viewport.Height;
    public GraphicsDeviceWrapper(Microsoft.Xna.Framework.Graphics.GraphicsDevice device) => _device = device;
}
