use std::cmp::Reverse;
use std::collections::HashMap;
use trusty_common::github_path::parse_remote_url;
use trusty_mpm::client::ManagedSessionSummary;
use trusty_mpm::project::Project;
use crate::commands::session_picker_order::{group_rank, recency_key};
use super::new_session::{CheckoutOf, Target, repo_is_session_project, row_labels};
const NO_SESSION: u8 = 3;
const FORGE_HOSTS: [&str; 4] = ["github.com", "gitlab.com", "bitbucket.org", "codeberg.org"];
const NO_REMOTE_GROUP: &str = "(local)";
pub(crate) fn group_of(repo: &str) -> (u8, String) {
let Ok(remote) = parse_remote_url(repo) else {
return (1, NO_REMOTE_GROUP.to_string());
};
let host = remote.host.to_lowercase();
let is_forge = FORGE_HOSTS
.iter()
.any(|f| host == *f || host.ends_with(&format!(".{f}")));
if is_forge {
(0, remote.owner.to_lowercase())
} else {
(0, host)
}
}
pub(crate) fn ordered_targets(
projects: &[Project],
sessions: &[ManagedSessionSummary],
checkout_of: CheckoutOf,
) -> Vec<Target> {
let registered: Vec<Target> = projects
.iter()
.filter(|p| crate::commands::projects::offerable::is_offerable_project(p))
.map(|p| Target::Registered {
name: p.name.clone(),
repo: p.repo_url.clone(),
checkout: checkout_of(p),
})
.collect();
let labels = row_labels(®istered);
let facts: Vec<Facts> = registered
.iter()
.zip(&labels)
.map(|(target, label)| facts(target, label, sessions))
.collect();
let groups = group_ranks(&facts);
let mut ranked: Vec<(Rank, Target)> = registered
.into_iter()
.zip(&facts)
.map(|(target, row)| (rank(row, &groups), target))
.collect();
ranked.sort_by(|a, b| a.0.cmp(&b.0));
ranked
.into_iter()
.map(|(_, target)| target)
.chain(std::iter::once(Target::Other))
.collect()
}
struct Facts {
group: (u8, String),
session: u8,
recency: String,
label: String,
name: String,
}
fn facts(target: &Target, label: &str, sessions: &[ManagedSessionSummary]) -> Facts {
let (name, repo) = match target {
Target::Registered { name, repo, .. } => (name.as_str(), repo.as_str()),
Target::Other => ("", ""),
};
let mut session = NO_SESSION;
let mut recency = "";
for summary in sessions.iter().filter(|s| repo_is_session_project(repo, s)) {
session = session.min(group_rank(summary));
recency = recency.max(recency_key(summary));
}
Facts {
group: group_of(repo),
session,
recency: recency.to_string(),
label: label.to_lowercase(),
name: name.to_string(),
}
}
fn group_ranks(facts: &[Facts]) -> HashMap<(u8, String), (u8, String)> {
let mut best: HashMap<(u8, String), (u8, String)> = HashMap::new();
for row in facts {
let slot = best
.entry(row.group.clone())
.or_insert((NO_SESSION, String::new()));
slot.0 = slot.0.min(row.session);
if row.recency > slot.1 {
slot.1 = row.recency.clone();
}
}
best
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Rank {
group_tier: u8,
group_session: u8,
group_recency: Reverse<String>,
group_key: String,
session: u8,
recency: Reverse<String>,
label: String,
name: String,
}
fn rank(row: &Facts, groups: &HashMap<(u8, String), (u8, String)>) -> Rank {
let (group_session, group_recency) = groups
.get(&row.group)
.cloned()
.unwrap_or((NO_SESSION, String::new()));
Rank {
group_tier: row.group.0,
group_session,
group_recency: Reverse(group_recency),
group_key: row.group.1.clone(),
session: row.session,
recency: Reverse(row.recency.clone()),
label: row.label.clone(),
name: row.name.clone(),
}
}