//! The curated named-generator vocabulary (ADR-0048 D9).
//!
//! This is the **single source of truth** for "what generator names can
//! a learner write after `set <col> as …`", shared by three consumers
//! (mirroring `KNOWN_SQL_FUNCTIONS`, ADR-0022 Amд6):
//!
//! - **Tab completion** — the `seed … set <col> as ⟨here⟩` slot offers
//!   these names (`src/completion.rs`).
//! - **The typing-time validity indicator (ADR-0027)** — an unknown
//!   name after `as` is flagged `[ERR]` while typing.
//! - **The executor** — `db.rs::do_seed` maps a name to a [`Generator`]
//!   via [`generator_for_name`]; an unknown name is a friendly error.
//!
//! The list is a deliberately *curated pedagogical set* — the generators
//! a learner reaches for, not every internal [`Generator`] variant
//! (stateful markers like `ForeignKeySample` are executor-only and have
//! no name). It is lowercase + sorted (pinned by a unit test).

use crate::seed::Generator;

/// The curated generator names, lowercase and **sorted** (invariant
/// pinned by a test — completion relies on stable order and a
/// case-insensitive prefix match against these canonical spellings).
pub const KNOWN_GENERATORS: &[&str] = &[
    "age",
    "bool",
    "city",
    "color",
    "company",
    "country",
    "date",
    "datetime",
    "email",
    "first_name",
    "job",
    "last_name",
    "name",
    "paragraph",
    "password",
    "phone",
    "price",
    "product",
    "sentence",
    "state",
    "street",
    "url",
    "username",
    "zip",
];

/// Map a generator name (case-insensitive) to its [`Generator`].
///
/// `None` for an unrecognised name — the executor turns that into a
/// friendly "unknown generator" error naming the curated set. A couple
/// of common spelling variants (`firstname`, `lastname`, `colour`,
/// `full_name`) are accepted as aliases even though only the canonical
/// spelling is offered for completion.
#[must_use]
pub fn generator_for_name(name: &str) -> Option<Generator> {
    let n = name.to_ascii_lowercase();
    let g = match n.as_str() {
        "name" | "full_name" => Generator::FullName,
        "first_name" | "firstname" => Generator::FirstName,
        "last_name" | "lastname" | "surname" => Generator::LastName,
        "email" => Generator::Email,
        "username" => Generator::Username,
        "password" => Generator::Password,
        "phone" => Generator::Phone,
        "city" => Generator::City,
        "country" => Generator::Country,
        "state" => Generator::StateName,
        "street" => Generator::Street,
        "zip" => Generator::ZipCode,
        "company" => Generator::Company,
        "job" => Generator::JobTitle,
        "product" => Generator::ProductName,
        "sentence" => Generator::Sentence,
        "paragraph" => Generator::Paragraph,
        "url" => Generator::Url,
        "color" | "colour" => Generator::HexColor,
        "price" => Generator::CurrencyAmount,
        "age" => Generator::Age,
        "date" => Generator::DateRecent,
        "datetime" => Generator::DateTimeRecent,
        "bool" => Generator::Boolean,
        _ => return None,
    };
    Some(g)
}

/// Whether `partial` is a case-insensitive prefix of at least one known
/// generator name.
///
/// An empty `partial` matches every generator (it is a prefix of all) —
/// mirrors `is_known_function_prefix`. Used by the validity indicator to
/// avoid flagging a still-being-typed name.
#[must_use]
pub fn is_known_generator_prefix(partial: &str) -> bool {
    let lowered = partial.to_ascii_lowercase();
    KNOWN_GENERATORS.iter().any(|g| g.starts_with(&lowered))
}

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

    #[test]
    fn known_generators_is_sorted_and_lowercase() {
        let mut sorted = KNOWN_GENERATORS.to_vec();
        sorted.sort_unstable();
        assert_eq!(KNOWN_GENERATORS, sorted.as_slice(), "must be sorted");
        for g in KNOWN_GENERATORS {
            assert_eq!(*g, g.to_ascii_lowercase(), "must be lowercase: {g}");
        }
    }

    #[test]
    fn every_listed_name_maps_to_a_generator() {
        for g in KNOWN_GENERATORS {
            assert!(
                generator_for_name(g).is_some(),
                "listed generator name `{g}` has no mapping"
            );
        }
    }

    #[test]
    fn mapping_is_case_insensitive_and_has_aliases() {
        assert_eq!(generator_for_name("EMAIL"), Some(Generator::Email));
        assert_eq!(generator_for_name("FirstName"), Some(Generator::FirstName));
        assert_eq!(generator_for_name("colour"), Some(Generator::HexColor));
        assert_eq!(generator_for_name("full_name"), Some(Generator::FullName));
    }

    #[test]
    fn unknown_name_has_no_mapping() {
        assert_eq!(generator_for_name("bogus"), None);
        assert_eq!(generator_for_name(""), None);
    }

    #[test]
    fn prefix_check_matches_known_and_rejects_unknown() {
        assert!(is_known_generator_prefix("ema"));
        assert!(is_known_generator_prefix("EMA"));
        assert!(is_known_generator_prefix("")); // empty is a prefix of all
        assert!(!is_known_generator_prefix("zzz"));
    }
}
