use super::types::{Inferred, RELATION};
use crate::corpus::Corpus;
use crate::corpus::graph::{Graph, Resolution};
pub fn lang_problem(lang: &str) -> Option<String> {
let mut parts = lang.split('-');
let first = parts.next().unwrap_or("");
let ok = (2..=3).contains(&first.len())
&& first.chars().all(|c| c.is_ascii_alphabetic())
&& parts.all(|p| (1..=8).contains(&p.len()) && p.chars().all(|c| c.is_ascii_alphanumeric()));
(!ok).then(|| format!("lang '{lang}' is not a language tag (BCP 47) like ko, ja or pt-BR"))
}
pub fn source_ref(c: &Corpus, idx: usize) -> Option<String> {
c.docs[idx].rel().get(RELATION).and_then(|l| l.first().cloned())
}
pub fn source_of(c: &Corpus, g: &Graph, idx: usize) -> Option<usize> {
match source_ref(c, idx) {
Some(r) => match g.resolve(&r) {
Resolution::Doc { idx, .. } => Some(idx),
_ => None,
},
None => inferred(c, idx).and_then(|p| p.source),
}
}
pub fn is_translation(c: &Corpus, idx: usize) -> bool {
source_ref(c, idx).is_some() || position(c, idx).is_some()
}
pub fn lang_of(c: &Corpus, idx: usize) -> Option<String> {
c.docs[idx]
.str_field("lang")
.or_else(|| position(c, idx).map(|p| p.0))
.or_else(|| (!is_translation(c, idx)).then(|| c.cfg.raw.translations.source_lang.clone()).flatten())
}
fn position(c: &Corpus, idx: usize) -> Option<(String, (String, String, String))> {
use crate::config::types::TranslationLayout as L;
let t = &c.cfg.raw.translations;
let d = &c.docs[idx];
let root = spec_dir(c, idx);
match t.layout {
L::None => None,
L::Suffix => t.languages.iter().find_map(|l| {
let base = d.slug.strip_suffix(&format!("-{}", l.to_ascii_lowercase()))?;
(!base.is_empty()).then(|| (l.clone(), (d.id.ty.clone(), root.clone(), base.to_string())))
}),
L::Dir => {
let segs: Vec<&str> = root.split('/').collect();
let at = segs.iter().rposition(|s| t.languages.iter().any(|l| l == s))?;
let lang = segs[at].to_string();
let rest: Vec<&str> = segs.iter().enumerate().filter(|(i, _)| *i != at).map(|(_, s)| *s).collect();
Some((lang, (d.id.ty.clone(), rest.join("/"), d.slug.clone())))
}
}
}
pub fn inferred(c: &Corpus, idx: usize) -> Option<Inferred> {
let (lang, want) = position(c, idx)?;
let candidates: Vec<usize> = (0..c.docs.len())
.filter(|&j| j != idx && position(c, j).is_none())
.filter(|&j| {
let d = &c.docs[j];
d.id.ty == want.0 && d.slug == want.2 && spec_dir(c, j) == want.1
})
.collect();
let source = (candidates.len() == 1).then(|| candidates[0]);
Some(Inferred { lang, source, candidates })
}
fn spec_dir(c: &Corpus, idx: usize) -> String {
c.spec_root_of(idx).map(|r| r.dir.clone()).unwrap_or_default()
}