use crate::matcher::span::{MatchSpan, Property};
use super::super::clean::clean_title;
use super::{StrategyContext, TitleConfidence, TitleStrategy};
pub(crate) struct UnclaimedBracket;
impl TitleStrategy for UnclaimedBracket {
fn name(&self) -> &'static str {
"unclaimed_bracket"
}
fn confidence(&self) -> TitleConfidence {
TitleConfidence::Strong
}
fn try_extract(&self, ctx: &StrategyContext<'_>) -> Option<MatchSpan> {
let StrategyContext {
input,
matches,
filename_start,
} = *ctx;
let filename = &input[filename_start..];
if !filename.starts_with('[') {
return None;
}
let mut brackets: Vec<(usize, usize, &str)> = Vec::new();
let mut pos = 0;
while pos < filename.len() {
if filename[pos..].starts_with('[') {
if let Some(close) = filename[pos..].find(']') {
let content = &filename[pos + 1..pos + close];
let abs_start = filename_start + pos + 1;
let abs_end = filename_start + pos + close;
brackets.push((abs_start, abs_end, content));
pos += close + 1;
} else {
break;
}
} else {
let rest = &filename[pos..];
if rest.starts_with(['.', ' ', '-', '_']) {
break; }
return None;
}
}
if brackets.len() < 2 {
return None;
}
let start_index = usize::from(matches.iter().any(|m| m.property == Property::ReleaseGroup));
for &(abs_start, abs_end, content) in &brackets[start_index..] {
if content.is_empty() || content.chars().all(|c| c.is_ascii_digit()) {
continue;
}
let is_claimed = matches.iter().any(|m| {
!matches!(
m.property,
Property::ReleaseGroup | Property::Title | Property::Season | Property::Episode
) && m.start < abs_end
&& m.end > abs_start
});
if !is_claimed {
let cleaned = clean_title(content);
if !cleaned.is_empty() {
return Some(MatchSpan::new(abs_start, abs_end, Property::Title, cleaned));
}
}
}
None
}
}