use crate::cache::Cache;
use crate::tui::WeekStats;
use super::FileExtensionStats;
use crate::model::CommitStats;
use crate::error::{Result, GmapError};
use chrono::{DateTime, Datelike, Utc};
use std::collections::HashMap;
use std::path::Path;
use crate::model::HeatBucket;
pub fn aggregate_weeks(stats: &[CommitStats], cache: &Cache, path_prefix: Option<&str>) -> Vec<WeekStats> {
let mut week_map: HashMap<String, (usize, usize, usize, HashMap<String, usize>, HashMap<String, FileExtensionStats>, HashMap<String, usize>)> = HashMap::new();
for commit_stats in stats {
let commit_info = match cache.get_commit_info(&commit_stats.commit_id) {
Ok(Some(info)) => info,
_ => continue,
};
let week_key = format!("{}-W{:02}", commit_info.timestamp.year(), commit_info.timestamp.iso_week().week());
let mut added = 0usize;
let mut deleted = 0usize;
let mut has_matching_files = false;
for file_stats in &commit_stats.files {
if let Some(prefix) = path_prefix {
if !file_stats.path.starts_with(prefix) {
continue;
}
}
has_matching_files = true;
added += file_stats.added_lines as usize;
deleted += file_stats.deleted_lines as usize;
}
if has_matching_files || path_prefix.is_none() {
let entry = week_map.entry(week_key.clone()).or_insert((0, 0, 0, HashMap::new(), HashMap::new(), HashMap::new()));
entry.0 += 1;
entry.1 += added;
entry.2 += deleted;
*entry.3.entry(commit_info.author_name.clone()).or_insert(0) += 1;
for file_stats in &commit_stats.files {
if let Some(prefix) = path_prefix {
if !file_stats.path.starts_with(prefix) {
continue;
}
}
let extension = Path::new(&file_stats.path)
.extension()
.and_then(|s| s.to_str())
.unwrap_or("")
.to_lowercase();
let ext_entry = entry.4.entry(extension).or_insert(FileExtensionStats {
commits: 0,
lines_added: 0,
lines_deleted: 0,
files_changed: 0,
});
ext_entry.commits += 1;
ext_entry.lines_added += file_stats.added_lines as usize;
ext_entry.lines_deleted += file_stats.deleted_lines as usize;
ext_entry.files_changed += 1;
*entry.5.entry(file_stats.path.clone()).or_insert(0) += 1;
}
}
}
let mut weeks: Vec<WeekStats> = week_map.into_iter().map(|(week, (commits, added, deleted, authors, file_extensions, file_changes))| {
let mut top_authors: Vec<_> = authors.into_iter().collect();
top_authors.sort_by(|a, b| b.1.cmp(&a.1));
let top_authors = top_authors.into_iter().map(|(name, _)| name).take(3).collect();
let mut top_files: Vec<_> = file_changes.into_iter().collect();
top_files.sort_by(|a, b| b.1.cmp(&a.1));
let top_files = top_files.into_iter().take(10).collect();
WeekStats {
week,
commits,
lines_added: added,
lines_deleted: deleted,
top_authors,
file_extensions,
top_files,
}
}).collect();
weeks.sort_by(|a, b| a.week.cmp(&b.week));
weeks
}
pub fn compute_heat(
stats: &[CommitStats],
cache: &Cache,
path_prefix: Option<&str>,
) -> Result<Vec<HeatBucket>> {
let mut week_map: HashMap<String, (u32, u64)> = HashMap::new();
for commit_stats in stats {
let commit_info = cache
.get_commit_info(&commit_stats.commit_id)?
.ok_or_else(|| GmapError::Cache("Commit info not found".to_string()))?;
let week_key = get_week_key(&commit_info.timestamp);
let mut lines_changed = 0u64;
let mut has_matching_files = false;
for file_stats in &commit_stats.files {
if let Some(prefix) = path_prefix {
if !file_stats.path.starts_with(prefix) {
continue;
}
}
has_matching_files = true;
lines_changed += (file_stats.added_lines + file_stats.deleted_lines) as u64;
}
if has_matching_files || path_prefix.is_none() {
let entry = week_map.entry(week_key).or_insert((0, 0));
entry.0 += 1;
entry.1 += lines_changed;
}
}
let mut buckets: Vec<_> = week_map
.into_iter()
.map(|(week, (commit_count, lines_changed))| HeatBucket {
week,
commit_count,
lines_changed,
})
.collect();
buckets.sort_by(|a, b| a.week.cmp(&b.week));
Ok(buckets)
}
fn get_week_key(timestamp: &DateTime<Utc>) -> String {
format!("{}-W{:02}", timestamp.year(), timestamp.iso_week().week())
}