"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.findFontFilesInCss = exports.getFallbackFontOverrideMetrics = exports.getFontAxes = exports.fetchFontFile = exports.fetchCSSFromGoogleFonts = exports.getUrl = exports.validateData = void 0;
const fs_1 = __importDefault(require("fs"));
// @ts-ignore
const node_fetch_1 = __importDefault(require("next/dist/compiled/node-fetch"));
// @ts-ignore
const font_utils_1 = require("next/dist/server/font-utils");
// @ts-ignore
const Log = __importStar(require("next/dist/build/output/log"));
const utils_1 = require("../utils");
const font_data_json_1 = __importDefault(require("./font-data.json"));
const allowedDisplayValues = ['auto', 'block', 'swap', 'fallback', 'optional'];
const formatValues = (values) => values.map((val) => `\`${val}\``).join(', ');
function validateData(functionName, data, config) {
    var _a;
    let { weight, style, preload = true, display = 'swap', axes, fallback, adjustFontFallback = true, variable, subsets: callSubsets, } = data[0] || {};
    // Get the subsets defined for the font from either next.config.js or the function call. If both are present, pick the function call subsets.
    const subsets = (_a = callSubsets !== null && callSubsets !== void 0 ? callSubsets : config === null || config === void 0 ? void 0 : config.subsets) !== null && _a !== void 0 ? _a : [];
    if (functionName === '') {
        (0, utils_1.nextFontError)(`next/font/google has no default export`);
    }
    const fontFamily = functionName.replace(/_/g, ' ');
    // Get the Google font metadata, we'll use this to validate the font arguments and to print better error messages
    const fontFamilyData = font_data_json_1.default[fontFamily];
    if (!fontFamilyData) {
        (0, utils_1.nextFontError)(`Unknown font \`${fontFamily}\``);
    }
    const availableSubsets = fontFamilyData.subsets;
    if (availableSubsets.length === 0) {
        // If the font doesn't have any preloadeable subsets, disable preload
        preload = false;
    }
    else {
        if (preload && !callSubsets && !(config === null || config === void 0 ? void 0 : config.subsets)) {
            (0, utils_1.nextFontError)(`Missing selected subsets for font \`${fontFamily}\`. Please specify subsets in the function call or in your \`next.config.js\`. Read more: https://nextjs.org/docs/messages/google-fonts-missing-subsets`);
        }
        subsets.forEach((subset) => {
            if (!availableSubsets.includes(subset)) {
                (0, utils_1.nextFontError)(`Unknown subset \`${subset}\` for font \`${fontFamily}\`.\nAvailable subsets: ${formatValues(availableSubsets)}`);
            }
        });
    }
    const fontWeights = fontFamilyData.weights;
    const fontStyles = fontFamilyData.styles;
    // Get the unique weights and styles from the function call
    const weights = !weight
        ? []
        : [...new Set(Array.isArray(weight) ? weight : [weight])];
    const styles = !style
        ? []
        : [...new Set(Array.isArray(style) ? style : [style])];
    if (weights.length === 0) {
        // Set variable as default, throw if not available
        if (fontWeights.includes('variable')) {
            weights.push('variable');
        }
        else {
            (0, utils_1.nextFontError)(`Missing weight for font \`${fontFamily}\`.\nAvailable weights: ${formatValues(fontWeights)}`);
        }
    }
    if (weights.length > 1 && weights.includes('variable')) {
        (0, utils_1.nextFontError)(`Unexpected \`variable\` in weight array for font \`${fontFamily}\`. You only need \`variable\`, it includes all available weights.`);
    }
    weights.forEach((selectedWeight) => {
        if (!fontWeights.includes(selectedWeight)) {
            (0, utils_1.nextFontError)(`Unknown weight \`${selectedWeight}\` for font \`${fontFamily}\`.\nAvailable weights: ${formatValues(fontWeights)}`);
        }
    });
    if (styles.length === 0) {
        if (fontStyles.length === 1) {
            // Handle default style for fonts that only have italic
            styles.push(fontStyles[0]);
        }
        else {
            // Otherwise set default style to normal
            styles.push('normal');
        }
    }
    styles.forEach((selectedStyle) => {
        if (!fontStyles.includes(selectedStyle)) {
            (0, utils_1.nextFontError)(`Unknown style \`${selectedStyle}\` for font \`${fontFamily}\`.\nAvailable styles: ${formatValues(fontStyles)}`);
        }
    });
    if (!allowedDisplayValues.includes(display)) {
        (0, utils_1.nextFontError)(`Invalid display value \`${display}\` for font \`${fontFamily}\`.\nAvailable display values: ${formatValues(allowedDisplayValues)}`);
    }
    if (weights[0] !== 'variable' && axes) {
        (0, utils_1.nextFontError)('Axes can only be defined for variable fonts');
    }
    return {
        fontFamily,
        weights,
        styles,
        display,
        preload,
        selectedVariableAxes: axes,
        fallback,
        adjustFontFallback,
        variable,
        subsets,
    };
}
exports.validateData = validateData;
/**
 * Generate the Google Fonts URL given the requested weight(s), style(s) and additional variable axes
 */
