//! Built-in validation rule functions.
//!
//! Each function appends an error to `errors` if the rule is violated.
//! Called by the code generated by `#[derive(Validate)]`.

use crate::errors::ValidationErrors;

// ── presence ──────────────────────────────────────────────────────────────────

/// The field must be non-empty.
pub fn check_required_str(val: &str, field: &str, errors: &mut ValidationErrors) {
    if val.trim().is_empty() {
        errors.add(field, &msg_required(field));
    }
}

/// An `Option<String>` must be `Some` and non-empty.
pub fn check_required_opt(val: Option<&str>, field: &str, errors: &mut ValidationErrors) {
    match val {
        None | Some("") => errors.add(field, &msg_required(field)),
        Some(s) if s.trim().is_empty() => errors.add(field, &msg_required(field)),
        _ => {}
    }
}

// ── format ────────────────────────────────────────────────────────────────────

/// Validate email format (must contain `@` with a `.` in the domain part).
pub fn check_email(val: &str, field: &str, errors: &mut ValidationErrors) {
    let ok = {
        let mut parts = val.splitn(2, '@');
        let local = parts.next().unwrap_or("");
        let domain = parts.next().unwrap_or("");
        !local.is_empty()
            && domain.contains('.')
            && !domain.starts_with('.')
            && !domain.ends_with('.')
    };
    if !ok {
        errors.add(field, &msg_email(field));
    }
}

/// Validate URL format (must start with `http://` or `https://`).
pub fn check_url(val: &str, field: &str, errors: &mut ValidationErrors) {
    if !val.starts_with("http://") && !val.starts_with("https://") {
        errors.add(field, &msg_url(field));
    }
}

/// The string must parse as a finite number.
pub fn check_numeric_str(val: &str, field: &str, errors: &mut ValidationErrors) {
    if !val.parse::<f64>().map(|n| n.is_finite()).unwrap_or(false) {
        errors.add(field, &msg_numeric(field));
    }
}

// ── string length ─────────────────────────────────────────────────────────────

/// String character count must be ≥ `min`.
pub fn check_min_len(val: &str, min: usize, field: &str, errors: &mut ValidationErrors) {
    if val.chars().count() < min {
        errors.add(field, &msg_min_length(field, min));
    }
}

/// String character count must be ≤ `max`.
pub fn check_max_len(val: &str, max: usize, field: &str, errors: &mut ValidationErrors) {
    if val.chars().count() > max {
        errors.add(field, &msg_max_length(field, max));
    }
}

// ── numeric bounds ────────────────────────────────────────────────────────────

/// Numeric value must be ≥ `min`.
pub fn check_min_num(val: f64, min: f64, field: &str, errors: &mut ValidationErrors) {
    if val < min {
        errors.add(field, &msg_min(field, min));
    }
}

/// Numeric value must be ≤ `max`.
pub fn check_max_num(val: f64, max: f64, field: &str, errors: &mut ValidationErrors) {
    if val > max {
        errors.add(field, &msg_max(field, max));
    }
}

// ── cross-field ───────────────────────────────────────────────────────────────

/// `val` must equal `other`.
pub fn check_same(val: &str, other: &str, field: &str, errors: &mut ValidationErrors) {
    if val != other {
        errors.add(field, &msg_same(field));
    }
}

// ── membership ────────────────────────────────────────────────────────────────

/// `val` must be one of the strings in `list`.
pub fn check_in_list(val: &str, list: &[&str], field: &str, errors: &mut ValidationErrors) {
    if !list.contains(&val) {
        errors.add(field, &msg_in(field, list));
    }
}

/// `val` must NOT be any of the strings in `list`.
pub fn check_not_in_list(val: &str, list: &[&str], field: &str, errors: &mut ValidationErrors) {
    if list.contains(&val) {
        errors.add(field, &msg_not_in(field, list));
    }
}

/// `val` must be a syntactically valid UUID (any version).
///
/// Accepts the standard hyphenated form: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
pub fn check_uuid(val: &str, field: &str, errors: &mut ValidationErrors) {
    let ok = val.len() == 36 && {
        let bytes = val.as_bytes();
        bytes[8] == b'-'
            && bytes[13] == b'-'
            && bytes[18] == b'-'
            && bytes[23] == b'-'
            && val[..8].chars().all(|c| c.is_ascii_hexdigit())
            && val[9..13].chars().all(|c| c.is_ascii_hexdigit())
            && val[14..18].chars().all(|c| c.is_ascii_hexdigit())
            && val[19..23].chars().all(|c| c.is_ascii_hexdigit())
            && val[24..].chars().all(|c| c.is_ascii_hexdigit())
    };
    if !ok {
        errors.add(field, &msg_uuid(field));
    }
}

