//! XML and auxiliary header loading. Default mode uses compile-time-embedded
//! bundled copies; `--fetch` downloads from remote Khronos URLs.

use anyhow::{Context, Result};

use crate::bundled;

// ---------------------------------------------------------------------------
// URL bases
// ---------------------------------------------------------------------------

const BASE_GL: &str = "https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/main/xml/";
const BASE_EGL: &str = "https://raw.githubusercontent.com/KhronosGroup/EGL-Registry/main/api/";
const BASE_VK: &str = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/";
const BASE_VK_HEADERS: &str =
    "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/";
const BASE_ANGLE: &str = "https://raw.githubusercontent.com/google/angle/main/scripts/";
const GLSL_EXTS_URL: &str = "https://www.uplinklabs.net/glsl_exts.xml";
const XXHASH_HEAD_URL: &str =
    "https://raw.githubusercontent.com/Cyan4973/xxHash/refs/heads/dev/xxhash.h";

// ---------------------------------------------------------------------------
// SpecSources
// ---------------------------------------------------------------------------

/// Raw XML text for one spec family: the primary doc plus any supplementals.
/// The parser iterates all of them in order, treating supplementals as if they
/// had been merged into the primary before parsing.
pub struct SpecSources {
    pub primary: String,
    pub supplementals: Vec<String>,
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

pub fn load_spec(spec_name: &str, use_fetch: bool) -> Result<SpecSources> {
    if use_fetch {
        fetch_spec(spec_name)
    } else {
        bundled_spec(spec_name)
    }
}

pub fn load_auxiliary_header(path: &str, use_fetch: bool) -> Result<String> {
    if use_fetch {
        if let Some(url) = auxiliary_url(path) {
            return fetch_text(&url)
                .with_context(|| format!("fetching auxiliary header '{}'", path));
        }
        eprintln!(
            "no remote auxiliary URL for '{}'; using bundled version",
            path
        );
    }

    bundled_auxiliary(path).map(str::to_string)
}

// ---------------------------------------------------------------------------
// Bundled mode
// ---------------------------------------------------------------------------

fn bundled_spec(spec_name: &str) -> Result<SpecSources> {
    let primary = match spec_name {
        "gl" => bundled::gl_xml()?,
        "egl" => bundled::egl_xml()?,
        "glx" => bundled::glx_xml()?,
        "wgl" => bundled::wgl_xml()?,
        "vk" => bundled::vk_xml()?,
        other => anyhow::bail!("unknown spec name '{}'", other),
    }
    .to_string();

    let supplementals = match spec_name {
        "gl" => vec![
            bundled::glsl_exts_xml()?.to_string(),
            bundled::gl_angle_ext_xml()?.to_string(),
        ],
        "egl" => vec![bundled::egl_angle_ext_xml()?.to_string()],
        _ => vec![],
    };

    Ok(SpecSources {
        primary,
        supplementals,
    })
}

#[allow(dead_code)]
fn bundled_auxiliary(path: &str) -> Result<&'static str> {
    use bundled::*;
    Ok(match path {
        "xxhash.h" => XXHASH_H,
        "KHR/khrplatform.h" => KHR_PLATFORM_H,
        "EGL/eglplatform.h" => EGL_PLATFORM_H,
        "vk_platform.h" => VK_PLATFORM_H,
        "vk_video/vulkan_video_codecs_common.h" => VK_VIDEO_CODECS_COMMON_H,
        "vk_video/vulkan_video_codec_h264std.h" => VK_VIDEO_H264STD_H,
        "vk_video/vulkan_video_codec_h264std_decode.h" => VK_VIDEO_H264STD_DECODE_H,
        "vk_video/vulkan_video_codec_h264std_encode.h" => VK_VIDEO_H264STD_ENCODE_H,
        "vk_video/vulkan_video_codec_h265std.h" => VK_VIDEO_H265STD_H,
        "vk_video/vulkan_video_codec_h265std_decode.h" => VK_VIDEO_H265STD_DECODE_H,
        "vk_video/vulkan_video_codec_h265std_encode.h" => VK_VIDEO_H265STD_ENCODE_H,
        "vk_video/vulkan_video_codec_av1std.h" => VK_VIDEO_AV1STD_H,
        "vk_video/vulkan_video_codec_av1std_decode.h" => VK_VIDEO_AV1STD_DECODE_H,
        "vk_video/vulkan_video_codec_av1std_encode.h" => VK_VIDEO_AV1STD_ENCODE_H,
        "vk_video/vulkan_video_codec_vp9std.h" => VK_VIDEO_VP9STD_H,
        "vk_video/vulkan_video_codec_vp9std_decode.h" => VK_VIDEO_VP9STD_DECODE_H,
        other => anyhow::bail!("unknown auxiliary header '{}'", other),
    })
}

// ---------------------------------------------------------------------------
// Fetch mode
// ---------------------------------------------------------------------------

fn fetch_spec(spec_name: &str) -> Result<SpecSources> {
    let primary_url = match spec_name {
        "gl" => format!("{}gl.xml", BASE_GL),
        "egl" => format!("{}egl.xml", BASE_EGL),
        "glx" => format!("{}glx.xml", BASE_GL),
        "wgl" => format!("{}wgl.xml", BASE_GL),
        "vk" => format!("{}vk.xml", BASE_VK),
        other => anyhow::bail!("unknown spec name '{}'", other),
    };
    let primary = fetch_text(&primary_url)
        .with_context(|| format!("fetching primary XML for '{}'", spec_name))?;

    let supp_urls: Vec<String> = match spec_name {
        "gl" => vec![
            GLSL_EXTS_URL.to_string(),
            format!("{}gl_angle_ext.xml", BASE_ANGLE),
        ],
        "egl" => vec![format!("{}egl_angle_ext.xml", BASE_ANGLE)],
        _ => vec![],
    };

    let supplementals = supp_urls
        .iter()
        .map(|url| fetch_text(url).with_context(|| format!("fetching supplemental '{}'", url)))
        .collect::<Result<Vec<_>>>()?;

    Ok(SpecSources {
        primary,
        supplementals,
    })
}

fn auxiliary_url(path: &str) -> Option<String> {
    if path.starts_with("vk_video/") || path == "vk_platform.h" {
        Some(format!("{}vulkan/{}", BASE_VK_HEADERS, path))
    } else if path.starts_with("KHR/") || path.starts_with("EGL/") {
        Some(format!("{}{}", BASE_EGL, path))
    } else if path == "xxhash.h" {
        Some(XXHASH_HEAD_URL.to_string())
    } else {
        None
    }
}

fn fetch_text(url: &str) -> Result<String> {
    let resp = reqwest::blocking::get(url)
        .with_context(|| format!("GET {}", url))?
        .error_for_status()
        .with_context(|| format!("HTTP error from {}", url))?;
    Ok(resp.text()?)
}
