todomd 0.1.0

A simple markdown-based todo list CLI and TUI
use crate::core::model::{Category, ColorTag, State, SubTask, Todo};
use crate::core::store::Store;
use anyhow::{bail, Result};
use chrono::Local;

pub fn add_todo(store: &dyn Store, category: Category, title: String, notes: Option<String>) -> Result<u64> {
    let mut state = store.load()?;
    let id = state.next_id;
    state.next_id += 1;
    
    let now = Local::now().timestamp();
    let todo = Todo {
        id,
        title,
        notes,
        created_at: now,
        updated_at: now,
        color: ColorTag::None,
        subtasks: Vec::new(),
    };
    
    state.get_category_mut(category).push(todo);
    store.save(&state)?;
    Ok(id)
}

pub fn edit_todo(store: &dyn Store, id: u64, title: Option<String>, notes: Option<String>) -> Result<()> {
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        if let Some(todo) = vec.iter_mut().find(|t| t.id == id) {
            if let Some(t) = title { todo.title = t; }
            if let Some(n) = notes { todo.notes = Some(n); }
            todo.updated_at = now;
            store.save(&state)?;
            return Ok(());
        }
    }
    bail!("Todo not found: {}", id)
}

pub fn move_todo(store: &dyn Store, id: u64, target_cat: Category) -> Result<()> {
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    
    let mut found_todo: Option<Todo> = None;
    
    // Remove from old location
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        if let Some(idx) = vec.iter().position(|t| t.id == id) {
            found_todo = Some(vec.remove(idx));
            break;
        }
    }
    
    if let Some(mut todo) = found_todo {
        todo.updated_at = now;
        state.get_category_mut(target_cat).push(todo);
        store.save(&state)?;
        Ok(())
    } else {
        bail!("Todo not found: {}", id)
    }
}

pub fn remove_todo(store: &dyn Store, id: u64) -> Result<()> {
    let mut state = store.load()?;
    
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        if let Some(idx) = vec.iter().position(|t| t.id == id) {
            vec.remove(idx);
            store.save(&state)?;
            return Ok(());
        }
    }
    bail!("Todo not found: {}", id)
}

pub fn toggle_done(store: &dyn Store, id: u64) -> Result<()> {
    // If it's in completed, move to default previous or ask? 
    // CLI: `todo done <id>` moves to Completed.
    // CLI: `todo undo <id> <category>` moves out.
    // Here we can implement generic move, but for convenience `mark_done` is specific.
    move_todo(store, id, Category::Completed)
}

// Helper to find parent vec of an item and swap
pub fn reorder_item(store: &dyn Store, id: u64, move_up: bool) -> Result<()> {
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    let mut modified = false;

    // Check Categories (Level 1 items)
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        if let Some(idx) = vec.iter().position(|t| t.id == id) {
            // Found in root
            if move_up {
                if idx > 0 {
                    vec.swap(idx, idx - 1);
                    vec[idx].updated_at = now;
                    vec[idx-1].updated_at = now;
                    modified = true;
                }
            } else {
                if idx < vec.len() - 1 {
                    vec.swap(idx, idx + 1);
                    vec[idx].updated_at = now;
                    vec[idx+1].updated_at = now;
                    modified = true;
                }
            }
            if modified { break; }
        }
        
        // Recursive check for subtasks
        for todo in vec.iter_mut() {
            if reorder_in_subs(&mut todo.subtasks, id, move_up, now) {
                todo.updated_at = now;
                modified = true;
                break;
            }
        }
        if modified { break; }
    }
    
    if modified {
         store.save(&state)?;
         Ok(())
    } else {
         // Either not found or boundary reached (cannot move further)
         // We return Ok even if no move happened? Or bail?
         // TUI expects simple return. 
         Ok(())
    }
}

fn reorder_in_subs(subs: &mut Vec<SubTask>, id: u64, move_up: bool, now: i64) -> bool {
    // Check direct children
    if let Some(idx) = subs.iter().position(|s| s.id == id) {
        if move_up {
            if idx > 0 {
                subs.swap(idx, idx - 1);
                subs[idx].updated_at = now;
                subs[idx-1].updated_at = now;
                return true;
            }
        } else {
            if idx < subs.len() - 1 {
                subs.swap(idx, idx + 1);
                subs[idx].updated_at = now;
                subs[idx+1].updated_at = now;
                return true;
            }
        }
        return false; // Found but couldn't move
    }
    
    // Recurse
    for sub in subs.iter_mut() {
        if reorder_in_subs(&mut sub.subtasks, id, move_up, now) {
            sub.updated_at = now;
            return true;
        }
    }
    false
}

