use crate::matcher::span::{MatchSpan, Property};
use crate::tokenizer::{Separator, TokenStream};
const CONFIDENT_TECH: &[Property] = &[
Property::VideoCodec,
Property::AudioCodec,
Property::ScreenSize,
Property::Source,
Property::Year,
Property::Season,
Property::Episode,
Property::Date,
Property::Container,
Property::FrameRate,
Property::ColorDepth,
Property::VideoProfile,
Property::AudioChannels,
Property::AudioProfile,
Property::Edition,
Property::StreamingService,
Property::EpisodeCount,
Property::SeasonCount,
Property::Part,
Property::Bonus,
Property::Other,
Property::VideoApi,
Property::ReleaseGroup,
];
pub fn is_in_title_context(
m: &MatchSpan,
matches: &[MatchSpan],
token_stream: &TokenStream,
) -> bool {
let segment = token_stream
.segments
.iter()
.find(|s| s.start <= m.start && m.end <= s.end);
let segment = match segment {
Some(s) => s,
None => return false, };
let match_token_indices: Vec<usize> = segment
.tokens
.iter()
.enumerate()
.filter(|(_, t)| t.start >= m.start && t.end <= m.end)
.map(|(i, _)| i)
.collect();
if match_token_indices.is_empty() {
return false;
}
let first_idx = match_token_indices[0];
let last_idx = *match_token_indices.last().unwrap();
if let Some(token) = segment.tokens.get(first_idx) {
if is_after_metadata_separator(token, first_idx, &segment.tokens) {
return false; }
if token.in_brackets {
return false; }
}
let mut score: i32 = 0;
let mut sides_checked = 0;
for offset in 1..=2 {
if first_idx >= offset {
let neighbor = &segment.tokens[first_idx - offset];
if is_tech_or_peer(neighbor, matches, m.property) {
score -= 1;
} else {
score += 1;
}
sides_checked += 1;
break; }
}
for offset in 1..=2 {
let right_idx = last_idx + offset;
if right_idx < segment.tokens.len() {
let neighbor = &segment.tokens[right_idx];
if is_tech_or_peer(neighbor, matches, m.property) {
score -= 1;
} else {
score += 1;
}
sides_checked += 1;
break;
}
}
if sides_checked < 2 {
return is_before_first_anchor_in_segment(m, matches, segment);
}
if score == 0 {
return is_before_first_anchor_in_segment(m, matches, segment);
}
score > 0
}
fn is_before_first_anchor_in_segment(
m: &MatchSpan,
matches: &[MatchSpan],
segment: &crate::tokenizer::PathSegment,
) -> bool {
let first_anchor = matches
.iter()
.filter(|other| {
other.start >= segment.start
&& other.end <= segment.end
&& matches!(
other.property,
Property::Year | Property::Season | Property::Episode | Property::Date
)
})
.map(|other| other.start)
.min();
match first_anchor {
Some(anchor_pos) => m.start < anchor_pos, None => false, }
}
pub fn is_in_tech_context(
m: &MatchSpan,
matches: &[MatchSpan],
token_stream: &TokenStream,
) -> bool {
let segment = token_stream
.segments
.iter()
.find(|s| s.start <= m.start && m.end <= s.end);
let segment = match segment {
Some(s) => s,
None => return false,
};
let match_token_indices: Vec<usize> = segment
.tokens
.iter()
.enumerate()
.filter(|(_, t)| t.start >= m.start && t.end <= m.end)
.map(|(i, _)| i)
.collect();
if match_token_indices.is_empty() {
return false;
}
let first_idx = match_token_indices[0];
let last_idx = *match_token_indices.last().unwrap();
let mut has_tech = false;
if first_idx > 0 {
let left = &segment.tokens[first_idx - 1];
if !is_claimed_by_tech(left, matches) {
return false; }
has_tech = true;
}
if last_idx + 1 < segment.tokens.len() {
let right = &segment.tokens[last_idx + 1];
if !is_claimed_by_tech(right, matches) {
return false; }
has_tech = true;
}
has_tech
}
fn is_claimed_by_tech(token: &crate::tokenizer::Token, matches: &[MatchSpan]) -> bool {
matches.iter().any(|m| {
CONFIDENT_TECH.contains(&m.property) && m.start <= token.start && m.end >= token.end
})
}
fn is_tech_or_peer(
token: &crate::tokenizer::Token,
matches: &[MatchSpan],
current_property: Property,
) -> bool {
matches.iter().any(|m| {
m.start <= token.start
&& m.end >= token.end
&& (CONFIDENT_TECH.contains(&m.property) || m.property == current_property)
})
}
fn is_after_metadata_separator(
_token: &crate::tokenizer::Token,
token_idx: usize,
tokens: &[crate::tokenizer::Token],
) -> bool {
if token_idx == 0 {
return false;
}
let prev = &tokens[token_idx - 1];
if let Some(token) = tokens.get(token_idx) {
if token.start >= 3 {
if token.separator == Separator::Dash || token.separator == Separator::Space {
let gap_start = prev.end;
let gap_end = token.start;
if gap_end > gap_start + 1 {
return true;
}
}
}
}
false
}
pub fn has_duplicate_in_tech_context(
m: &MatchSpan,
matches: &[MatchSpan],
token_stream: &TokenStream,
) -> bool {
matches.iter().any(|other| {
other.property == m.property
&& other.value.to_lowercase() == m.value.to_lowercase()
&& other.start != m.start
&& is_in_tech_context(other, matches, token_stream)
})
}