/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.ui.theme

import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontVariation
import androidx.compose.ui.text.font.FontWeight
import com.runicgateway.app.R

/**
 * Every type family the app can draw with — the eight the admin's Appearance page
 * can select between (THEMING_AND_NAV.md §5.3) plus the two system stacks its
 * "shipped default" options resolve to.
 *
 * All eight webfonts are **bundled**, not downloadable: the Play Store font
 * provider is the only downloadable-font source Compose ships with, so a
 * de-Googled device would silently fall back and every text style would gain an
 * async loading state. The binaries are taken verbatim from `google/fonts`, which
 * is how [Cinzel] arrived in M5; each carries its SIL OFL licence under
 * `app/licenses/`, **never** under `res/font/` (aapt rejects a `.txt` there).
 *
 * A family reaches a text style through [ShardTypeface], which is what maps a
 * shard's `--display` / `--serif` / `--sans` stacks onto these. The three
 * declared *shipped* roles are [Cinzel] for the engraved display/headline/title
 * block, [AppSerif] for parchment body copy, and [AppSans] for the letter-spaced
 * label/meta/button block.
 *
 * ## Weights
 *
 * The type scale asks for four: 400 (body), 500 and 700 (labels), 600 (display).
 * Every family here must supply all four, because a family is **not** confined to
 * the role its dropdown lives in — the `modern` preset puts Work Sans in the
 * display slot and the `fantasy` preset puts EB Garamond in the sans slot, both
 * bypassing the server's per-role option list (see [ShardTypeface]). The variable
 * families pin the four instances through [FontVariation] (API 26+; minSdk is 29).
 *
 * Two families are exceptions, both upstream facts rather than choices:
 * - **[IMFellEnglish] has a single weight.** Its one 400 face answers all four
 *   requests and Android synthesises the bold. The website's dropdown labels it
 *   "(no bold weight)" for the same reason.
 * - **[Cinzel] is left at the 500/600/700 it shipped with in M5.** It is the only
 *   family the server offers in the display role alone, so nothing can ask it for
 *   400; adding an instance would have edited M5's type for no reachable case.
 *
 * ## Italics
 *
 * Four families carry a true italic — the same four `client/index.html` requests
 * one for. The rest are upright-only and Compose skews them, which is what the
 * app already did for every family before this milestone and what the website
 * does for its own upright-only faces. The app draws italic in two places.
 */
@OptIn(ExperimentalTextApi::class)
private fun variable(resId: Int, weight: FontWeight, style: FontStyle = FontStyle.Normal) =
    Font(
        resId,
        weight = weight,
        style = style,
        variationSettings = FontVariation.Settings(FontVariation.weight(weight.weight)),
    )

// The four weights the type scale asks for, in the order Compose prefers to match.
private val ScaleWeights = listOf(
    FontWeight.Normal,   // 400 — body
    FontWeight.Medium,   // 500 — labelMedium / labelSmall
    FontWeight.SemiBold, // 600 — display / headline / title
    FontWeight.Bold,     // 700 — labelLarge
)

/** A variable family pinned at the four scale weights, upright only. */
private fun variableFamily(resId: Int) =
    FontFamily(ScaleWeights.map { variable(resId, it) })

/** A variable family pinned at the four scale weights, upright and italic. */
private fun variableFamily(uprightResId: Int, italicResId: Int) =
    FontFamily(
        ScaleWeights.map { variable(uprightResId, it) } +
            ScaleWeights.map { variable(italicResId, it, FontStyle.Italic) },
    )

private fun cinzel(weight: FontWeight) = variable(R.font.cinzel_variable, weight)

val Cinzel = FontFamily(
    cinzel(FontWeight.Medium),   // 500
    cinzel(FontWeight.SemiBold), // 600
    cinzel(FontWeight.Bold),     // 700
)

val EBGaramond = variableFamily(R.font.eb_garamond_variable, R.font.eb_garamond_italic)
val Merriweather = variableFamily(R.font.merriweather_variable, R.font.merriweather_italic)
val PlayfairDisplay =
    variableFamily(R.font.playfair_display_variable, R.font.playfair_display_italic)
val Inter = variableFamily(R.font.inter_variable)
val WorkSans = variableFamily(R.font.work_sans_variable)
val SourceSans3 = variableFamily(R.font.source_sans_3_variable)

/**
 * IM Fell English, whose upstream release is a single 400 face per style — there
 * is no weight axis and no bold cut to pin. Declared once per style so a request
 * at 500/600/700 lands on it rather than falling out of the family.
 */
val IMFellEnglish = FontFamily(
    Font(R.font.im_fell_english_regular, weight = FontWeight.Normal),
    Font(R.font.im_fell_english_italic, weight = FontWeight.Normal, style = FontStyle.Italic),
)

/**
 * The platform serif (Noto Serif), which is what the website's
 * `Georgia, "Times New Roman", serif` stack resolves to on Android — and the
 * app's shipped body face since M5.
 */
val AppSerif = FontFamily.Serif

/**
 * The platform sans (Roboto), which the website's
 * `"Helvetica Neue", Arial, sans-serif` stack resolves to on Android — and the
 * app's shipped label face since M5.
 */
val AppSans = FontFamily.SansSerif
