use crate::engine::{CompiledPolicy, Hit};
use crate::extract::Doc;
use crate::input::Prepared;
use crate::Config;
pub const HANDLED: &[&str] = &["SLOP-D001", "SLOP-D002", "SLOP-D003", "SLOP-D004"];
fn count_rule_hits(cp: &CompiledPolicy, hits: &[Hit], ids: &[String]) -> u64 {
hits.iter()
.filter(|h| ids.iter().any(|id| &cp.pkg.rules[h.rule].id == id))
.count() as u64
}
pub fn evaluate(
cp: &CompiledPolicy,
prepared: &Prepared,
doc: &Doc,
config: &Config,
hits: &mut Vec<Hit>,
) {
let whole = 0..prepared.text.len().min(1);
for id in ["SLOP-D001", "SLOP-D004"] {
let Some(idx) = super::active(cp, config, id) else {
continue;
};
let rule = &cp.pkg.rules[idx];
let ids: Vec<String> = rule
.params
.as_table()
.and_then(|t| t.get("count_rules"))
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default();
let threshold = super::param_i64(rule, "threshold").unwrap_or(3) as u64;
let count = count_rule_hits(cp, hits, &ids);
let per_document = rule
.params
.as_table()
.and_then(|t| t.get("per_document"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
let fires = if per_document {
count >= threshold
} else {
let per_words = super::param_i64(rule, "per_words").unwrap_or(500) as u64;
let words = doc.stats.word_count;
words > 0 && count >= threshold && count * per_words >= threshold * words
};
if fires {
hits.push(Hit::new(idx, whole.clone()));
}
}
if let Some(idx) = super::active(cp, config, "SLOP-D002") {
let rule = &cp.pkg.rules[idx];
let min_bullets = super::param_i64(rule, "min_bullets").unwrap_or(8) as u64;
let min_pct = super::param_i64(rule, "min_link_bullet_pct").unwrap_or(60) as u64;
if doc.stats.bullets >= min_bullets
&& doc.stats.bullets_with_link * 100 >= min_pct * doc.stats.bullets
{
hits.push(Hit::new(idx, whole.clone()));
}
}
if let Some(idx) = super::active(cp, config, "SLOP-D003") {
let rule = &cp.pkg.rules[idx];
let min_paragraphs = super::param_i64(rule, "min_paragraphs").unwrap_or(5) as usize;
let cv_pct = super::param_i64(rule, "max_length_cv_pct").unwrap_or(20) as u128;
let words: Vec<u64> = doc
.stats
.paragraph_words
.iter()
.copied()
.filter(|&w| w > 0)
.collect();
if words.len() >= min_paragraphs {
let n = words.len() as u128;
let s: u128 = words.iter().map(|&w| w as u128).sum();
let sq: u128 = words.iter().map(|&w| (w as u128) * (w as u128)).sum();
if s > 0 && 10_000 * (n * sq - s * s) <= cv_pct * cv_pct * s * s {
hits.push(Hit::new(idx, whole.clone()));
}
}
}
}