agent-device-rec 0.1.0

Health device recommendation engine for longevity monitoring
use crate::types::{Device, DeviceBrand, DeviceCatalogFile};

const EMBEDDED_CATALOG: &str = include_str!("../data/catalog.toml");

/// Load the embedded TOML catalog. Panics if the embedded catalog is malformed.
pub fn load_catalog() -> DeviceCatalogFile {
    toml::from_str(EMBEDDED_CATALOG).expect("embedded catalog.toml is invalid")
}

/// Find a device by its ID.
pub fn find_device<'a>(catalog: &'a DeviceCatalogFile, id: &str) -> Option<&'a Device> {
    catalog.device.iter().find(|d| d.id == id)
}

/// Find a brand by its ID.
pub fn find_brand<'a>(catalog: &'a DeviceCatalogFile, id: &str) -> Option<&'a DeviceBrand> {
    catalog.brand.iter().find(|b| b.id == id)
}

/// Get all devices that track a given metric.
pub fn devices_tracking_metric<'a>(
    catalog: &'a DeviceCatalogFile,
    metric: &str,
) -> Vec<&'a Device> {
    let lower = metric.to_lowercase();
    catalog
        .device
        .iter()
        .filter(|d| d.tracks.iter().any(|t| t.metric.to_lowercase() == lower))
        .collect()
}

/// Get all devices in a given category.
pub fn devices_by_category<'a>(
    catalog: &'a DeviceCatalogFile,
    category: &str,
) -> Vec<&'a Device> {
    let lower = category.to_lowercase();
    catalog
        .device
        .iter()
        .filter(|d| d.category.to_lowercase() == lower)
        .collect()
}

/// Get all devices from a given brand.
pub fn devices_by_brand<'a>(catalog: &'a DeviceCatalogFile, brand_id: &str) -> Vec<&'a Device> {
    let lower = brand_id.to_lowercase();
    catalog
        .device
        .iter()
        .filter(|d| d.brand_id.to_lowercase() == lower)
        .collect()
}

/// Get all devices within a price range filter.
pub fn devices_by_price_range<'a>(
    catalog: &'a DeviceCatalogFile,
    price_range: &str,
) -> Vec<&'a Device> {
    let lower = price_range.to_lowercase();
    catalog
        .device
        .iter()
        .filter(|d| d.price_range.to_lowercase() == lower)
        .collect()
}

/// Search devices by text query. Case-insensitive substring matching across
/// name, category, brand_id, description, tracked metrics, and use cases.
pub fn search_devices<'a>(catalog: &'a DeviceCatalogFile, query: &str) -> Vec<&'a Device> {
    let lower = query.to_lowercase();
    catalog
        .device
        .iter()
        .filter(|d| {
            d.name.to_lowercase().contains(&lower)
                || d.id.to_lowercase().contains(&lower)
                || d.category.to_lowercase().contains(&lower)
                || d.brand_id.to_lowercase().contains(&lower)
                || d.description.to_lowercase().contains(&lower)
                || d.tracks
                    .iter()
                    .any(|t| t.metric.to_lowercase().contains(&lower))
                || d.use_cases
                    .iter()
                    .any(|u| u.to_lowercase().contains(&lower))
                || d.notes.to_lowercase().contains(&lower)
        })
        .collect()
}