devbrain 0.2.0

Local-first CLI to capture, search, and recall developer workflow (commands, errors, and fixes)
use crate::error::DevbrainError;
use chrono::{DateTime, SecondsFormat, Utc};
use std::env;

const MAX_INPUT_LENGTH: usize = 500;

pub fn get_timestamp() -> String {
    Utc::now().to_rfc3339_opts(SecondsFormat::Secs, true)
}

pub fn format_timestamp(timestamp: &str) -> String {
    DateTime::parse_from_rfc3339(timestamp)
        .map(|timestamp| timestamp.format("%Y-%m-%d %H:%M").to_string())
        .unwrap_or_else(|_| timestamp.to_string())
}

pub fn get_project_name() -> String {
    env::current_dir()
        .ok()
        .and_then(|path| {
            path.file_name()
                .map(|name| name.to_string_lossy().into_owned())
        })
        .filter(|name| !name.is_empty())
        .unwrap_or_else(|| "unknown".to_string())
}

pub fn normalize_input(input: &str) -> Result<String, DevbrainError> {
    let normalized = input.trim();

    if normalized.is_empty() {
        return Err(DevbrainError::Other("Input cannot be empty".to_string()));
    }

    if normalized.chars().count() > MAX_INPUT_LENGTH {
        return Err(DevbrainError::Other("Input too long".to_string()));
    }

    Ok(normalized.to_string())
}

pub fn normalize_command_input(input: &str) -> Result<String, DevbrainError> {
    let collapsed = input.split_whitespace().collect::<Vec<_>>().join(" ");
    normalize_input(&collapsed)
}

pub fn normalize_tags(tags: &[String]) -> Vec<String> {
    tags.iter()
        .map(|tag| tag.trim().to_lowercase())
        .filter(|tag| !tag.is_empty())
        .collect()
}

pub fn infer_tags(content: &str) -> Vec<String> {
    let content = content.to_lowercase();
    let tokens: Vec<&str> = content
        .split(|c: char| !c.is_alphanumeric())
        .filter(|token| !token.is_empty())
        .collect();

    let has_token = |needle: &str| tokens.contains(&needle);
    let mut tags = Vec::new();

    if content.contains("auth") {
        tags.push("auth".to_string());
    }

    if has_token("db") || content.contains("database") {
        tags.push("database".to_string());
    }

    if has_token("api") {
        tags.push("api".to_string());
    }

    tags
}