use crate::commands::watch::comments::detect_comments;
use crate::commands::watch::debounce::{can_reprocess, cleanup_old_entries};
use crate::commands::watch::state::WatchState;
use crate::commands::watch::tasks::handle_detected_comments;
use crate::commands::watch::types::{DetectedComment, WatchOptions};
use crate::config::Resolved;
use anyhow::Result;
use regex::Regex;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
pub fn process_pending_files(
resolved: &Resolved,
state: &Arc<Mutex<WatchState>>,
comment_regex: &Regex,
opts: &WatchOptions,
last_processed: &mut HashMap<PathBuf, Instant>,
) -> Result<()> {
let files: Vec<PathBuf> = match state.lock() {
Ok(mut guard) => guard.take_pending(),
Err(e) => {
log::error!("Watch 'state' mutex poisoned, cannot process files: {}", e);
return Ok(());
}
};
if files.is_empty() {
return Ok(());
}
let debounce = Duration::from_millis(opts.debounce_ms);
let mut all_comments: Vec<DetectedComment> = Vec::new();
let mut processed_files: Vec<PathBuf> = Vec::new();
for file_path in files {
if !can_reprocess(&file_path, last_processed, debounce) {
continue;
}
match detect_comments(&file_path, comment_regex) {
Ok(comments) => {
processed_files.push(file_path.clone());
if !comments.is_empty() {
log::debug!(
"Detected {} comments in {}",
comments.len(),
file_path.display()
);
all_comments.extend(comments);
}
last_processed.insert(file_path, Instant::now());
}
Err(e) if !file_path.exists() => {
log::debug!(
"Treating missing file {} as removed for watch reconciliation: {}",
file_path.display(),
e
);
processed_files.push(file_path.clone());
last_processed.insert(file_path, Instant::now());
}
Err(e) => {
log::warn!("Failed to process file {}: {}", file_path.display(), e);
}
}
}
cleanup_old_entries(last_processed, debounce);
if !processed_files.is_empty() {
handle_detected_comments(resolved, &all_comments, &processed_files, opts)?;
}
Ok(())
}
#[cfg(test)]
mod tests;