use anyhow::Result;
use colored::Colorize;
use crate::cli::NoteEditArgs;
use crate::models::{NoteFormat, Project};
use crate::storage::Storage;
use crate::utils::validation::{resolve_visible, resolve_visible_index};
pub fn execute(storage: &impl Storage, args: NoteEditArgs) -> Result<()> {
let (tasks, projects, mut notes, resources) = storage.load_all_with_resources()?;
let real_index = resolve_visible_index(¬es, args.id, |n| n.is_deleted())
.map_err(|_| anyhow::anyhow!("Note #{} not found", args.id))?;
let note = &mut notes[real_index];
let mut changes = Vec::new();
if args.editor {
let current = note.body.clone();
let new_body = edit::edit_with_builder(¤t, edit::Builder::new().suffix(".md"))?;
let new_body = new_body.trim().to_string();
if new_body.is_empty() {
return Err(anyhow::anyhow!("Aborted: note body is empty."));
}
if note.body != new_body {
note.body = new_body.clone();
note.format = NoteFormat::Markdown;
changes.push("body → updated via editor".bright_white().to_string());
}
} else if let Some(new_body) = args.body {
if new_body.trim().is_empty() {
return Err(anyhow::anyhow!("Note body cannot be empty"));
}
if note.body != new_body {
note.body = new_body.clone();
changes.push(format!("body → {}", new_body.bright_white()));
}
}
if args.clear_title {
if note.title.is_some() {
note.title = None;
changes.push("title → cleared".dimmed().to_string());
}
} else if let Some(new_title) = args.title
&& note.title.as_deref() != Some(new_title.as_str())
{
note.title = Some(new_title.clone());
changes.push(format!("title → {}", new_title.bright_white()));
}
if args.clear_language {
if note.language.is_some() {
note.language = None;
changes.push("language → cleared".dimmed().to_string());
}
} else if let Some(new_lang) = args.language
&& note.language.as_deref() != Some(new_lang.as_str())
{
note.language = Some(new_lang.clone());
changes.push(format!("language → {}", new_lang.yellow()));
}
if args.clear_tags {
if !note.tags.is_empty() {
let old = note.tags.clone();
note.tags.clear();
changes.push(format!("tags cleared → was [{}]", old.join(", ").dimmed()));
}
} else {
if !args.remove_tag.is_empty() {
let mut removed = Vec::new();
note.tags.retain(|t| {
if args.remove_tag.contains(t) {
removed.push(t.clone());
false
} else {
true
}
});
if !removed.is_empty() {
changes.push(format!("removed tags → [{}]", removed.join(", ").red()));
}
}
if !args.add_tag.is_empty() {
let mut added = Vec::new();
for tag in &args.add_tag {
if !note.tags.contains(tag) {
note.tags.push(tag.clone());
added.push(tag.clone());
}
}
if !added.is_empty() {
changes.push(format!("added tags → [{}]", added.join(", ").cyan()));
}
}
}
if args.clear_project {
if note.project_id.is_some() {
note.project_id = None;
changes.push("project → cleared".dimmed().to_string());
}
} else if let Some(ref proj_name) = args.project {
let new_uuid = Project::resolve_or_create(storage, &projects, proj_name)?;
if note.project_id != Some(new_uuid) {
note.project_id = Some(new_uuid);
changes.push(format!("project → {}", proj_name.cyan()));
}
}
if args.clear_task {
if note.task_id.is_some() {
note.task_id = None;
changes.push("task → cleared".dimmed().to_string());
}
} else if let Some(task_num) = args.task {
let task = resolve_visible(&tasks, task_num, |t| t.is_deleted())
.map_err(|_| anyhow::anyhow!("Task #{} not found", task_num))?;
if note.task_id != Some(task.uuid) {
note.task_id = Some(task.uuid);
changes.push(format!("task → #{} {}", task_num, task.text.cyan()));
}
}
if args.clear_resources {
if !note.resource_ids.is_empty() {
note.resource_ids.clear();
changes.push("resources → cleared".dimmed().to_string());
}
} else {
let visible_resources: Vec<_> = resources.iter().filter(|r| !r.is_deleted()).collect();
if !args.remove_resource.is_empty() {
let mut removed = Vec::new();
for res_num in &args.remove_resource {
let resource = visible_resources
.get(res_num.saturating_sub(1))
.ok_or_else(|| anyhow::anyhow!("Resource #{} not found", res_num))?;
if note.resource_ids.contains(&resource.uuid) {
note.remove_resource(resource.uuid);
removed.push(resource.title.clone());
}
}
if !removed.is_empty() {
changes.push(format!(
"removed resources → [{}]",
removed.join(", ").red()
));
}
}
if !args.add_resource.is_empty() {
let mut added = Vec::new();
for res_num in &args.add_resource {
let resource = visible_resources
.get(res_num.saturating_sub(1))
.ok_or_else(|| anyhow::anyhow!("Resource #{} not found", res_num))?;
if !note.resource_ids.contains(&resource.uuid) {
note.add_resource(resource.uuid);
added.push(resource.title.clone());
}
}
if !added.is_empty() {
changes.push(format!("added resources → [{}]", added.join(", ").cyan()));
}
}
}
if changes.is_empty() {
println!(
"{} No changes made (values are already set to the specified values).",
"".blue()
);
return Ok(());
}
notes[real_index].touch();
storage.save_notes(¬es)?;
println!("{} Note #{} updated:", "✓".green(), args.id);
for change in &changes {
println!(" • {}", change);
}
Ok(())
}