function getUrl(fontFamily, axes, display) {
    var _a, _b;
    // Variants are all combinations of weight and style, each variant will result in a separate font file
    const variants = [];
    if (axes.wght) {
        for (const wght of axes.wght) {
            if (!axes.ital) {
                variants.push([['wght', wght], ...((_a = axes.variableAxes) !== null && _a !== void 0 ? _a : [])]);
            }
            else {
                for (const ital of axes.ital) {
                    variants.push([
                        ['ital', ital],
                        ['wght', wght],
                        ...((_b = axes.variableAxes) !== null && _b !== void 0 ? _b : []),
                    ]);
                }
            }
        }
    }
    else if (axes.variableAxes) {
        // Variable fonts might not have a range of weights, just add optional variable axes in that case
        variants.push([...axes.variableAxes]);
    }
    // Google api requires the axes to be sorted, starting with lowercase words
    if (axes.variableAxes) {
        variants.forEach((variant) => {
            variant.sort(([a], [b]) => {
                const aIsLowercase = a.charCodeAt(0) > 96;
                const bIsLowercase = b.charCodeAt(0) > 96;
                if (aIsLowercase && !bIsLowercase)
                    return -1;
                if (bIsLowercase && !aIsLowercase)
                    return 1;
                return a > b ? 1 : -1;
            });
        });
    }
    let url = `https://fonts.googleapis.com/css2?family=${fontFamily.replace(/ /g, '+')}`;
    if (variants.length > 0) {
        url = `${url}:${variants[0].map(([key]) => key).join(',')}@${variants
            .map((variant) => variant.map(([, val]) => val).join(','))
            .sort()
            .join(';')}`;
    }
    url = `${url}&display=${display}`;
    return url;
}
exports.getUrl = getUrl;
/**
 * Fetches the CSS containing the @font-face declarations from Google Fonts.
 * The fetch has a user agent header with a modern browser to ensure we'll get .woff2 files.
 *
 * The env variable NEXT_FONT_GOOGLE_MOCKED_RESPONSES may be set containing a path to mocked data.
 * It's used to defined mocked data to avoid hitting the Google Fonts API during tests.
 */
async function fetchCSSFromGoogleFonts(url, fontFamily) {
    // Check if mocked responses are defined, if so use them instead of fetching from Google Fonts
    let mockedResponse;
    if (process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES) {
        const mockFile = require(process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES);
        mockedResponse = mockFile[url];
        if (!mockedResponse) {
            (0, utils_1.nextFontError)('Missing mocked response for URL: ' + url);
        }
    }
    let cssResponse;
    if (mockedResponse) {
        // Just use the mocked CSS if it's set
        cssResponse = mockedResponse;
    }
    else {
        const res = await (0, node_fetch_1.default)(url, {
            headers: {
                // The file format is based off of the user agent, make sure woff2 files are fetched
                'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36',
            },
        });
        if (!res.ok) {
            (0, utils_1.nextFontError)(`Failed to fetch font  \`${fontFamily}\`.\nURL: ${url}`);
        }
        cssResponse = await res.text();
    }
    return cssResponse;
}
exports.fetchCSSFromGoogleFonts = fetchCSSFromGoogleFonts;
/**
 * Fetch the url and return a buffer with the font file.
 */
async function fetchFontFile(url) {
    // Check if we're using mocked data
    if (process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES) {
        // If it's an absolute path, read the file from the filesystem
        if (url.startsWith('/')) {
            return fs_1.default.readFileSync(url);
        }
        // Otherwise just return a unique buffer
        return Buffer.from(url);
    }
    const arrayBuffer = await (0, node_fetch_1.default)(url).then((r) => r.arrayBuffer());
    return Buffer.from(arrayBuffer);
}
exports.fetchFontFile = fetchFontFile;
/**
 * Validates and gets the data for each font axis required to generate the Google Fonts URL.
 */
