using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

namespace SatelliteProvider.Common.Imaging;

// AZ-367 / C14: shared tile-grid stitcher.
// Both region and route map generation previously implemented the same placement
// loop (min/max of TileX/TileY, allocate Image<Rgb24>, foreach tile load + DrawImage
// at (x-minX, y-minY) * tileSizePixels). The overlays (region center cross / route
// geofence rectangles + route point crosses) differ and stay at the call sites.
public sealed class TileGridStitcher
{
    public async Task<StitchResult> StitchAsync(
        IEnumerable<TilePlacement> tiles,
        int tileSizePixels,
        bool deduplicateByTileCoords,
        bool swallowTileLoadErrors,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(tiles);
        if (tileSizePixels <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(tileSizePixels), tileSizePixels, "Tile size must be positive.");
        }

        var sourceList = tiles.ToList();
        if (sourceList.Count == 0)
        {
            throw new InvalidOperationException("No tiles to stitch.");
        }

        var orderedTiles = deduplicateByTileCoords
            ? sourceList
                .GroupBy(t => (t.TileX, t.TileY))
                .Select(g => g.First())
                .OrderBy(t => t.TileY)
                .ThenBy(t => t.TileX)
                .ToList()
            : sourceList;

        var minX = orderedTiles.Min(t => t.TileX);
        var maxX = orderedTiles.Max(t => t.TileX);
        var minY = orderedTiles.Min(t => t.TileY);
        var maxY = orderedTiles.Max(t => t.TileY);

        var gridWidth = maxX - minX + 1;
        var gridHeight = maxY - minY + 1;
        var imageWidth = gridWidth * tileSizePixels;
        var imageHeight = gridHeight * tileSizePixels;

        var stitchedImage = new Image<Rgb24>(imageWidth, imageHeight);
        var placed = new List<TilePlacement>(orderedTiles.Count);
        var missing = new List<MissingTile>();

        try
        {
            foreach (var tile in orderedTiles)
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (!File.Exists(tile.FilePath))
                {
                    missing.Add(new MissingTile(tile, MissingTileReason.FileNotFound, Error: null));
                    continue;
                }

                Image<Rgb24>? tileImage = null;
                try
                {
                    tileImage = await Image.LoadAsync<Rgb24>(tile.FilePath, cancellationToken);
                    var destX = (tile.TileX - minX) * tileSizePixels;
                    var destY = (tile.TileY - minY) * tileSizePixels;
                    stitchedImage.Mutate(ctx => ctx.DrawImage(tileImage, new Point(destX, destY), 1f));
                    placed.Add(tile);
                }
                catch (Exception ex) when (swallowTileLoadErrors && ex is not OperationCanceledException)
                {
                    missing.Add(new MissingTile(tile, MissingTileReason.LoadFailed, ex));
                }
                finally
                {
                    tileImage?.Dispose();
                }
            }
        }
        catch
        {
            stitchedImage.Dispose();
            throw;
        }

        return new StitchResult(
            Image: stitchedImage,
            MinX: minX,
            MinY: minY,
            MaxX: maxX,
            MaxY: maxY,
            TileSizePixels: tileSizePixels,
            ImageWidth: imageWidth,
            ImageHeight: imageHeight,
            PlacedTiles: placed,
            MissingTiles: missing);
    }

    public void DrawCross(Image<Rgb24> image, Point center, Rgb24 color, int armLength, int thickness = 10)
    {
        ArgumentNullException.ThrowIfNull(image);
        if (armLength < 0 || thickness < 1)
        {
            return;
        }

        var halfThickness = thickness / 2;
        var width = image.Width;
        var height = image.Height;

        for (int dx = -armLength; dx <= armLength; dx++)
        {
            for (int t = -halfThickness; t <= halfThickness; t++)
            {
                int x = center.X + dx;
                int y = center.Y + t;
                if (x >= 0 && x < width && y >= 0 && y < height)
                {
                    image[x, y] = color;
                }
            }
        }

        for (int dy = -armLength; dy <= armLength; dy++)
        {
            for (int t = -halfThickness; t <= halfThickness; t++)
            {
                int x = center.X + t;
                int y = center.Y + dy;
                if (x >= 0 && x < width && y >= 0 && y < height)
                {
                    image[x, y] = color;
                }
            }
        }
    }

    public void DrawRectangleBorder(Image<Rgb24> image, Rectangle rect, Rgb24 color, int thickness = 5)
    {
        ArgumentNullException.ThrowIfNull(image);
        if (thickness < 1)
        {
            return;
        }

        var x1 = rect.X;
        var y1 = rect.Y;
        var x2 = rect.X + rect.Width - 1;
        var y2 = rect.Y + rect.Height - 1;
        var width = image.Width;
        var height = image.Height;

        for (int t = 0; t < thickness; t++)
        {
            for (int x = x1; x <= x2; x++)
            {
                int topY = y1 + t;
                int bottomY = y2 - t;
                if (x >= 0 && x < width && topY >= 0 && topY < height)
                {
                    image[x, topY] = color;
                }
                if (x >= 0 && x < width && bottomY >= 0 && bottomY < height)
                {
                    image[x, bottomY] = color;
                }
            }

            for (int y = y1; y <= y2; y++)
            {
                int leftX = x1 + t;
                int rightX = x2 - t;
                if (leftX >= 0 && leftX < width && y >= 0 && y < height)
                {
                    image[leftX, y] = color;
                }
                if (rightX >= 0 && rightX < width && y >= 0 && y < height)
                {
                    image[rightX, y] = color;
                }
            }
        }
    }
}

public sealed record TilePlacement(int TileX, int TileY, string FilePath);

public sealed record StitchResult(
    Image<Rgb24> Image,
    int MinX,
    int MinY,
    int MaxX,
    int MaxY,
    int TileSizePixels,
    int ImageWidth,
    int ImageHeight,
    IReadOnlyList<TilePlacement> PlacedTiles,
    IReadOnlyList<MissingTile> MissingTiles);

public sealed record MissingTile(TilePlacement Tile, MissingTileReason Reason, Exception? Error);

public enum MissingTileReason
{
    FileNotFound,
    LoadFailed
}
