use crate::task::{parse_line, Line, Task};
use anyhow::{Context, Result};
use std::collections::HashSet;
use std::fs;
use std::path::Path;

pub struct CitrineFile {
    pub lines: Vec<Line>,
}

impl CitrineFile {
    pub fn read(path: &Path) -> Result<Self> {
        let content = fs::read_to_string(path)
            .with_context(|| format!("Failed to read {}", path.display()))?;
        let lines = content.lines().map(parse_line).collect();
        Ok(CitrineFile { lines })
    }

    pub fn read_or_empty(path: &Path) -> Self {
        Self::read(path).unwrap_or(CitrineFile { lines: vec![] })
    }

    pub fn write(&self, path: &Path) -> Result<()> {
        let content: String = self.lines.iter().map(|l| l.to_string()).collect::<Vec<_>>().join("\n");
        let content = if content.is_empty() { content } else { content + "\n" };
        fs::write(path, content).with_context(|| format!("Failed to write {}", path.display()))
    }

    pub fn tasks(&self) -> Vec<&Task> {
        self.lines.iter().filter_map(|l| match l {
            Line::Task(t) => Some(t),
            _ => None,
        }).collect()
    }

    pub fn hashes(&self) -> HashSet<String> {
        self.tasks().iter().map(|t| t.hash.clone()).collect()
    }

    pub fn find_task_mut(&mut self, hash: &str) -> Option<&mut Task> {
        self.lines.iter_mut().find_map(|l| match l {
            Line::Task(t) if t.hash == hash => Some(t),
            _ => None,
        })
    }

    pub fn remove_task(&mut self, hash: &str) -> Option<Task> {
        let pos = self.lines.iter().position(|l| matches!(l, Line::Task(t) if t.hash == hash));
        if let Some(i) = pos {
            if let Line::Task(t) = self.lines.remove(i) {
                return Some(t);
            }
        }
        None
    }
}

pub fn collect_all_hashes(dir: &Path) -> HashSet<String> {
    let mut hashes = HashSet::new();
    let main_file = CitrineFile::read_or_empty(&dir.join(".citrine"));
    let local_file = CitrineFile::read_or_empty(&dir.join(".citrine.local"));
    hashes.extend(main_file.hashes());
    hashes.extend(local_file.hashes());
    hashes
}

pub fn resolve_hash(main: &CitrineFile, local: &CitrineFile, partial: &str) -> Result<(String, bool)> {
    let mut matches: Vec<(String, bool)> = Vec::new();

    for t in main.tasks() {
        if t.hash.starts_with(partial) {
            matches.push((t.hash.clone(), false));
        }
    }
    for t in local.tasks() {
        if t.hash.starts_with(partial) {
            matches.push((t.hash.clone(), true));
        }
    }

    match matches.len() {
        0 => anyhow::bail!("Task not found: {}", partial),
        1 => Ok(matches.remove(0)),
        _ => {
            let hash_list: Vec<&str> = matches.iter().map(|(h, _)| h.as_str()).collect();
            anyhow::bail!("Ambiguous hash '{}': matches {}", partial, hash_list.join(", "));
        }
    }
}