function getFontAxes(fontFamily, weights, styles, selectedVariableAxes) {
    // Get all the available axes for the current font from the metadata file
    const allAxes = font_data_json_1.default[fontFamily].axes;
    const hasItalic = styles.includes('italic');
    const hasNormal = styles.includes('normal');
    // Make sure the order is correct, otherwise Google Fonts will return an error
    // If only normal is set, we can skip returning the ital axis as normal is the default
    const ital = hasItalic ? [...(hasNormal ? ['0'] : []), '1'] : undefined;
    // Weights will always contain one element if it's a variable font
    if (weights[0] === 'variable') {
        if (selectedVariableAxes) {
            // The axes other than weight and style that can be defined for the current variable font
            const defineAbleAxes = allAxes
                .map(({ tag }) => tag)
                .filter((tag) => tag !== 'wght');
            if (defineAbleAxes.length === 0) {
                (0, utils_1.nextFontError)(`Font \`${fontFamily}\` has no definable \`axes\``);
            }
            if (!Array.isArray(selectedVariableAxes)) {
                (0, utils_1.nextFontError)(`Invalid axes value for font \`${fontFamily}\`, expected an array of axes.\nAvailable axes: ${formatValues(defineAbleAxes)}`);
            }
            selectedVariableAxes.forEach((key) => {
                if (!defineAbleAxes.some((tag) => tag === key)) {
                    (0, utils_1.nextFontError)(`Invalid axes value \`${key}\` for font \`${fontFamily}\`.\nAvailable axes: ${formatValues(defineAbleAxes)}`);
                }
            });
        }
        let weightAxis;
        let variableAxes;
        for (const { tag, min, max } of allAxes) {
            if (tag === 'wght') {
                // In variable fonts the weight is a range
                weightAxis = `${min}..${max}`;
            }
            else if (selectedVariableAxes === null || selectedVariableAxes === void 0 ? void 0 : selectedVariableAxes.includes(tag)) {
                if (!variableAxes) {
                    variableAxes = [];
                }
                variableAxes.push([tag, `${min}..${max}`]);
            }
        }
        return {
            wght: weightAxis ? [weightAxis] : undefined,
            ital,
            variableAxes,
        };
    }
    else {
        return {
            ital,
            wght: weights,
        };
    }
}
exports.getFontAxes = getFontAxes;
/**
 * Get precalculated fallback font metrics for the Google Fonts family.
 *
 * TODO:
 * We might want to calculate these values with fontkit instead (like in next/font/local).
 * That way we don't have to update the precalculated values every time a new font is added to Google Fonts.
 */
function getFallbackFontOverrideMetrics(fontFamily) {
    try {
        const { ascent, descent, lineGap, fallbackFont, sizeAdjust } = (0, font_utils_1.calculateSizeAdjustValues)(require('next/dist/server/google-font-metrics.json')[fontFamily]);
        return {
            fallbackFont,
            ascentOverride: `${ascent}%`,
            descentOverride: `${descent}%`,
            lineGapOverride: `${lineGap}%`,
            sizeAdjust: `${sizeAdjust}%`,
        };
    }
    catch {
        Log.error(`Failed to find font override values for font \`${fontFamily}\``);
    }
}
exports.getFallbackFontOverrideMetrics = getFallbackFontOverrideMetrics;
/**
 * Find all font files in the CSS response and determine which files should be preloaded.
 * In Google Fonts responses, the @font-face's subset is above it in a comment.
 * Walk through the CSS from top to bottom, keeping track of the current subset.
 */
function findFontFilesInCss(css, subsetsToPreload) {
    var _a, _b;
    // Find font files to download
    const fontFiles = [];
    // Keep track of the current subset
    let currentSubset = '';
    for (const line of css.split('\n')) {
        const newSubset = (_a = /\/\* (.+?) \*\//.exec(line)) === null || _a === void 0 ? void 0 : _a[1];
        if (newSubset) {
            // Found new subset in a comment above the next @font-face declaration
            currentSubset = newSubset;
        }
        else {
            const googleFontFileUrl = (_b = /src: url\((.+?)\)/.exec(line)) === null || _b === void 0 ? void 0 : _b[1];
            if (googleFontFileUrl &&
                !fontFiles.some((foundFile) => foundFile.googleFontFileUrl === googleFontFileUrl)) {
                // Found the font file in the @font-face declaration.
                fontFiles.push({
                    googleFontFileUrl,
                    preloadFontFile: !!(subsetsToPreload === null || subsetsToPreload === void 0 ? void 0 : subsetsToPreload.includes(currentSubset)),
                });
            }
        }
    }
    return fontFiles;
}
exports.findFontFilesInCss = findFontFilesInCss;
