sara-tasks 2.3.0

Sara — folder-aware task manager
use rusqlite::Connection;
use std::collections::HashMap;
use std::collections::hash_map::Entry;

use crate::commands::recall::types::{ClusterInfo, Hit};
use crate::commands::shared::derived_count;
use crate::infrastructure::db;

pub(in crate::commands::recall) fn family_key(h: &Hit) -> Option<String> {
    if let Some(parent) = h.derived_from_labels.first() {
        Some(parent.clone())
    } else if !h.derived_children.is_empty() {
        Some(h.label.clone())
    } else {
        None
    }
}

pub(in crate::commands::recall) fn family_size(
    conn: &Connection,
    rep: &Hit,
    canonical_label: &str,
) -> usize {
    if rep.label == canonical_label {
        return 1 + rep.derived_children.len();
    }
    match db::get_item_by_handle(conn, canonical_label) {
        Ok(canon) => 1 + derived_count(conn, &canon.uuid.to_string()),
        Err(_) => 1,
    }
}

struct Family {
    rep: usize,
    collapsed: usize,
    nearest: Option<String>,
}

pub(in crate::commands::recall) fn collapse_clusters(
    conn: &Connection,
    hits: Vec<Hit>,
) -> Vec<Hit> {
    let mut kept: Vec<Hit> = Vec::with_capacity(hits.len());
    let mut families: HashMap<(String, String), Family> = HashMap::new();

    for h in hits {
        let Some(fam) = family_key(&h) else {
            kept.push(h);
            continue;
        };
        let sig = hit_project_sig(conn, &h);
        match families.entry((fam, sig)) {
            Entry::Vacant(v) => {
                v.insert(Family {
                    rep: kept.len(),
                    collapsed: 0,
                    nearest: None,
                });
                kept.push(h);
            }
            Entry::Occupied(o) => {
                let canonical = &o.key().0;
                let promote = h.label == *canonical && kept[o.get().rep].label != *canonical;
                let family = o.into_mut();
                family.collapsed += 1;
                if promote {
                    let displaced = std::mem::replace(&mut kept[family.rep], h);
                    family.nearest = Some(displaced.label);
                } else if family.nearest.is_none() {
                    family.nearest = Some(h.label);
                }
            }
        }
    }

    for h in kept.iter_mut() {
        let Some(fam) = family_key(h) else { continue };
        let size = family_size(conn, h, &fam);
        if size <= 1 {
            continue;
        }
        let key = (fam, hit_project_sig(conn, h));
        let family = families.get(&key);
        let collapsed_here = family.map_or(0, |f| f.collapsed);
        let nearest = family.and_then(|f| f.nearest.clone());
        h.cluster = Some(ClusterInfo {
            canonical_label: key.0,
            size,
            collapsed_here,
            nearest,
        });
    }

    kept
}

pub(in crate::commands::recall) fn hit_project_sig(conn: &Connection, h: &Hit) -> String {
    match h.item_uuid {
        Some(u) => {
            let mut ps = db::get_item_projects(conn, &u).unwrap_or_default();
            ps.sort();
            ps.join("\u{1f}")
        }
        None => String::new(),
    }
}