//! Static metadata for every setting in `settings.toml`.
//!
//! The `SETTINGS` slice is emitted at build time by `build.rs` from
//! the crate-local `settings.toml`. Each entry is a `'static`
//! borrow so there's zero runtime allocation and the compiler
//! verifies the schema shape at compile time.
//!
//! Use cases:
//! - Future `aube settings` subcommand to render a CLI version of
//!   pnpm's settings reference page
//! - Sanity tests that verify every `implemented = true` entry still
//!   maps to real behavior in the Rust source
//! - A future docs generator that reads this and emits markdown

/// Metadata for one setting. Every field is `'static` because the
/// containing `SETTINGS` slice is a `const` built from string
/// literals at compile time.
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)] // Fields are consumed by tests and future generators.
pub struct SettingMeta {
    /// pnpm-style canonical name (e.g. `preferFrozenLockfile`, `registries`)
    pub name: &'static str,
    /// One-line summary
    pub description: &'static str,
    /// TOML-ish type spec (e.g. `"bool"`, `"list<string>"`, `"\"highest\" | \"time-based\""`)
    pub type_: &'static str,
    /// Default value rendered as a string (e.g. `"true"`, `"undefined"`)
    pub default: &'static str,
    /// Whether aube currently honors this setting. Transitional: removed at v1.
    pub implemented: bool,
    /// ISO date the setting was added to the registry (`YYYY-MM-DD`)
    pub since: &'static str,
    /// Longer markdown docs body
    pub docs: &'static str,
    /// CLI flags that set this setting
    pub cli_flags: &'static [&'static str],
    /// Environment variables that set this setting
    pub env_vars: &'static [&'static str],
    /// `.npmrc` keys that set this setting (aliases listed together;
    /// the first entry is treated as the canonical spelling)
    pub npmrc_keys: &'static [&'static str],
    /// `pnpm-workspace.yaml` (and `aube-workspace.yaml`) keys that
    /// set this setting.
    pub workspace_yaml_keys: &'static [&'static str],
    /// Example invocations
    pub examples: &'static [&'static str],
    /// Escape hatch for the workspace-level accessor audit in
    /// `tests/accessor_audit.rs`. `true` means "this setting is
    /// honored but not via its generated `resolved::<name>` typed
    /// accessor" — e.g. read through `std::env::var` behind a
    /// hand-rolled `LazyLock`, looked up in `NpmConfig` by string
    /// key, or accepted for pnpm parity with no behavior wired.
    /// Default `false`; the audit fails CI when an `implemented =
    /// true` setting with a supported scalar type has no
    /// `resolved::<name>` call site anywhere in the workspace and
    /// this flag is not set.
    pub typed_accessor_unused: bool,
}

// Pulls in `pub const SETTINGS: &[SettingMeta] = &[...]` generated by build.rs.
include!(concat!(env!("OUT_DIR"), "/settings_meta_data.rs"));

/// Return the full slice of settings, alphabetically sorted by name.
pub fn all() -> &'static [SettingMeta] {
    SETTINGS
}

/// Look up a setting by its canonical pnpm name.
pub fn find(name: &str) -> Option<&'static SettingMeta> {
    SETTINGS.iter().find(|s| s.name == name)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn settings_metadata_has_expected_count() {
        assert!(
            SETTINGS.len() >= 100,
            "expected ≥100 settings, got {}",
            SETTINGS.len()
        );
    }

    #[test]
    fn every_setting_has_required_fields() {
        for s in SETTINGS {
            assert!(!s.name.is_empty(), "empty name in entry");
            assert!(!s.description.is_empty(), "{}: empty description", s.name);
            assert!(!s.type_.is_empty(), "{}: empty type", s.name);
            assert!(!s.since.is_empty(), "{}: empty since", s.name);
        }
    }

    #[test]
    fn known_settings_resolve() {
        assert!(find("registries").is_some());
        assert!(find("preferFrozenLockfile").is_some());
        assert!(find("aubeNoLock").is_some());
        assert!(find("totally-fake-setting").is_none());
    }

    #[test]
    fn find_matches_all() {
        for s in all() {
            assert_eq!(find(s.name).map(|x| x.name), Some(s.name));
        }
    }

    /// Every setting that advertises a `workspaceYaml` source must
    /// actually be readable from `pnpm-workspace.yaml` — i.e. the key
    /// has to deserialize onto `WorkspaceConfig` without falling into
    /// the `#[serde(flatten)] extra` catch-all. Guards against metadata
    /// rotting away from real workspace-config support.
    ///
    /// Lives here (in `aube-settings` — a crate that `aube-manifest`
    /// itself does not depend on) so it's a dev-only parity check; the
    /// dependency edge only exists for tests.
    #[test]
    fn workspace_yaml_keys_deserialize_onto_workspace_config() {
        // Field types on WorkspaceConfig vary, so there's no single
        // YAML value that parses for every field. Try a handful of
        // shapes in turn — if at least one parses AND the key doesn't
        // fall into `extra`, the field is wired up.
        let shapes = ["null", "true", "\"x\"", "0", "{}", "[]"];
        for s in SETTINGS {
            for key in s.workspace_yaml_keys {
                let mut recognized = false;
                let top_level_key = key.split('.').next().unwrap_or(key);
                for shape in shapes {
                    let yaml = format!("{top_level_key}: {shape}\n");
                    if let Ok(cfg) = serde_yaml::from_str::<aube_manifest::WorkspaceConfig>(&yaml)
                        && !cfg.extra.contains_key(top_level_key)
                    {
                        recognized = true;
                        break;
                    }
                }
                assert!(
                    recognized,
                    "{}: workspaceYaml key `{key}` is not a real field on WorkspaceConfig \
                     — it fell through to `extra` (or failed to deserialize entirely). \
                     Either add the field or remove the metadata.",
                    s.name
                );
            }
        }
    }
}
