use serde::Deserialize;

#[derive(Debug, Clone, Deserialize)]
pub struct BuildConfig {
    pub build: BuildMeta,
    #[serde(default)]
    pub plugin: Vec<PluginEntry>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct BuildMeta {
    pub output: String,
    #[serde(default = "default_rust_version")]
    pub rust_version: String,
    /// Path to folk-ext crate (for local builds). If not set, uses crates.io.
    pub folk_ext_path: Option<String>,
    /// Path to folk-api crate (for local builds with unpublished changes).
    pub folk_api_path: Option<String>,
}

fn default_rust_version() -> String {
    "1.85".into()
}

#[derive(Debug, Clone, Deserialize)]
pub struct PluginEntry {
    pub crate_name: String,
    pub version: Option<String>,
    pub path: Option<String>,
    pub git: Option<String>,
    #[serde(default)]
    pub config_key: String,
    /// Cargo features to enable on this plugin dependency (e.g. ["embedded"]).
    /// Empty ⇒ the plugin's default features.
    #[serde(default)]
    pub features: Vec<String>,
}

pub fn load(path: &str) -> anyhow::Result<BuildConfig> {
    let text = std::fs::read_to_string(path)?;
    Ok(toml::from_str(&text)?)
}

/// A release build manifest (`build-manifest.toml`). Unlike [`BuildConfig`],
/// the manifest is the source of truth checked into folk-releases: it lists
/// plugin version specs (`0.4.*`) and optional Cargo features per plugin, and
/// CI turns it into a [`BuildConfig`] to drive the build.
///
/// Plugins may be declared in two forms under `[plugins]`:
/// * string form — `folk-plugin-http = "0.4.*"` (default features)
/// * table form — `[plugins.folk-plugin-jobs]` with `version` + `features`
#[derive(Debug, Clone, Deserialize)]
pub struct Manifest {
    #[serde(default)]
    pub builder: BuilderMeta,
    #[serde(default)]
    pub plugins: toml::Table,
}

#[derive(Debug, Clone, Default, Deserialize)]
pub struct BuilderMeta {
    pub version: Option<String>,
}

/// Strip a trailing `.*`/`*` wildcard so the value is a Cargo version
/// requirement (`0.4.*` → `0.4`, which Cargo reads as `^0.4`).
fn version_req(spec: &str) -> String {
    spec.trim_end_matches('*').trim_end_matches('.').to_string()
}

/// Parse a `build-manifest.toml` and convert it into a [`BuildConfig`] ready
/// for codegen. Plugin order follows the manifest's `[plugins]` table (sorted
/// by name); registration order does not affect runtime behaviour.
pub fn load_manifest(path: &str) -> anyhow::Result<BuildConfig> {
    let text = std::fs::read_to_string(path)?;
    manifest_from_str(&text)
}

/// Convert manifest TOML text into a [`BuildConfig`]. See [`load_manifest`].
pub fn manifest_from_str(text: &str) -> anyhow::Result<BuildConfig> {
    let manifest: Manifest = toml::from_str(text)?;

    let mut plugin = Vec::new();
    for (name, spec) in &manifest.plugins {
        let (ver_spec, features) = match spec {
            toml::Value::String(s) => (s.clone(), Vec::new()),
            toml::Value::Table(t) => {
                let version = t
                    .get("version")
                    .and_then(toml::Value::as_str)
                    .unwrap_or("0.1.*")
                    .to_string();
                let features = t
                    .get("features")
                    .and_then(toml::Value::as_array)
                    .map(|arr| {
                        arr.iter()
                            .filter_map(|v| v.as_str().map(str::to_string))
                            .collect()
                    })
                    .unwrap_or_default();
                (version, features)
            }
            other => anyhow::bail!(
                "plugin \"{name}\": expected a version string or a table, got {}",
                other.type_str()
            ),
        };

        let config_key = name
            .strip_prefix("folk-plugin-")
            .unwrap_or(name)
            .to_string();
        plugin.push(PluginEntry {
            crate_name: name.clone(),
            version: Some(version_req(&ver_spec)),
            path: None,
            git: None,
            config_key,
            features,
        });
    }

    Ok(BuildConfig {
        build: BuildMeta {
            output: "folk".into(),
            rust_version: default_rust_version(),
            folk_ext_path: None,
            folk_api_path: None,
        },
        plugin,
    })
}
