use regex::Regex;
use std::fmt;

#[derive(Debug, Clone, PartialEq)]
pub enum Status {
    Todo,
    InProgress,
    Blocked,
    Done,
}

impl Status {
    pub fn symbol(&self) -> char {
        match self {
            Status::Todo => ' ',
            Status::InProgress => '>',
            Status::Blocked => '-',
            Status::Done => 'x',
        }
    }

    pub fn from_symbol(c: char) -> Option<Self> {
        match c {
            ' ' => Some(Status::Todo),
            '>' => Some(Status::InProgress),
            '-' => Some(Status::Blocked),
            'x' => Some(Status::Done),
            _ => None,
        }
    }

    pub fn from_name(name: &str) -> Option<Self> {
        match name {
            "todo" => Some(Status::Todo),
            "progress" => Some(Status::InProgress),
            "blocked" => Some(Status::Blocked),
            "done" => Some(Status::Done),
            " " => Some(Status::Todo),
            ">" => Some(Status::InProgress),
            "-" => Some(Status::Blocked),
            "x" => Some(Status::Done),
            _ => None,
        }
    }
}

impl fmt::Display for Status {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[{}]", self.symbol())
    }
}

#[derive(Debug, Clone)]
pub struct Task {
    pub hash: String,
    pub status: Status,
    pub description: String,
    pub metadata: String,
}

impl Task {
    pub fn to_line(&self) -> String {
        let meta = if self.metadata.is_empty() {
            String::new()
        } else {
            format!(" {}", self.metadata)
        };
        format!("[{}] {} {}{}", self.hash, self.status, self.description, meta)
    }
}

#[derive(Debug, Clone)]
pub enum Line {
    Task(Task),
    Section(String),
    Other(String),
}

impl Line {
    pub fn to_string(&self) -> String {
        match self {
            Line::Task(t) => t.to_line(),
            Line::Section(s) => format!("## {}", s),
            Line::Other(s) => s.clone(),
        }
    }
}

pub fn parse_line(line: &str) -> Line {
    let task_re = Regex::new(r"^\[([0-9a-f]{4})\]\s+\[([x\->]| )\]\s+(.+?)(?:\s+(@.*))?$").unwrap();
    let section_re = Regex::new(r"^##\s+(.+)$").unwrap();

    if let Some(caps) = task_re.captures(line) {
        let hash = caps[1].to_string();
        let status = Status::from_symbol(caps[2].chars().next().unwrap()).unwrap();
        let description = caps[3].to_string();
        let metadata = caps.get(4).map_or(String::new(), |m| m.as_str().to_string());
        Line::Task(Task { hash, status, description, metadata })
    } else if let Some(caps) = section_re.captures(line) {
        Line::Section(caps[1].to_string())
    } else {
        Line::Other(line.to_string())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_task_line() {
        let line = "[a4f9] [ ] Implement caching layer @priority:high @backend";
        if let Line::Task(t) = parse_line(line) {
            assert_eq!(t.hash, "a4f9");
            assert_eq!(t.status, Status::Todo);
            assert_eq!(t.description, "Implement caching layer");
            assert_eq!(t.metadata, "@priority:high @backend");
        } else {
            panic!("Expected task");
        }
    }

    #[test]
    fn test_parse_done_task() {
        let line = "[f1a2] [x] Set up Docker config @completed:2025-02-14";
        if let Line::Task(t) = parse_line(line) {
            assert_eq!(t.status, Status::Done);
        } else {
            panic!("Expected task");
        }
    }

    #[test]
    fn test_parse_section() {
        let line = "## Backend";
        if let Line::Section(s) = parse_line(line) {
            assert_eq!(s, "Backend");
        } else {
            panic!("Expected section");
        }
    }

    #[test]
    fn test_parse_other() {
        let line = "some random text";
        assert!(matches!(parse_line(line), Line::Other(_)));
    }

    #[test]
    fn test_task_roundtrip() {
        let line = "[a4f9] [ ] Implement caching layer @priority:high @backend";
        if let Line::Task(t) = parse_line(line) {
            assert_eq!(t.to_line(), line);
        }
    }

    #[test]
    fn test_task_no_metadata() {
        let line = "[d4b3] [ ] Build settings page";
        if let Line::Task(t) = parse_line(line) {
            assert_eq!(t.description, "Build settings page");
            assert_eq!(t.metadata, "");
            assert_eq!(t.to_line(), line);
        } else {
            panic!("Expected task");
        }
    }
}
