namespace RioJoy.Core.Overlay;

/// <summary>
/// Finds the largest font size whose text fits a cell — a faithful port of the
/// legacy <c>calc-fontsize</c> Script-Fu routine
/// (docs/reference/customBackground/sg-goobie-MFD.scm, lines 50–67).
///
/// <para>The original is an expand-then-narrow search: grow the size by a factor
/// (<c>adjust</c>, starting at 2) while the text fits; on the first overflow, halve
/// the growth factor's excess-over-1 and retry from the last size that fit. It
/// converges when the (truncated) size stops changing or the measured extents
/// repeat, and never returns below <see cref="MinFontSize"/>.</para>
/// </summary>
public static class FontFitter
{
    /// <summary>Minimum font size the legacy routine will return (px).</summary>
    public const double MinFontSize = 6;

    // Guard against a pathological measurer that never converges.
    private const int MaxIterations = 1000;

    /// <summary>
    /// Largest font size (px) at which <paramref name="text"/> fits within
    /// <paramref name="width"/> × <paramref name="height"/>, measured by
    /// <paramref name="measurer"/>.
    /// </summary>
    public static double BestFitSize(
        string text, string fontFamily, double width, double height, ITextMeasurer measurer)
    {
        if (measurer is null) throw new ArgumentNullException(nameof(measurer));

        // State mirrors the Scheme named-let: (fontsize last-extents last-fontsize adjust).
        double fontsize = 6;        // minimum possible fontsize (the loop's seed)
        double lastFontsize = 3;
        double adjust = 2;
        TextExtent? lastExtents = null;

        for (int i = 0; i < MaxIterations; i++)
        {
            TextExtent extents = measurer.Measure(text, fontFamily, fontsize);

            // Converged: size stopped moving, or the extents repeated.
            if (lastFontsize == fontsize || (lastExtents is { } prev && prev == extents))
                return Math.Max(fontsize, MinFontSize);

            if (extents.Width > width || extents.Height > height)
            {
                // Overflow: shrink the growth factor and retry from the last good size.
                adjust = (adjust - 1) * 0.5 + 1;
                fontsize = Math.Truncate(lastFontsize * adjust);
                // lastExtents / lastFontsize unchanged — they hold the last fit.
            }
            else
            {
                // Fits: remember it and keep growing by the current factor.
                lastExtents = extents;
                lastFontsize = fontsize;
                fontsize = Math.Truncate(fontsize * adjust);
            }
        }

        return Math.Max(fontsize, MinFontSize);
    }
}
