use crate::report::{OrphanFact, SimilarAtoms, label};
use crate::unsat::key;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use elenchus_compiler::{AtomId, AtomKey, Compiled, levenshtein};
pub(crate) fn orphan_facts(c: &Compiled) -> Vec<OrphanFact> {
let mut referenced = vec![false; c.atoms.len()];
for clause in &c.clauses {
for l in &clause.lits {
referenced[l.atom as usize] = true;
}
}
for r in &c.rules {
for l in r.antecedent.iter().chain(r.consequent.iter()) {
referenced[l.atom as usize] = true;
}
}
for &a in &c.consumed {
referenced[a as usize] = true;
}
let mut out: Vec<OrphanFact> = c
.facts
.iter()
.filter(|f| !referenced[f.atom as usize])
.map(|f| OrphanFact {
atom: label(c, f.atom),
value: f.value,
origin: f.origin.clone(),
})
.collect();
out.sort_by_key(|o| key(&o.origin));
out
}
pub(crate) fn similar_atom_pairs(c: &Compiled) -> Vec<SimilarAtoms> {
let folded: Vec<Vec<char>> = c.atoms.iter().map(fold_atom).collect();
let cased: Vec<bool> = folded.iter().map(|f| is_cased_alphabetic(f)).collect();
let mut consumed = vec![false; c.atoms.len()];
for &a in &c.consumed {
consumed[a as usize] = true;
}
let mut out = Vec::new();
for i in 0..c.atoms.len() {
if consumed[i] {
continue;
}
for j in (i + 1)..c.atoms.len() {
if consumed[j] {
continue;
}
if let Some(reason) = atoms_look_similar(
&c.atoms[i],
&folded[i],
cased[i],
&c.atoms[j],
&folded[j],
cased[j],
) {
out.push(SimilarAtoms {
a: label(c, i as AtomId),
b: label(c, j as AtomId),
reason,
});
}
}
}
out
}
pub(crate) fn fold_atom(k: &AtomKey) -> Vec<char> {
let mut raw = String::new();
raw.push_str(&k.subject);
if let Some(p) = &k.predicate {
raw.push(' ');
raw.push_str(p);
}
if let Some(o) = &k.object {
raw.push(' ');
raw.push_str(o);
}
let mut out: Vec<char> = Vec::new();
let mut prev_space = false;
for ch in raw.chars() {
if ch == '_' || ch.is_whitespace() {
if !prev_space && !out.is_empty() {
out.push(' ');
prev_space = true;
}
} else {
for lc in ch.to_lowercase() {
out.push(lc);
}
prev_space = false;
}
}
if out.last() == Some(&' ') {
out.pop();
}
out
}
pub(crate) fn is_cased_alphabetic(folded: &[char]) -> bool {
folded.iter().all(|&c| c == ' ' || c.is_lowercase())
}
pub(crate) fn atoms_look_similar(
ka: &AtomKey,
fa: &[char],
cased_a: bool,
kb: &AtomKey,
fb: &[char],
cased_b: bool,
) -> Option<&'static str> {
if fa == fb && ka.domain == kb.domain {
return Some("same name up to case, '_', or spaces");
}
if !cased_a || !cased_b || ka.domain != kb.domain || ka.subject != kb.subject {
return None;
}
if fa.len().abs_diff(fb.len()) > 1 {
return None; }
let min_len = fa.len().min(fb.len());
if min_len >= 5 && levenshtein(fa, fb) == 1 {
return Some("looks like a one-character typo of each other");
}
None
}