using RioJoy.Core.Overlay;
using SkiaSharp;

namespace RioJoy.Overlay;

/// <summary>
/// Renders a cockpit wallpaper: lays out per-region label text with
/// <see cref="OverlayLayoutEngine"/>, then draws each label onto the base image
/// with SkiaSharp. The modern replacement for the legacy GIMP/Script-Fu pipeline
/// (docs/reference/customBackground/sg-goobie-MFD.scm). Because it shares its
/// <see cref="SkiaTextMeasurer"/> with the layout engine, drawn positions match the
/// measured extents exactly.
/// </summary>
public sealed class SkiaOverlayRenderer
{
    private readonly SkiaTextMeasurer _measurer;
    private readonly OverlayLayoutEngine _engine;
    private readonly SKColor _defaultColor;

    /// <param name="defaultColorHex">Label color when a region sets none (default white).</param>
    public SkiaOverlayRenderer(string defaultColorHex = "#FFFFFF")
    {
        _measurer = new SkiaTextMeasurer();
        _engine = new OverlayLayoutEngine(_measurer);
        _defaultColor = SKColor.Parse(defaultColorHex);
    }

    /// <summary>
    /// Render labels onto <paramref name="baseImage"/> and return the result as a
    /// new bitmap (the input is not modified). Caller owns the returned bitmap.
    /// </summary>
    public SKBitmap Render(
        OverlayTemplate template,
        IDictionary<string, string> labels,
        SKBitmap baseImage,
        OverlayLayoutOptions? options = null)
    {
        if (template is null) throw new ArgumentNullException(nameof(template));
        if (labels is null) throw new ArgumentNullException(nameof(labels));
        if (baseImage is null) throw new ArgumentNullException(nameof(baseImage));
        options ??= new OverlayLayoutOptions();

        var output = baseImage.Copy();
        using var canvas = new SKCanvas(output);

        foreach (PlacedLabel label in _engine.Layout(template, labels, options))
        {
            OverlayRegion? region = template.FindRegion(label.RegionName);
            SKColor color = region?.ColorHex is { } hex ? SKColor.Parse(hex) : _defaultColor;

            using var paint = new SKPaint
            {
                Typeface = _measurer.Typeface(label.FontFamily),
                TextSize = (float)label.FontSizePx,
                Color = color,
                IsAntialias = true,
            };

            // PlacedLabel.Y is the top of the text box; Skia draws from the baseline.
            float baseline = (float)label.Y - paint.FontMetrics.Ascent; // ascent is negative

            // Rotated labels: turn the canvas about the cell center (the engine
            // centered the text box there). RotationDegrees is counter-clockwise;
            // Skia's RotateDegrees is clockwise-positive, so negate.
            if (label.RotationDegrees != 0 && region is not null)
            {
                float cx = (float)(region.X + region.Width / 2);
                float cy = (float)(region.Y + region.Height / 2);
                canvas.Save();
                canvas.RotateDegrees(-(float)label.RotationDegrees, cx, cy);
                canvas.DrawText(label.Text, (float)label.X, baseline, paint);
                canvas.Restore();
                continue;
            }

            bool clip = options.SizeMode == OverlaySizeMode.Crop && region is not null;
            if (clip)
            {
                canvas.Save();
                canvas.ClipRect(new SKRect(
                    (float)region!.X, (float)region.Y,
                    (float)(region.X + region.Width), (float)(region.Y + region.Height)));
            }

            canvas.DrawText(label.Text, (float)label.X, baseline, paint);

            if (clip)
                canvas.Restore();
        }

        canvas.Flush();
        return output;
    }

    /// <summary>Render and encode to PNG bytes.</summary>
    public byte[] RenderToPng(
        OverlayTemplate template,
        IDictionary<string, string> labels,
        SKBitmap baseImage,
        OverlayLayoutOptions? options = null)
    {
        using SKBitmap bmp = Render(template, labels, baseImage, options);
        using SKData data = bmp.Encode(SKEncodedImageFormat.Png, 100);
        return data.ToArray();
    }

    /// <summary>
    /// Render using the template's <see cref="OverlayTemplate.BaseImagePath"/> and
    /// write a PNG to <paramref name="outputPath"/>.
    /// </summary>
    public void RenderToFile(
        OverlayTemplate template,
        IDictionary<string, string> labels,
        string outputPath,
        OverlayLayoutOptions? options = null)
    {
        if (template is null) throw new ArgumentNullException(nameof(template));
        if (string.IsNullOrWhiteSpace(outputPath)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(outputPath));
        if (string.IsNullOrWhiteSpace(template.BaseImagePath))
            throw new InvalidOperationException("OverlayTemplate.BaseImagePath is not set.");

        using SKBitmap baseImage = SKBitmap.Decode(template.BaseImagePath)
            ?? throw new InvalidOperationException($"Could not decode base image: {template.BaseImagePath}");

        byte[] png = RenderToPng(template, labels, baseImage, options);

        string? dir = Path.GetDirectoryName(Path.GetFullPath(outputPath));
        if (!string.IsNullOrEmpty(dir))
            Directory.CreateDirectory(dir);
        File.WriteAllBytes(outputPath, png);
    }
}
