namespace RioJoy.Core.Overlay;

/// <summary>
/// Lays out per-region label text over a cockpit template into positioned
/// <see cref="PlacedLabel"/>s ready for a rasterizer. Pure: all measurement goes
/// through an <see cref="ITextMeasurer"/>, so the placement/auto-fit math is
/// unit-tested without a real font. Ports the per-field logic of
/// <c>create-data-layer</c> (sg-goobie-MFD.scm, lines 69–113): optional auto-fit
/// when the text overflows, then vertical (and here also horizontal) justification.
/// </summary>
public sealed class OverlayLayoutEngine
{
    private readonly ITextMeasurer _measurer;

    public OverlayLayoutEngine(ITextMeasurer measurer) =>
        _measurer = measurer ?? throw new ArgumentNullException(nameof(measurer));

    /// <summary>
    /// Lay out every region that has non-empty label text. Regions without an entry
    /// in <paramref name="labels"/> (or with empty text) are skipped — matching the
    /// legacy behavior where a blank field produced no visible text.
    /// </summary>
    public IList<PlacedLabel> Layout(
        OverlayTemplate template,
        IDictionary<string, string> labels,
        OverlayLayoutOptions? options = null)
    {
        if (template is null) throw new ArgumentNullException(nameof(template));
        if (labels is null) throw new ArgumentNullException(nameof(labels));
        options ??= new OverlayLayoutOptions();

        var placed = new List<PlacedLabel>(template.Regions.Count);
        foreach (OverlayRegion region in template.Regions)
        {
            if (labels.TryGetValue(region.Name, out string? text) && !string.IsNullOrEmpty(text))
                placed.Add(Place(region, text, options));
        }
        return placed;
    }

    /// <summary>Lay out a single label within a region.</summary>
    public PlacedLabel Place(OverlayRegion region, string text, OverlayLayoutOptions? options = null)
    {
        if (region is null) throw new ArgumentNullException(nameof(region));
        if (string.IsNullOrEmpty(text)) throw new ArgumentException("Value cannot be null or empty.", nameof(text));
        options ??= new OverlayLayoutOptions();

        double rotation = region.RotationDegrees ?? 0;

        // A quarter turn swaps the axes the text is fitted against: a 90°/270° label
        // runs its length along the cell's height and its line-height along the width.
        bool quarterTurn = (int)Math.Abs(rotation) % 180 == 90;
        double fitWidth = quarterTurn ? region.Height : region.Width;
        double fitHeight = quarterTurn ? region.Width : region.Height;

        double size = region.FontSizePx;
        TextExtent ext = _measurer.Measure(text, region.FontFamily, size);

        // Auto-fit only in Fit mode and only when the base size actually overflows
        // (legacy: the calc-fontsize call is guarded by size-handling == 0 && overflow).
        if (options.SizeMode == OverlaySizeMode.Fit && (ext.Width > fitWidth || ext.Height > fitHeight))
        {
            size = FontFitter.BestFitSize(text, region.FontFamily, fitWidth, fitHeight, _measurer);
            ext = _measurer.Measure(text, region.FontFamily, size);
        }

        if (rotation != 0)
        {
            // Center the (unrotated) text box in the cell; the renderer turns it about
            // the cell center, so a centered box rotates in place. Justification N/A.
            double cxLeft = region.X + (region.Width - ext.Width) / 2;
            double cyTop = region.Y + (region.Height - ext.Height) / 2;
            return new PlacedLabel(region.Name, text, cxLeft, cyTop, ext.Width, ext.Height, region.FontFamily, size, rotation);
        }

        OverlayHJust hjust = region.HJust ?? options.HJust;
        OverlayVJust vjust = region.VJust ?? options.VJust;

        // Vertical placement is a direct port of the script; horizontal is the
        // generalization of its left-anchored behavior (its header comment intends
        // "centered within the bounds", so Center is the engine default).
        double x = hjust switch
        {
            OverlayHJust.Center => region.X + (region.Width - ext.Width) / 2,
            OverlayHJust.Right => region.X + (region.Width - ext.Width),
            _ => region.X,
        };
        double y = vjust switch
        {
            OverlayVJust.Center => region.Y + (region.Height - ext.Height) / 2,
            OverlayVJust.Bottom => region.Y + region.Height - ext.Height,
            _ => region.Y,
        };

        return new PlacedLabel(region.Name, text, x, y, ext.Width, ext.Height, region.FontFamily, size);
    }
}
