devbrain 0.1.0

Local-first CLI to capture, search, and recall developer workflow (commands, errors, and fixes)
use crate::models::{Entry, EntryType};

pub fn process_entries(
    entries: &[Entry],
    query: Option<String>,
    project: Option<String>,
    entry_type: Option<EntryType>,
    offset: Option<usize>,
    limit: Option<usize>,
) -> Vec<&Entry> {
    let normalized_query = query.map(|value| value.to_lowercase());

    let mut entries: Vec<&Entry> = if let Some(query) = normalized_query.as_ref() {
        let mut scored_entries: Vec<(&Entry, i32)> = entries
            .iter()
            .filter(|entry| {
                project
                    .as_ref()
                    .is_none_or(|project| entry.project == *project)
            })
            .filter(|entry| {
                entry_type
                    .as_ref()
                    .is_none_or(|entry_type| entry.entry_type == *entry_type)
            })
            .filter_map(|entry| {
                let content_match = entry.content.to_lowercase().contains(query);
                let project_match = entry.project.to_lowercase().contains(query);
                let entry_type_match = entry.entry_type.as_str() == query;

                let mut score = 0;

                if content_match {
                    score += 3;
                }

                if project_match {
                    score += 2;
                }

                if entry_type_match {
                    score += 2;
                }

                (score > 0).then_some((entry, score))
            })
            .collect();

        scored_entries.sort_by(|a, b| {
            b.1.cmp(&a.1)
                .then_with(|| b.0.timestamp.cmp(&a.0.timestamp))
        });

        scored_entries
            .into_iter()
            .map(|(entry, _score)| entry)
            .collect()
    } else {
        entries
            .iter()
            .filter(|entry| {
                project
                    .as_ref()
                    .is_none_or(|project| entry.project == *project)
            })
            .filter(|entry| {
                entry_type
                    .as_ref()
                    .is_none_or(|entry_type| entry.entry_type == *entry_type)
            })
            .collect()
    };

    if normalized_query.is_none() {
        entries.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
    }

    if let Some(offset) = offset {
        entries = entries.into_iter().skip(offset).collect();
    }

    if let Some(limit) = limit {
        return entries.into_iter().take(limit).collect();
    }

    entries
}