use std::collections::{BTreeMap, BTreeSet, HashMap};
use crate::lineage::LineageContext;
use crate::model::Clip;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TrackAssignment {
pub track: u32,
pub total: u32,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct LeadResolution {
pub resolved: BTreeSet<String>,
pub unmatched: Vec<String>,
pub ambiguous: Vec<String>,
}
pub fn resolve_lead_ids(clips: &[&Clip], configured: &[String]) -> LeadResolution {
let mut out = LeadResolution::default();
for entry in configured {
let needle = entry.trim();
if needle.is_empty() {
continue;
}
let mut matches = clips.iter().filter(|clip| id_matches(&clip.id, needle));
match (matches.next(), matches.next()) {
(Some(clip), None) => {
out.resolved.insert(clip.id.clone());
}
(Some(_), Some(_)) => out.ambiguous.push(needle.to_owned()),
(None, _) => out.unmatched.push(needle.to_owned()),
}
}
out
}
fn id_matches(id: &str, needle: &str) -> bool {
id.get(..needle.len())
.is_some_and(|prefix| prefix.eq_ignore_ascii_case(needle))
}
pub fn assign_track_numbers(
clips: &[&Clip],
contexts: &HashMap<String, LineageContext>,
leads: &BTreeSet<String>,
number_singletons: bool,
) -> HashMap<String, TrackAssignment> {
let mut groups: BTreeMap<&str, Vec<&Clip>> = BTreeMap::new();
for clip in clips {
let root = contexts
.get(&clip.id)
.map(|ctx| ctx.root_id.as_str())
.filter(|root| !root.is_empty())
.unwrap_or(clip.id.as_str());
groups.entry(root).or_default().push(clip);
}
let mut out = HashMap::with_capacity(clips.len());
for (_root, mut members) in groups {
members.sort_by(|a, b| {
a.created_at
.cmp(&b.created_at)
.then_with(|| a.id.cmp(&b.id))
});
let total = members.len() as u32;
if total == 1 && !number_singletons {
continue;
}
if let Some(pos) = members.iter().position(|clip| leads.contains(&clip.id)) {
let lead = members.remove(pos);
members.insert(0, lead);
}
for (index, clip) in members.iter().enumerate() {
out.insert(
clip.id.clone(),
TrackAssignment {
track: index as u32 + 1,
total,
},
);
}
}
out
}
#[cfg(test)]
mod tests;