// Recursive add subtask
pub fn add_subtask(store: &dyn Store, parent_id: u64, title: String, notes: Option<String>) -> Result<u64> {
    let mut state = store.load()?;
    let id = state.next_id;
    state.next_id += 1;
    let now = Local::now().timestamp();
    
    let sub = SubTask {
        id,
        title,
        done: false,
        created_at: now,
        updated_at: now,
        color: ColorTag::None,
        notes,
        subtasks: Vec::new(),
    };
    
    let mut added = false;
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        for todo in vec.iter_mut() {
            if todo.id == parent_id {
                todo.subtasks.push(sub.clone());
                todo.updated_at = now;
                added = true;
                break;
            }
            // Recurse
            if add_to_sub(&mut todo.subtasks, parent_id, &sub, now) {
                todo.updated_at = now;
                added = true;
                break;
            }
        }
        if added { break; }
    }
    
    if added {
        store.save(&state)?;
        Ok(id)
    } else {
        bail!("Parent not found: {}", parent_id)
    }
}

fn add_to_sub(subs: &mut Vec<SubTask>, parent_id: u64, new_sub: &SubTask, now: i64) -> bool {
    for sub in subs.iter_mut() {
        if sub.id == parent_id {
            sub.subtasks.push(new_sub.clone());
            sub.updated_at = now;
            return true;
        }
        if add_to_sub(&mut sub.subtasks, parent_id, new_sub, now) {
            sub.updated_at = now;
            return true;
        }
    }
    false
}

pub fn edit_subtask(store: &dyn Store, parent_id: u64, sub_id: u64, title: Option<String>, done: Option<bool>, notes: Option<String>) -> Result<()> {
    // NOTE: parent_id argument is legacy/optimization? 
    // If we have unique sub_id, we can ignore parent_id or use it to narrow search.
    // TUI calls this with `todo_id` as parent for L2 subs.
    // For L3, TUI might pass L2 id? Or TUI might not know hierarchy easily.
    // `app.rs` knows parent because of `RowRef`.
    // Let's rely on recursive search for `sub_id`. `parent_id` is hint?
    // Actually our old implementation used `parent_id` to find Todo.
    // New recursive: `sub_id` is unique. We can ignore `parent_id` or check it.
    // Let's implement finding by `sub_id` globally for simplicity.
    
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    
    let mut found = false;
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        for todo in vec.iter_mut() {
             if edit_in_subs(&mut todo.subtasks, sub_id, &title, done, &notes, now) {
                 todo.updated_at = now;
                 found = true;
                 break;
             }
        }
        if found { break; }
    }
    
    if found {
        store.save(&state)?;
        Ok(())
    } else {
        bail!("Subtask not found: {}", sub_id)
    }
}

fn edit_in_subs(subs: &mut Vec<SubTask>, sub_id: u64, title: &Option<String>, done: Option<bool>, notes: &Option<String>, now: i64) -> bool {
    for sub in subs.iter_mut() {
        if sub.id == sub_id {
            if let Some(t) = title { sub.title = t.clone(); }
            if let Some(d) = done { sub.done = d; }
            if let Some(n) = notes { sub.notes = Some(n.clone()); }
            sub.updated_at = now;
            return true;
        }
        if edit_in_subs(&mut sub.subtasks, sub_id, title, done, notes, now) {
            sub.updated_at = now;
            return true;
        }
    }
    false
}

pub fn remove_subtask(store: &dyn Store, _parent_id: u64, sub_id: u64) -> Result<()> {
    // Ignore parent_id, search by sub_id
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    
    let mut found = false;
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        for todo in vec.iter_mut() {
            if remove_from_subs(&mut todo.subtasks, sub_id, now) {
                todo.updated_at = now;
                found = true;
                break;
            }
        }
        if found { break; }
    }
    
    if found {
        store.save(&state)?;
        Ok(())
    } else {
        bail!("Subtask not found: {}", sub_id)
    }
}

fn remove_from_subs(subs: &mut Vec<SubTask>, sub_id: u64, now: i64) -> bool {
    if let Some(idx) = subs.iter().position(|s| s.id == sub_id) {
        subs.remove(idx);
        return true;
    }
    
    for sub in subs.iter_mut() {
        if remove_from_subs(&mut sub.subtasks, sub_id, now) {
            sub.updated_at = now;
            return true;
        }
    }
    false
}

pub fn set_color(store: &dyn Store, id: u64, color: ColorTag) -> Result<()> {
    let mut state = store.load()?;
    let now = Local::now().timestamp();
    
    let mut found = false;
    for cat in State::all_categories() {
        let vec = state.get_category_mut(cat);
        // Check Todos
        if let Some(todo) = vec.iter_mut().find(|t| t.id == id) {
            todo.color = color;
            todo.updated_at = now;
            found = true;
        } else {
             // Check Subtasks recursively
             for todo in vec.iter_mut() {
                 if set_color_in_subs(&mut todo.subtasks, id, color, now) {
                     todo.updated_at = now;
                     found = true;
                     break;
                 }
             }
        }
        if found { break; }
    }
    
    if found {
        store.save(&state)?;
        Ok(())
    } else {
        bail!("Item not found: {}", id)
    }
}

fn set_color_in_subs(subs: &mut Vec<SubTask>, id: u64, color: ColorTag, now: i64) -> bool {
    for sub in subs.iter_mut() {
        if sub.id == id {
            sub.color = color;
            sub.updated_at = now;
            return true;
        }
        if set_color_in_subs(&mut sub.subtasks, id, color, now) {
            sub.updated_at = now;
            return true;
        }
    }
    false
}