use crate::grapheme::{form_of, is_joining_type, Grapheme};
use crate::pattern::{LengthGuard, PatternSet, Token, Weight};
use crate::rasm::rasm_matches;
use crate::KashidaPoint;
use icu_properties::props::JoiningType;
fn match_token(token: &Token, graphemes: &[Grapheme], index: usize) -> bool {
let g = &graphemes[index];
match token {
Token::Any => is_joining_type(g.joining_type),
Token::Literal(cp) => g.base_codepoint == *cp,
Token::Group(group) => rasm_matches(*group, g.joining_group, form_of(graphemes, index)),
Token::ExactGroup(group) => g.joining_group == *group,
Token::GroupSet(members) => members.iter().any(|m| match_token(m, graphemes, index)),
Token::NotGroupSet(members) => {
is_joining_type(g.joining_type)
&& !members.iter().any(|m| match_token(m, graphemes, index))
}
}
}
fn guard_matches(guard: Option<LengthGuard>, len: usize) -> bool {
match guard {
None => true,
Some(LengthGuard::Exact(n)) => len == n,
Some(LengthGuard::Min(n)) => len >= n,
Some(LengthGuard::Range { lo, hi }) => len >= lo && len <= hi,
}
}
fn guard_floor(guard: Option<LengthGuard>) -> usize {
match guard {
None => 2,
Some(LengthGuard::Range { lo, .. }) => lo,
Some(LengthGuard::Exact(n) | LengthGuard::Min(n)) => n,
}
}
fn effective_priority(base: u8, min: u8, len: usize, floor: usize) -> u8 {
let value = base as i32 - (len as i32 - floor as i32);
value.max(min as i32) as u8
}
pub(crate) fn resolve_run(
graphemes: &[Grapheme],
run: &[usize],
set: &PatternSet,
) -> Vec<KashidaPoint> {
let len = run.len();
if len < 2 {
return Vec::new();
}
let mut priority: Vec<Option<u8>> = vec![None; len - 1];
let mut suppressed: Vec<bool> = vec![false; len - 1];
for pattern in &set.patterns {
if !guard_matches(pattern.guard, len) {
continue;
}
let floor = guard_floor(pattern.guard);
let m = pattern.tokens.len();
if m > len {
continue;
}
for start in 0..=(len - m) {
if pattern.leading_boundary && start != 0 {
continue;
}
if pattern.trailing_boundary
&& (start + m != len
|| graphemes[run[len - 1]].joining_type == JoiningType::JoinCausing)
{
continue;
}
let matched =
(0..m).all(|k| match_token(&pattern.tokens[k], graphemes, run[start + k]));
if !matched {
continue;
}
for gap in 0..=m {
let weight = match &pattern.weights[gap] {
Some(w) => w,
None => continue,
};
let junction = start as isize + gap as isize - 1;
if junction < 0 || junction > len as isize - 2 {
continue;
}
let junction = junction as usize;
match weight {
Weight::Suppress => suppressed[junction] = true,
Weight::Priority { base, min } => {
let value = effective_priority(*base, *min, len, floor);
let cur = priority[junction];
if cur.is_none_or(|c| value > c) {
priority[junction] = Some(value);
}
}
}
}
}
}
let mut out = Vec::new();
for junction in 0..len - 1 {
if let Some(value) = priority[junction] {
if suppressed[junction] {
continue;
}
out.push(KashidaPoint {
index: run[junction] as u32,
priority: value,
});
}
}
out
}