sara-tasks 2.7.4

Sara — folder-aware task manager
use anyhow::Result;
use rusqlite::Connection;
use uuid::Uuid;

use crate::commands::shared::{derived_children, memory_handle};
use crate::infrastructure::db;

const LINK_WEIGHT: f64 = 1.0;

pub(super) fn link_handles(
    conn: &Connection,
    new_uuid: &str,
    handles: &[String],
    relation: &str,
    flag: &str,
) -> Result<Vec<String>> {
    let mut labels = Vec::new();
    for handle in handles {
        let target = match db::get_item_by_handle(conn, handle) {
            Ok(target) => target,
            Err(e) => {
                eprintln!("warning: could not resolve memory '{handle}' for {flag}: {e}");
                continue;
            }
        };
        let target_uuid = target.uuid.to_string();
        db::insert_memory_link(conn, new_uuid, &target_uuid, relation, LINK_WEIGHT)?;
        let label = target
            .display_id
            .map(|id| format!("m{id}"))
            .unwrap_or_else(|| handle.clone());
        if relation == "supersedes" {
            warn_superseded_canonical(conn, &label, &target_uuid);
        }
        labels.push(label);
    }
    Ok(labels)
}

fn warn_superseded_canonical(conn: &Connection, label: &str, uuid: &str) {
    let derived: Vec<String> = derived_children(conn, uuid)
        .iter()
        .map(memory_handle)
        .collect();
    if derived.is_empty() {
        return;
    }
    eprintln!(
        "Warning: {label} is a canonical pattern memory with {} derived {} ({}) — review with \
         `sara dream <label>` or archive with `sara forget <label>`; they are not auto-archived \
         by this supersede.",
        derived.len(),
        if derived.len() == 1 {
            "memory"
        } else {
            "memories"
        },
        derived.join(", ")
    );
}

pub(super) fn auto_link_canonical(
    conn: &Connection,
    new_uuid: &str,
    candidates: &[Uuid],
    already_linked: &[&String],
) -> Result<Vec<String>> {
    let mut labels: Vec<String> = Vec::new();
    for u in candidates {
        let Ok(target) = db::get_item_by_uuid(conn, &u.to_string()) else {
            continue;
        };
        let label = memory_handle(&target);
        if already_linked.contains(&&label) || labels.contains(&label) {
            continue;
        }
        db::insert_memory_link(conn, new_uuid, &u.to_string(), "derived_from", LINK_WEIGHT)?;
        labels.push(label);
    }
    Ok(labels)
}