using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Theriapolis.Core;
using Theriapolis.Core.World;
using Theriapolis.Core.World.Generation;
using Theriapolis.Core.World.Polylines;
using Theriapolis.Core.Util;

namespace Theriapolis.Game.Rendering;

/// <summary>
/// Renders rivers, roads, and rail lines as thick world-space polylines.
/// Uses simplified LOD geometry at low zoom levels.
/// </summary>
public sealed class LineFeatureRenderer : IDisposable
{
    private readonly GraphicsDevice _gd;
    private readonly WorldGenContext _ctx;
    private Texture2D _pixel = null!;
    private bool _disposed;

    // Zoom threshold below which SimplifiedPoints are used
    private const float LodSwitchZoom = 0.15f;

    // Line widths in world pixels at full zoom (scaled by camera zoom)
    private const float RiverMajorWidth  = 6f;
    private const float RiverWidth       = 3.5f;
    private const float StreamWidth      = 1.5f;
    private const float RailWidth        = 3f;
    private const float HighwayWidth     = 4f;
    private const float PostRoadWidth    = 2.5f;
    private const float DirtRoadWidth    = 1.5f;

    // Line colors
    private static readonly Color RiverColor       = new(60,  120, 200);
    private static readonly Color RailColor        = new(120, 100,  80);
    private static readonly Color RailTieColor     = new(80,  70,  60);
    private static readonly Color HighwayColor     = new(210, 180,  80);
    private static readonly Color PostRoadColor    = new(180, 155,  70);
    private static readonly Color DirtRoadColor    = new(150, 130,  90);
    private static readonly Color BridgeDeckColor  = new(160, 140, 100);
    private static readonly Color BridgeRailColor  = new(100,  85,  60);
    private const float BridgeDeckWidth = 6f;  // wider than HighwayWidth so the deck fully covers the road underneath

    public LineFeatureRenderer(GraphicsDevice gd, WorldGenContext ctx)
    {
        _gd  = gd;
        _ctx = ctx;
        BuildPixel();
    }

    private void BuildPixel()
    {
        _pixel = new Texture2D(_gd, 1, 1);
        _pixel.SetData(new[] { Color.White });
    }

    public void Draw(SpriteBatch sb, Camera2D camera)
    {
        bool useLod = camera.Zoom < LodSwitchZoom;

        sb.Begin(
            transformMatrix: camera.TransformMatrix,
            samplerState:    SamplerState.PointClamp,
            sortMode:        SpriteSortMode.Deferred,
            blendState:      BlendState.AlphaBlend);

        // Draw order: roads → rivers → bridges → rail (rail on top)
        DrawRoads(sb, useLod, camera.Zoom);
        DrawRivers(sb, useLod, camera.Zoom);
        DrawBridges(sb, camera.Zoom);
        DrawRail(sb, useLod, camera.Zoom);

        sb.End();
    }

    private void DrawRoads(SpriteBatch sb, bool useLod, float zoom)
    {
        // Smallest first, biggest last — so when a smaller road has been merged
        // onto a larger road by PolylineCleanupStage, the larger road's wider
        // stroke covers the overdraw and the junction looks clean.
        foreach (var road in _ctx.World.Roads.OrderBy(RoadDrawRank))
        {
            var (color, width) = road.RoadClassification switch
            {
                RoadType.Highway   => (HighwayColor,  HighwayWidth),
                RoadType.PostRoad  => (PostRoadColor, PostRoadWidth),
                _                  => (DirtRoadColor, DirtRoadWidth),
            };
            DrawPolyline(sb, road, color, width, useLod);
        }
    }

    private static int RoadDrawRank(Polyline r) => r.RoadClassification switch
    {
        RoadType.Footpath => 0,
        RoadType.DirtRoad => 1,
        RoadType.PostRoad => 2,
        RoadType.Highway  => 3,
        _                 => 1,
    };

    private void DrawRivers(SpriteBatch sb, bool useLod, float zoom)
    {
        foreach (var river in _ctx.World.Rivers)
        {
            var (color, width) = river.RiverClassification switch
            {
                RiverClass.MajorRiver => (RiverColor, RiverMajorWidth),
                RiverClass.River      => (RiverColor, RiverWidth),
                _                     => (RiverColor, StreamWidth),
            };
            // Scale river width slightly by flow for a natural look
            float flowScale = 1f + (river.FlowAccumulation / (float)C.RIVER_MAJOR_THRESHOLD) * 0.3f;
            DrawPolyline(sb, river, color, Math.Min(width * flowScale, RiverMajorWidth * 1.5f), useLod);
        }
    }

    private void DrawRail(SpriteBatch sb, bool useLod, float zoom)
    {
        foreach (var rail in _ctx.World.Rails)
        {
            // Draw tie marks underneath, then the rail line on top
            DrawPolyline(sb, rail, RailTieColor, RailWidth + 2f, useLod);
            DrawPolyline(sb, rail, RailColor, RailWidth * 0.5f, useLod);
        }
    }

    private void DrawBridges(SpriteBatch sb, float zoom)
    {
        foreach (var bridge in _ctx.World.Bridges)
        {
            Vector2 start = new(bridge.Start.X, bridge.Start.Y);
            Vector2 end   = new(bridge.End.X,   bridge.End.Y);
            Vector2 span  = end - start;
            float   len   = span.Length();
            if (len < 0.5f) continue;

            Vector2 roadDir = span / len;
            Vector2 perpDir = new(-roadDir.Y, roadDir.X);

            // Bridge deck: follows the actual road polyline at the crossing.
            DrawSegment(sb, start, end, BridgeDeckColor, BridgeDeckWidth);

            // Bridge abutments: short perpendicular bars at each end.
            float halfBar = BridgeDeckWidth * 1.5f;
            DrawSegment(sb, start - perpDir * halfBar, start + perpDir * halfBar, BridgeRailColor, 1f);
            DrawSegment(sb, end   - perpDir * halfBar, end   + perpDir * halfBar, BridgeRailColor, 1f);
        }
    }

    private void DrawPolyline(SpriteBatch sb, Polyline polyline, Color color, float worldWidth, bool useLod)
    {
        var pts = (useLod && polyline.SimplifiedPoints is { Count: >= 2 })
            ? polyline.SimplifiedPoints
            : polyline.Points;

        if (pts.Count < 2) return;

        for (int i = 0; i < pts.Count - 1; i++)
        {
            DrawSegment(sb,
                new Vector2(pts[i].X, pts[i].Y),
                new Vector2(pts[i + 1].X, pts[i + 1].Y),
                color, worldWidth);
        }
    }

    private void DrawSegment(SpriteBatch sb, Vector2 from, Vector2 to, Color color, float width)
    {
        Vector2 diff  = to - from;
        float   len   = diff.Length();
        if (len < 0.5f) return;

        // Extend segment by half-width at both ends so consecutive segments
        // overlap at joints, filling the triangular gap that appears when
        // two thick rotated rectangles meet at an angle.
        float   extend = width * 0.5f;
        Vector2 dir    = diff / len;
        Vector2 start  = from - dir * extend;
        float   extLen = len + 2f * extend;
        float   angle  = MathF.Atan2(diff.Y, diff.X);

        sb.Draw(
            texture:          _pixel,
            position:         start,
            sourceRectangle:  null,
            color:            color,
            rotation:         angle,
            origin:           new Vector2(0f, 0.5f),
            scale:            new Vector2(extLen, width),
            effects:          SpriteEffects.None,
            layerDepth:       0f);
    }

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