use regex::Regex;
use std::sync::LazyLock;
use crate::math::clamp;
use crate::types::belief::AffectValence;
#[derive(Debug, Clone)]
pub struct ArousalResult {
pub arousal: f64,
pub valence: AffectValence,
}
static NEGATIVE_HIGH: LazyLock<Vec<Regex>> = LazyLock::new(|| {
vec![
Regex::new(r"(?i)\b(frustrat|angry|furious|hate|terrible|awful|worst|disaster|catastroph|panic|terrif)\w*\b").expect("valid regex"),
]
});
static NEGATIVE_MOD: LazyLock<Vec<Regex>> = LazyLock::new(|| {
vec![
Regex::new(r"(?i)\b(confus|stuck|struggl|annoying|difficult|weird|broken|fail|stress|anxious|worried|concern|nervous|overwhelm|upset|lost|wrong)\w*\b").expect("valid regex"),
Regex::new(r"(?i)\b(hours?|days?|forever|all day|all night|so long|too long|ages)\b.*\b(nothing|no luck|can'?t|won'?t|doesn'?t|don'?t|not work)\w*\b").expect("valid regex"),
Regex::new(r"(?i)\b(nothing|no luck|can'?t|won'?t|doesn'?t|don'?t|not)\b.*\b(work|help|fix|solve|progress)\w*\b").expect("valid regex"),
Regex::new(r"(?i)\b(deadline|pressure|rush|hurry|urgent|emergency|crisis)\w*\b").expect("valid regex"),
]
});
static POSITIVE_HIGH: LazyLock<Vec<Regex>> = LazyLock::new(|| {
vec![
Regex::new(r"(?i)\b(amazing|awesome|perfect|excellent|brilliant|fantastic|incredible)\b").expect("valid regex"),
Regex::new(r"(?i)\b(finally|eureka|figured it out|nailed it|breakthrough)\b").expect("valid regex"),
]
});
static POSITIVE_MOD: LazyLock<Vec<Regex>> = LazyLock::new(|| {
vec![
Regex::new(r"(?i)\b(good|great|nice|helpful|works|working|better|improved|solved)\b").expect("valid regex"),
Regex::new(r"(?i)\b(thanks|thank you|appreciate)\b").expect("valid regex"),
]
});
static NEGATION_NEAR_NEGATIVE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?i)\b(not|isn'?t|wasn'?t|aren'?t|weren'?t|haven'?t|hasn'?t|couldn'?t|shouldn'?t|wouldn'?t|never)\s+(?:\w+\s+){0,2}(frustrat|angry|furious|hate|terrible|awful|worst|disaster|catastroph|confus|stuck|struggl|annoying|difficult|weird|broken|fail)\w*").expect("valid regex")
});
pub fn compute_arousal(message: &str) -> ArousalResult {
let mut arousal = 0.0_f64;
let mut positive_score = 0.0_f64;
let mut negative_score = 0.0_f64;
let has_negation = NEGATION_NEAR_NEGATIVE.is_match(message);
let excl_count = message.matches('!').count();
if excl_count >= 3 {
arousal += 0.2;
} else {
arousal += excl_count as f64 * 0.05;
}
let alpha_chars: Vec<char> = message.chars().filter(|c| c.is_alphabetic()).collect();
let upper_count = alpha_chars.iter().filter(|c| c.is_uppercase()).count();
if alpha_chars.len() >= 8 {
let caps_ratio = upper_count as f64 / alpha_chars.len() as f64;
if caps_ratio >= 0.5 {
arousal += 0.2;
} else if caps_ratio >= 0.3 {
arousal += 0.1;
}
}
let neg_high = NEGATIVE_HIGH.iter().any(|p| p.is_match(message));
if neg_high && !has_negation {
negative_score += 0.4;
arousal += 0.3;
}
let neg_mod = NEGATIVE_MOD.iter().any(|p| p.is_match(message));
if neg_mod && !has_negation {
negative_score += 0.2;
arousal += 0.15;
}
let pos_high = POSITIVE_HIGH.iter().any(|p| p.is_match(message));
if pos_high {
positive_score += 0.4;
arousal += 0.3;
}
let pos_mod = POSITIVE_MOD.iter().any(|p| p.is_match(message));
if pos_mod {
positive_score += 0.2;
arousal += 0.1;
}
let question_count = message.matches('?').count();
if question_count >= 3 {
arousal += 0.1;
}
if message.contains("...") || message.contains("!!!") {
arousal += 0.05;
}
let valence = if negative_score > positive_score {
AffectValence::Negative
} else if positive_score > negative_score {
AffectValence::Positive
} else {
AffectValence::Neutral
};
ArousalResult {
arousal: clamp(arousal, 0.0, 1.0),
valence,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn neutral_message_low_arousal() {
let r = compute_arousal("Can you help me with this?");
assert!(r.arousal < 0.3);
assert_eq!(r.valence, AffectValence::Neutral);
}
#[test]
fn frustrated_message_high_arousal() {
let r = compute_arousal("I'm so frustrated, nothing works!!!");
assert!(r.arousal >= 0.4);
assert_eq!(r.valence, AffectValence::Negative);
}
#[test]
fn excited_message_positive() {
let r = compute_arousal("This is amazing! Finally figured it out!");
assert!(r.arousal >= 0.3);
assert_eq!(r.valence, AffectValence::Positive);
}
#[test]
fn caps_boost_arousal() {
let r = compute_arousal("WHY IS THIS NOT WORKING");
assert!(r.arousal >= 0.2);
}
#[test]
fn negation_inverts_negative() {
let r = compute_arousal("I'm not frustrated at all");
assert_eq!(r.valence, AffectValence::Neutral);
}
#[test]
fn arousal_clamped() {
let r = compute_arousal("FRUSTRATED!!! TERRIBLE!!! EVERYTHING IS BROKEN!!! HATE THIS!!! WHY???");
assert!(r.arousal <= 1.0);
}
}