<?php

namespace App\Support;

/**
 * Build a Google Fonts CSS2 URL for one or more families. Used by
 * `app.blade.php` to render the <link rel="stylesheet"> tag based on the
 * active theme's `font_sans` and `font_display` knobs.
 *
 * `'system'` is a sentinel for "no Google Font — use the system stack" and
 * is filtered out before the URL is built. If every requested family is
 * `system` (or the input is empty), `build()` returns `null` and the
 * caller skips the <link> entirely.
 *
 * Default weight axis is `400;500;600;700` — covers body + heading +
 * button text. Heavier display weights are rare; admins who need them
 * can append weights to the family string via the picker.
 */
class GoogleFontUrl
{
    private const DEFAULT_WEIGHTS = [400, 500, 600, 700];

    /**
     * @param  array<string>  $families  e.g. ['Inter', 'Manrope']. Duplicates and 'system' are dropped.
     * @param  array<int>  $weights  Numeric Google Fonts wght axis values (e.g. [400, 600, 700, 800]).
     *   Sorted + deduplicated internally. Empty array (or default) falls back to [400, 500, 600, 700].
     *   Same axis is used for every family in the URL — Google CSS2 supports per-family axes but
     *   mirroring is fine; bandwidth is negligible for woff2 weights.
     * @param  array<string>  $italicFamilies  Subset of `$families` that should ALSO load italic
     *   weights (full ital,wght@ axis). Marketing display fonts (font_display, font_subheading)
     *   need this for `<em>` accent text in headlines — Lora Italic, Playfair Italic, etc.
     *   Body fonts skip this; modern browsers fake italic acceptably and the 4 extra woff2
     *   files for italic weights aren't worth it for body copy that rarely uses <em>.
     *   Comparison is case-insensitive, ignores leading/trailing whitespace.
     */
    public static function build(
        array $families,
        array $weights = self::DEFAULT_WEIGHTS,
        array $italicFamilies = [],
    ): ?string {
        $clean = [];
        foreach ($families as $family) {
            $f = trim((string) $family);
            if ($f === '' || strcasecmp($f, 'system') === 0) {
                continue;
            }
            // Family names may have spaces ("Open Sans") — Google's CSS2
            // URL encodes them with `+`. Keep allowed: letters, digits,
            // spaces (→ +), hyphens. Reject anything else as malformed.
            if (! preg_match('/^[A-Za-z][A-Za-z0-9 -]{0,63}$/', $f)) {
                continue;
            }
            $encoded = str_replace(' ', '+', $f);
            if (! in_array($encoded, $clean, true)) {
                $clean[] = $encoded;
            }
        }

        if ($clean === []) {
            return null;
        }

        // Normalize italic-needing families for case-insensitive comparison
        // against the encoded list. Same trim/system rules as $families so
        // a stray empty/system entry doesn't accidentally enable italic.
        $italicSet = [];
        foreach ($italicFamilies as $family) {
            $f = trim((string) $family);
            if ($f === '' || strcasecmp($f, 'system') === 0) {
                continue;
            }
            $italicSet[strtolower($f)] = true;
        }

        $weightAxis = self::buildWeightAxis($weights);
        $italicAxis = self::buildItalicWeightAxis($weights);

        $params = array_map(
            function (string $encoded) use ($italicSet, $weightAxis, $italicAxis) {
                // Reverse the `+` → space encoding for comparison against $italicSet.
                $original = str_replace('+', ' ', $encoded);
                $needsItalic = isset($italicSet[strtolower($original)]);

                return $needsItalic
                    ? "family={$encoded}:ital,wght@{$italicAxis}"
                    : "family={$encoded}:wght@{$weightAxis}";
            },
            $clean,
        );

        return 'https://fonts.googleapis.com/css2?'.implode('&', $params).'&display=swap';
    }

    /**
     * Sort + dedupe a numeric weight axis into a `wght@` segment value.
     * Empty input falls back to the default 400;500;600;700 axis.
     *
     * @param  array<int>  $weights
     */
    private static function buildWeightAxis(array $weights): string
    {
        $clean = self::cleanWeights($weights);

        return implode(';', $clean);
    }

    /**
     * Build a `ital,wght@` axis value: regular weights first (`0,W`), then
     * italic weights (`1,W`). Google CSS2 requires this ordering — items
     * sorted ascending by ital then weight. Used for display fonts whose
     * `<em>` accent text needs real italic glyphs (Lora Italic, etc.) rather
     * than browser-faked obliquing of the regular weight.
     *
     * @param  array<int>  $weights
     */
    private static function buildItalicWeightAxis(array $weights): string
    {
        $clean = self::cleanWeights($weights);
        $axis = [];
        foreach ($clean as $w) {
            $axis[] = "0,{$w}";
        }
        foreach ($clean as $w) {
            $axis[] = "1,{$w}";
        }

        return implode(';', $axis);
    }

    /**
     * @param  array<int>  $weights
     * @return array<int>
     */
    private static function cleanWeights(array $weights): array
    {
        $clean = [];
        foreach ($weights as $w) {
            $n = (int) $w;
            if ($n > 0) {
                $clean[$n] = true;
            }
        }
        if ($clean === []) {
            $clean = array_fill_keys(self::DEFAULT_WEIGHTS, true);
        }
        $sorted = array_keys($clean);
        sort($sorted);

        return $sorted;
    }
}
