rmcl 0.6.2

A fully featured Minecraft TUI launcher
// SPDX-FileCopyrightText: 2026 Constantin Bauer
// SPDX-License-Identifier: GPL-3.0-only

use std::path::Path;

use serde::{Deserialize, Serialize};

use crate::instance::loader::GameVersion;
use crate::net::{HttpClient, NetError, download_loader_libraries};

const QUILT_META_BASE: &str = "https://meta.quiltmc.org/v3";

#[derive(Debug, Clone, Deserialize)]
pub struct QuiltLoaderVersion {
    pub loader: QuiltVersion,
}

#[derive(Debug, Clone, Deserialize)]
pub struct QuiltVersion {
    pub version: String,
}

#[derive(Debug, Clone, Deserialize)]
pub struct QuiltGameVersion {
    pub version: String,
    pub stable: bool,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct QuiltProfile {
    pub id: String,
    pub main_class: String,
    pub libraries: Vec<QuiltLibrary>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct QuiltLibrary {
    pub name: String,
    pub url: String,
}

pub async fn fetch_quilt_game_versions(client: &HttpClient) -> Result<Vec<GameVersion>, NetError> {
    fetch_quilt_game_versions_from(client, QUILT_META_BASE).await
}

pub async fn fetch_quilt_game_versions_from(
    client: &HttpClient,
    meta_base: &str,
) -> Result<Vec<GameVersion>, NetError> {
    let url = format!("{}/versions/game", meta_base);
    tracing::debug!("Fetching Quilt game versions from {}", url);
    let versions: Vec<QuiltGameVersion> = client.get_json(&url).await?;
    tracing::debug!("Fetched {} Quilt game version(s)", versions.len());

    Ok(versions
        .into_iter()
        .map(|version| GameVersion {
            id: version.version,
            stable: version.stable,
        })
        .collect())
}

pub async fn fetch_quilt_versions(
    client: &HttpClient,
    game_version: &str,
) -> Result<Vec<QuiltLoaderVersion>, NetError> {
    fetch_quilt_versions_from(client, QUILT_META_BASE, game_version).await
}

pub async fn fetch_quilt_versions_from(
    client: &HttpClient,
    meta_base: &str,
    game_version: &str,
) -> Result<Vec<QuiltLoaderVersion>, NetError> {
    let url = format!("{}/versions/loader/{}", meta_base, game_version);
    tracing::debug!("Fetching Quilt loader versions for {}", game_version);
    let mut versions: Vec<QuiltLoaderVersion> = client.get_json(&url).await?;
    versions
        .sort_by(|a, b| super::versions::compare_versions(&b.loader.version, &a.loader.version));
    tracing::debug!(
        "Fetched {} Quilt loader version(s) for {}",
        versions.len(),
        game_version
    );
    Ok(versions)
}

pub async fn fetch_quilt_profile(
    client: &HttpClient,
    game_version: &str,
    loader_version: &str,
) -> Result<QuiltProfile, NetError> {
    fetch_quilt_profile_from(client, QUILT_META_BASE, game_version, loader_version).await
}

pub async fn fetch_quilt_profile_from(
    client: &HttpClient,
    meta_base: &str,
    game_version: &str,
    loader_version: &str,
) -> Result<QuiltProfile, NetError> {
    let url = format!(
        "{}/versions/loader/{}/{}/profile/json",
        meta_base, game_version, loader_version
    );
    tracing::debug!(
        "Fetching Quilt profile for Minecraft {} loader {}",
        game_version,
        loader_version
    );
    client.get_json(&url).await
}

// like fetch_quilt_profile but also returns the raw response bytes so the
// caller can write the upstream JSON byte-for-byte to disk. used by the
// install path so we don't lose data (e.g. any future arguments field) by
// re-serializing through our narrow QuiltProfile struct.
pub async fn fetch_quilt_profile_with_raw(
    client: &HttpClient,
    game_version: &str,
    loader_version: &str,
) -> Result<(QuiltProfile, Vec<u8>), NetError> {
    fetch_quilt_profile_with_raw_from(client, QUILT_META_BASE, game_version, loader_version).await
}

pub async fn fetch_quilt_profile_with_raw_from(
    client: &HttpClient,
    meta_base: &str,
    game_version: &str,
    loader_version: &str,
) -> Result<(QuiltProfile, Vec<u8>), NetError> {
    let url = format!(
        "{}/versions/loader/{}/{}/profile/json",
        meta_base, game_version, loader_version
    );
    tracing::debug!(
        "Fetching raw Quilt profile for Minecraft {} loader {}",
        game_version,
        loader_version
    );
    client.get_json_with_raw(&url, "Quilt profile").await
}

pub async fn download_quilt_libraries(
    client: &HttpClient,
    profile: &QuiltProfile,
    meta_dir: &Path,
) -> Result<(), NetError> {
    tracing::debug!(
        "Resolving {} Quilt libraries into {}",
        profile.libraries.len(),
        crate::storage::MetadataPaths::new(meta_dir)
            .libraries()
            .display()
    );
    let libraries: Vec<_> = profile
        .libraries
        .iter()
        .map(|lib| (lib.name.as_str(), lib.url.as_str()))
        .collect();
    download_loader_libraries(client, libraries, meta_dir, "Quilt").await?;

    tracing::debug!("Quilt library resolution complete for {}", profile.id);
    Ok(())
}