//! Curated set of SQL function names the playground recognises
//! (ADR-0022 Amendment 6, issues #15 / #16).
//!
//! This is the single source of truth for "what SQL function names
//! does this playground know about", shared by two consumers:
//!
//! - **Tab completion** (issue #15): at a `sql_expr_ident` slot the
//!   completion engine offers these as `CandidateKind::Function`
//!   candidates, so a learner can discover `sum` / `upper` / … without
//!   already knowing them.
//! - **The typing-time column-typo hint** (issue #16): at the same
//!   slot, `invalid_ident_at_cursor` flags a partial as "no such
//!   column" only when it matches neither a schema column *nor* a
//!   known function name — so a genuine typo still warns early while
//!   a real function name like `sum` does not get mis-flagged.
//!
//! Scope (ADR-0031 §1): the SQL expression grammar admits any
//! function-call *shape* without knowing which names are real — the
//! walker is a structural matcher, not an evaluator. This list is a
//! deliberately *curated pedagogical set*, not "every SQLite
//! built-in". It covers the aggregates a learner meets first plus the
//! common scalar functions, and is the allow-list / discovery source
//! layered on top of that shape-only grammar.
//!
//! Deliberately excluded: `cast`. SQLite's `CAST` uses the
//! `CAST(expr AS type)` syntax, which the expression grammar does
//! **not** parse as a function call (function args are expressions,
//! and `expr AS type` is not one). Offering `cast` as a completion
//! candidate would surface a name that does not parse in the call
//! shape, so it stays out of the set until the grammar grows a
//! dedicated `CAST` form.

/// The curated SQL function names, lowercase and sorted.
///
/// Sorted + lowercase is an invariant (pinned by a unit test): the
/// completion engine relies on a stable order, and the prefix match
/// is case-insensitive against these canonical lowercase spellings.
///
/// Grouping (all plain `name(args)` call shapes):
/// - **Aggregates:** `avg`, `count`, `max`, `min`, `sum`.
/// - **Common scalars:** `abs`, `coalesce`, `length`, `lower`,
///   `round`, `substr`, `trim`, `upper`.
/// - **Broader scalars:** `date`, `datetime`, `hex`, `ifnull`,
///   `instr`, `nullif`, `random`, `replace`, `strftime`, `typeof`.
pub const KNOWN_SQL_FUNCTIONS: &[&str] = &[
    "abs", "avg", "coalesce", "count", "date", "datetime", "hex", "ifnull", "instr", "length",
    "lower", "max", "min", "nullif", "random", "replace", "round", "strftime", "substr", "sum",
    "trim", "typeof", "upper",
];

/// Whether `partial` is a case-insensitive prefix of at least one
/// known function name.
///
/// An empty `partial` matches every function (it is a prefix of
/// all), mirroring the empty-prefix behaviour of the keyword /
/// identifier completion sources. Used by `invalid_ident_at_cursor`
/// to decide whether a partial at a `sql_expr_ident` slot might still
/// resolve to a function name (and so must not be flagged as an
/// unknown column).
#[must_use]
pub fn is_known_function_prefix(partial: &str) -> bool {
    let lowered = partial.to_lowercase();
    KNOWN_SQL_FUNCTIONS.iter().any(|f| f.starts_with(&lowered))
}

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

    #[test]
    fn list_is_sorted_and_lowercase() {
        // The completion engine and prefix matcher both rely on the
        // canonical sorted-lowercase invariant.
        for f in KNOWN_SQL_FUNCTIONS {
            assert_eq!(
                *f,
                f.to_lowercase(),
                "function name `{f}` must be lowercase",
            );
        }
        let mut sorted = KNOWN_SQL_FUNCTIONS.to_vec();
        sorted.sort_unstable();
        assert_eq!(
            sorted.as_slice(),
            KNOWN_SQL_FUNCTIONS,
            "KNOWN_SQL_FUNCTIONS must be declared in sorted order",
        );
    }

    #[test]
    fn list_has_no_duplicates() {
        let mut seen = std::collections::HashSet::new();
        for f in KNOWN_SQL_FUNCTIONS {
            assert!(seen.insert(*f), "duplicate function name `{f}`");
        }
    }

    #[test]
    fn cast_is_excluded() {
        // `CAST(expr AS type)` is not a plain call shape the grammar
        // parses — it must not be offered as a candidate (faithfulness
        // to ADR-0031's call-shape grammar).
        assert!(
            !KNOWN_SQL_FUNCTIONS.contains(&"cast"),
            "`cast` is not a plain-call function and must stay out of the set",
        );
    }

    #[test]
    fn prefix_match_is_case_insensitive() {
        assert!(is_known_function_prefix("su")); // sum, substr
        assert!(is_known_function_prefix("SU")); // case-folded
        assert!(is_known_function_prefix("sum")); // exact
        assert!(is_known_function_prefix("UPP")); // upper
    }

    #[test]
    fn empty_prefix_matches_all() {
        assert!(is_known_function_prefix(""));
    }

    #[test]
    fn unknown_prefix_does_not_match() {
        assert!(!is_known_function_prefix("zzz"));
        assert!(!is_known_function_prefix("xqz"));
        // A genuine column-typo shape that prefixes no function.
        assert!(!is_known_function_prefix("Agx"));
    }
}
