use crate::detail::container::{find_from, find_prev_token, mark};
use crate::detail::element::{build_element_value, underscore_is_separator};
use crate::detail::token::{
is_close_bracket_token, is_dash_token, is_delimiter_token, is_free_token, is_identified_token,
is_not_delimiter_token, is_open_bracket_token, Token,
};
use crate::element::{Element, ElementKind};
fn find_release_group(tokens: &[Token]) -> (usize, usize) {
let len = tokens.len();
let mut search_start = 0usize;
let mut candidates: Vec<(usize, usize)> = Vec::new();
loop {
let first = find_from(tokens, search_start, |t| {
t.is_enclosed && !is_identified_token(t)
});
let last = if first == len {
len
} else {
find_from(tokens, first, |t| {
is_close_bracket_token(t) || is_identified_token(t)
})
};
if first != len {
let blocked_by_prev = tokens
.get(search_start..first)
.and_then(|s| s.iter().rposition(is_not_delimiter_token))
.is_some_and(|rel| {
!tokens
.get(search_start + rel)
.is_some_and(is_open_bracket_token)
});
if blocked_by_prev {
search_start = last;
continue;
}
if last != len && !tokens.get(last).is_some_and(is_close_bracket_token) {
search_start = last;
continue;
}
candidates.push((first, last));
search_start = last;
continue;
}
if let Some(&(cf, cl)) = candidates.first() {
let title_precedes = tokens.get(cf).is_some_and(|c| {
tokens.iter().any(|t| {
t.element_kind == Some(ElementKind::Title)
&& !t.is_enclosed
&& t.position < c.position
})
});
return if title_precedes {
candidates.last().copied().unwrap_or((cf, cl))
} else {
(cf, cl)
};
}
let region = tokens.get(search_start..).unwrap_or(&[]);
let fallback = find_prev_token(region, region.len(), |t| {
t.element_kind != Some(ElementKind::FileExtension) && is_not_delimiter_token(t)
});
if let Some(local_idx) = fallback {
let idx = search_start + local_idx;
if idx > 0 && tokens.get(idx).is_some_and(is_free_token) {
let prev_idx = idx - 1;
if tokens
.get(prev_idx)
.is_some_and(|t| is_delimiter_token(t) && is_dash_token(t))
{
return (idx, idx + 1);
}
}
}
let peeled = find_prev_token(region, region.len(), |t| is_free_token(t) && !t.is_enclosed);
if let Some(local_idx) = peeled {
let idx = search_start + local_idx;
if idx > 0
&& tokens
.get(idx - 1)
.is_some_and(|t| is_delimiter_token(t) && is_dash_token(t))
{
return (idx, idx + 1);
}
}
return (len, len);
}
}
pub(super) fn parse_release_group(tokens: &mut [Token]) -> Option<Element> {
let (first, last) = find_release_group(tokens);
if first >= last {
return None;
}
let value = build_element_value(
tokens.get(first..last)?,
true,
underscore_is_separator(tokens),
);
if value.is_empty() {
return None;
}
let position = tokens.get(first)?.position;
for i in first..last {
mark(tokens, i, ElementKind::ReleaseGroup);
}
Some(Element {
kind: ElementKind::ReleaseGroup,
value,
position,
})
}