/// `val` must equal `other` — typically used for password confirmation.
///
/// This is an alias for [`check_same`] with a more intention-revealing name.
pub fn check_confirmed(val: &str, other: &str, field: &str, errors: &mut ValidationErrors) {
    check_same(val, other, field, errors);
}

/// A `Vec<T>` (or any slice) must have at least `min` elements.
pub fn check_min_items<T>(items: &[T], min: usize, field: &str, errors: &mut ValidationErrors) {
    if items.len() < min {
        errors.add(field, &msg_min_items(field, min));
    }
}

/// A `Vec<T>` (or any slice) must have at most `max` elements.
pub fn check_max_items<T>(items: &[T], max: usize, field: &str, errors: &mut ValidationErrors) {
    if items.len() > max {
        errors.add(field, &msg_max_items(field, max));
    }
}

// ── Message helpers ───────────────────────────────────────────────────────────
//
// Each helper tries rok_i18n::try_translate() when the `i18n` feature is on.
// Falls back to the hardcoded English string when outside an I18nLayer scope
// or when the feature is disabled.

fn msg_required(field: &str) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate("validation.required", &[("field", field.to_string())])
    {
        return s;
    }
    format!("The {field} field is required.")
}

fn msg_email(field: &str) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate("validation.email", &[("field", field.to_string())]) {
        return s;
    }
    format!("The {field} field must be a valid email address.")
}

fn msg_url(field: &str) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate("validation.url", &[("field", field.to_string())]) {
        return s;
    }
    format!("The {field} field must be a valid URL.")
}

fn msg_numeric(field: &str) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate("validation.numeric", &[("field", field.to_string())])
    {
        return s;
    }
    format!("The {field} field must be a number.")
}

fn msg_min_length(field: &str, min: usize) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate(
        "validation.min_length",
        &[("field", field.to_string()), ("min", min.to_string())],
    ) {
        return s;
    }
    format!("The {field} field must be at least {min} character(s).")
}

fn msg_max_length(field: &str, max: usize) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate(
        "validation.max_length",
        &[("field", field.to_string()), ("max", max.to_string())],
    ) {
        return s;
    }
    format!("The {field} field may not be greater than {max} character(s).")
}

fn msg_min(field: &str, min: f64) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate(
        "validation.min",
        &[("field", field.to_string()), ("min", min.to_string())],
    ) {
        return s;
    }
    format!("The {field} field must be at least {min}.")
}

fn msg_max(field: &str, max: f64) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate(
        "validation.max",
        &[("field", field.to_string()), ("max", max.to_string())],
    ) {
        return s;
    }
    format!("The {field} field may not be greater than {max}.")
}

fn msg_same(field: &str) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate("validation.same", &[("field", field.to_string())]) {
        return s;
    }
    format!("The {field} field confirmation does not match.")
}

fn msg_in(field: &str, list: &[&str]) -> String {
    #[cfg(feature = "i18n")]
    {
        let options = list.join(", ");
        if let Some(s) = rok_i18n::try_translate(
            "validation.in",
            &[("field", field.to_string()), ("options", options)],
        ) {
            return s;
        }
    }
    let options = list.join(", ");
    format!("The {field} field must be one of: {options}.")
}

fn msg_not_in(field: &str, list: &[&str]) -> String {
    #[cfg(feature = "i18n")]
    {
        let options = list.join(", ");
        if let Some(s) = rok_i18n::try_translate(
            "validation.not_in",
            &[("field", field.to_string()), ("options", options)],
        ) {
            return s;
        }
    }
    let options = list.join(", ");
    format!("The {field} field must not be one of: {options}.")
}

fn msg_uuid(field: &str) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate("validation.uuid", &[("field", field.to_string())]) {
        return s;
    }
    format!("The {field} field must be a valid UUID.")
}

fn msg_min_items(field: &str, min: usize) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate(
        "validation.min_items",
        &[("field", field.to_string()), ("min", min.to_string())],
    ) {
        return s;
    }
    format!("The {field} field must contain at least {min} item(s).")
}

fn msg_max_items(field: &str, max: usize) -> String {
    #[cfg(feature = "i18n")]
    if let Some(s) = rok_i18n::try_translate(
        "validation.max_items",
        &[("field", field.to_string()), ("max", max.to_string())],
    ) {
        return s;
    }
    format!("The {field} field may not contain more than {max} item(s).")
}
