use serde::{Deserialize, Serialize};

// ==================== Frontmatter Types ====================

/// Estimate information for frontmatter
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct EstimateFrontmatter {
    /// Optimistic estimate (best case)
    pub optimistic: String,
    /// Normal estimate (most likely)
    pub normal: String,
    /// Pessimistic estimate (worst case)
    pub pessimistic: String,
}

/// YAML frontmatter for task documents (plan.md)
///
/// Note: status is NOT stored here - it lives only in state.json
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct Frontmatter {
    /// Task name (matches folder name)
    pub name: String,
    /// Brief one-line description
    pub description: String,
    /// Affected project components/modules
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub parts: Vec<String>,
    /// Three-point time estimate for senior dev
    #[serde(skip_serializing_if = "Option::is_none")]
    pub estimate: Option<EstimateFrontmatter>,
}

impl Frontmatter {
    /// Create new frontmatter with required fields
    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            description: description.into(),
            parts: Vec::new(),
            estimate: None,
        }
    }

    /// Add parts to the frontmatter
    pub fn with_parts(mut self, parts: Vec<String>) -> Self {
        self.parts = parts;
        self
    }

    /// Add estimate to the frontmatter
    pub fn with_estimate(mut self, estimate: EstimateFrontmatter) -> Self {
        self.estimate = Some(estimate);
        self
    }

    /// Validate the frontmatter and return any warnings
    pub fn validate(&self) -> Vec<String> {
        let mut warnings = Vec::new();

        if self.name.is_empty() {
            warnings.push("Frontmatter: 'name' field is empty".to_string());
        }

        if self.description.is_empty() {
            warnings.push("Frontmatter: 'description' field is empty".to_string());
        }

        if let Some(estimate) = &self.estimate {
            if estimate.optimistic.is_empty() {
                warnings.push("Frontmatter: 'estimate.optimistic' field is empty".to_string());
            }
            if estimate.normal.is_empty() {
                warnings.push("Frontmatter: 'estimate.normal' field is empty".to_string());
            }
            if estimate.pessimistic.is_empty() {
                warnings.push("Frontmatter: 'estimate.pessimistic' field is empty".to_string());
            }
        }

        warnings
    }
}

/// Result of parsing frontmatter from markdown
#[derive(Debug, Clone)]
pub struct ParsedDocument {
    /// The parsed frontmatter (if present)
    pub frontmatter: Option<Frontmatter>,
    /// The markdown body (content after frontmatter)
    pub body: String,
    /// Any warnings from parsing/validation
    pub warnings: Vec<String>,
}

impl ParsedDocument {
    /// Create a new parsed document with just body content (no frontmatter)
    pub fn body_only(body: impl Into<String>) -> Self {
        Self {
            frontmatter: None,
            body: body.into(),
            warnings: Vec::new(),
        }
    }

    /// Create a new parsed document with frontmatter and body
    pub fn with_frontmatter(frontmatter: Frontmatter, body: impl Into<String>) -> Self {
        let warnings = frontmatter.validate();
        Self {
            frontmatter: Some(frontmatter),
            body: body.into(),
            warnings,
        }
    }
}

/// Parse YAML frontmatter from markdown content
///
/// Frontmatter must be at the very beginning of the document,
/// delimited by `---` on its own line.
pub fn parse_frontmatter(markdown: &str) -> ParsedDocument {
    let trimmed = markdown.trim_start();

    // Check if the document starts with frontmatter delimiter
    if !trimmed.starts_with("---") {
        return ParsedDocument::body_only(markdown);
    }

    // Find the start of the YAML (after first ---)
    let after_first_delimiter = &trimmed[3..];
    let yaml_start = if after_first_delimiter.starts_with('\n') {
        1
    } else if after_first_delimiter.starts_with("\r\n") {
        2
    } else {
        // No newline after ---, not valid frontmatter
        return ParsedDocument::body_only(markdown);
    };

    let yaml_content = &after_first_delimiter[yaml_start..];

    // Find the closing delimiter
    let closing_pos = yaml_content.find("\n---");
    if closing_pos.is_none() {
        // Try with \r\n
        if let Some(pos) = yaml_content.find("\r\n---") {
            let yaml_str = &yaml_content[..pos];
            let body_start = pos + 5; // \r\n---
            let body = if body_start < yaml_content.len() {
                let remaining = &yaml_content[body_start..];
                // Skip the newline after closing ---
                if remaining.starts_with('\n') {
                    &remaining[1..]
                } else if remaining.starts_with("\r\n") {
                    &remaining[2..]
                } else {
                    remaining
                }
            } else {
                ""
            };

            return parse_yaml_frontmatter(yaml_str, body);
        }
        // No closing delimiter found
        return ParsedDocument::body_only(markdown);
    }

    let closing_pos = closing_pos.unwrap();
    let yaml_str = &yaml_content[..closing_pos];
    let body_start = closing_pos + 4; // \n---
    let body = if body_start < yaml_content.len() {
        let remaining = &yaml_content[body_start..];
        // Skip the newline after closing ---
        if remaining.starts_with('\n') {
            &remaining[1..]
        } else if remaining.starts_with("\r\n") {
            &remaining[2..]
        } else {
            remaining
        }
    } else {
        ""
    };

    parse_yaml_frontmatter(yaml_str, body)
}

/// Parse the YAML string into Frontmatter struct
fn parse_yaml_frontmatter(yaml_str: &str, body: &str) -> ParsedDocument {
    match serde_yaml::from_str::<Frontmatter>(yaml_str) {
        Ok(frontmatter) => ParsedDocument::with_frontmatter(frontmatter, body),
        Err(e) => {
            let mut doc = ParsedDocument::body_only(body);
            doc.warnings
                .push(format!("Failed to parse frontmatter YAML: {}", e));
            doc
        }
    }
}

/// Generate YAML frontmatter string from a Frontmatter struct
pub fn generate_frontmatter(frontmatter: &Frontmatter) -> String {
    let yaml = serde_yaml::to_string(frontmatter).unwrap_or_default();
    format!("---\n{}---\n\n", yaml)
}

/// Update or insert frontmatter in a markdown document
pub fn update_frontmatter_in_markdown(markdown: &str, frontmatter: &Frontmatter) -> String {
    let parsed = parse_frontmatter(markdown);
    let frontmatter_str = generate_frontmatter(frontmatter);

    // If document already has frontmatter, replace it; otherwise prepend
    format!("{}{}", frontmatter_str, parsed.body.trim_start())
}

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

    #[test]
    fn test_parse_frontmatter_no_frontmatter() {
        let markdown = "# Task: Test\n\nSome content here.";
        let parsed = parse_frontmatter(markdown);

        assert!(parsed.frontmatter.is_none());
        assert_eq!(parsed.body, markdown);
        assert!(parsed.warnings.is_empty());
    }

    #[test]
    fn test_parse_frontmatter_valid() {
        let markdown = r#"---
name: test-task
description: A test task
parts:
  - api
  - storage
---

# Task: Test

Some content here."#;

        let parsed = parse_frontmatter(markdown);

        assert!(parsed.frontmatter.is_some());
        let fm = parsed.frontmatter.unwrap();
        assert_eq!(fm.name, "test-task");
        assert_eq!(fm.description, "A test task");
        assert_eq!(fm.parts, vec!["api", "storage"]);
        assert!(fm.estimate.is_none());
        assert!(parsed.body.contains("# Task: Test"));
    }

    #[test]
    fn test_parse_frontmatter_with_estimate() {
        let markdown = r#"---
name: test-task
description: A test task
estimate:
  optimistic: 2h
  normal: 4h
  pessimistic: 8h
---

Content here."#;

        let parsed = parse_frontmatter(markdown);

        assert!(parsed.frontmatter.is_some());
        let fm = parsed.frontmatter.unwrap();
        assert!(fm.estimate.is_some());
        let est = fm.estimate.unwrap();
        assert_eq!(est.optimistic, "2h");
        assert_eq!(est.normal, "4h");
        assert_eq!(est.pessimistic, "8h");
    }

    #[test]
    fn test_parse_frontmatter_invalid_yaml() {
        let markdown = r#"---
name: test
invalid: [unclosed
---

Content"#;

        let parsed = parse_frontmatter(markdown);

        assert!(parsed.frontmatter.is_none());
        assert!(!parsed.warnings.is_empty());
        assert!(parsed.warnings[0].contains("Failed to parse frontmatter"));
    }

    #[test]
    fn test_generate_frontmatter() {
        let fm = Frontmatter::new("my-task", "My description")
            .with_parts(vec!["api".to_string(), "db".to_string()])
            .with_estimate(EstimateFrontmatter {
                optimistic: "2h".to_string(),
                normal: "4h".to_string(),
                pessimistic: "8h".to_string(),
            });

        let result = generate_frontmatter(&fm);

        assert!(result.starts_with("---\n"));
        assert!(result.ends_with("---\n\n"));
        assert!(result.contains("name: my-task"));
        assert!(result.contains("description: My description"));
        assert!(result.contains("parts:"));
        assert!(result.contains("- api"));
        assert!(result.contains("estimate:"));
        assert!(result.contains("normal: 4h"));
    }

    #[test]
    fn test_update_frontmatter_add_new() {
        let markdown = "# Task: Test\n\nSome content.";
        let fm = Frontmatter::new("test", "Test task");

        let result = update_frontmatter_in_markdown(markdown, &fm);

        assert!(result.starts_with("---\n"));
        assert!(result.contains("name: test"));
        assert!(result.contains("# Task: Test"));
    }

    #[test]
    fn test_update_frontmatter_replace_existing() {
        let markdown = r#"---
name: old-name
description: Old description
---

# Task: Test

Content here."#;

        let fm = Frontmatter::new("new-name", "New description");
        let result = update_frontmatter_in_markdown(markdown, &fm);

        assert!(result.contains("name: new-name"));
        assert!(result.contains("description: New description"));
        assert!(!result.contains("old-name"));
        assert!(result.contains("# Task: Test"));
    }

    #[test]
    fn test_frontmatter_validation() {
        let fm = Frontmatter {
            name: String::new(),
            description: String::new(),
            parts: vec![],
            estimate: Some(EstimateFrontmatter {
                optimistic: String::new(),
                normal: "4h".to_string(),
                pessimistic: String::new(),
            }),
        };

        let warnings = fm.validate();

        assert!(warnings.len() >= 3);
        assert!(warnings.iter().any(|w| w.contains("name")));
        assert!(warnings.iter().any(|w| w.contains("description")));
        assert!(warnings.iter().any(|w| w.contains("optimistic")));
    }
}

/// Status of a task through its lifecycle
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum TaskStatus {
    /// Initial markdown created
    #[default]
    Draft,
    /// Being analyzed for gaps
    Analyzing,
    /// Waiting for human Q&A responses
    AwaitingAnswers,
    /// Complete definition generated, ready to implement
    Ready,
    /// Claude Code is building it
    Implementing,
    /// In code review cycle
    Reviewing,
    /// Complete
    Done,
}

impl std::fmt::Display for TaskStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Draft => write!(f, "draft"),
            Self::Analyzing => write!(f, "analyzing"),
            Self::AwaitingAnswers => write!(f, "awaiting_answers"),
            Self::Ready => write!(f, "ready"),
            Self::Implementing => write!(f, "implementing"),
            Self::Reviewing => write!(f, "reviewing"),
            Self::Done => write!(f, "done"),
        }
    }
}

impl std::str::FromStr for TaskStatus {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "draft" => Ok(Self::Draft),
            "analyzing" => Ok(Self::Analyzing),
            "awaiting_answers" => Ok(Self::AwaitingAnswers),
            "ready" => Ok(Self::Ready),
            "implementing" => Ok(Self::Implementing),
            "reviewing" => Ok(Self::Reviewing),
            "done" => Ok(Self::Done),
            _ => Err(format!("Invalid status: {}", s)),
        }
    }
}

/// Category of clarifying question
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum QuestionCategory {
    /// Clarifying existing requirements
    #[default]
    Clarification,
    /// Missing requirement identified
    MissingRequirement,
    /// Edge case to consider
    EdgeCase,
    /// Integration with existing code
    Integration,
    /// Testing considerations
    Testing,
    /// Other question type
    Other,
}

impl std::fmt::Display for QuestionCategory {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Clarification => write!(f, "clarification"),
            Self::MissingRequirement => write!(f, "missing_requirement"),
            Self::EdgeCase => write!(f, "edge_case"),
            Self::Integration => write!(f, "integration"),
            Self::Testing => write!(f, "testing"),
            Self::Other => write!(f, "other"),
        }
    }
}

/// A clarifying question for a task
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Question {
    /// Unique identifier for the question
    pub id: String,
    /// The question text
    pub question: String,
    /// Whether the question has been answered
    pub answered: bool,
    /// The answer (if provided)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub answer: Option<String>,
    /// Category of the question
    pub category: QuestionCategory,
}

impl Question {
    pub fn new(
        id: impl Into<String>,
        question: impl Into<String>,
        category: QuestionCategory,
    ) -> Self {
        Self {
            id: id.into(),
            question: question.into(),
            answered: false,
            answer: None,
            category,
        }
    }
}

/// State of a task being developed
///
/// This is a slim struct containing only task lifecycle state.
/// Features are stored in feature-list.json, questions in questions.json.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskState {
    /// Current status
    pub status: TaskStatus,
    /// When the task was created
    pub created_at: String,
    /// When the task was last updated
    pub updated_at: String,
    /// Current review session ID (if in review)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub current_review: Option<String>,
    /// Number of review iterations completed
    #[serde(default)]
    pub review_iterations: u32,
    /// Task-level override for feature approval mode (overrides project config)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub feature_approval_mode: Option<FeatureApprovalMode>,
    /// Files modified during task implementation (for auto-commit)
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub modified_files: Vec<String>,
}

impl TaskState {
    pub fn new() -> Self {
        let now = chrono::Utc::now().to_rfc3339();
        Self {
            status: TaskStatus::Draft,
            created_at: now.clone(),
            updated_at: now,
            current_review: None,
            review_iterations: 0,
            feature_approval_mode: None,
            modified_files: Vec::new(),
        }
    }

    /// Set task-level feature approval mode override
    pub fn with_feature_approval_mode(mut self, mode: FeatureApprovalMode) -> Self {
        self.feature_approval_mode = Some(mode);
        self
    }

    /// Set the modified files for this task (used for auto-commit)
    pub fn with_modified_files(mut self, files: Vec<String>) -> Self {
        self.modified_files = files;
        self
    }

    /// Add files to the modified files list (deduplicates)
    pub fn add_modified_files(&mut self, files: &[String]) {
        for file in files {
            if !self.modified_files.contains(file) {
                self.modified_files.push(file.clone());
            }
        }
    }

    pub fn touch(&mut self) {
        self.updated_at = chrono::Utc::now().to_rfc3339();
    }

    /// Get the effective feature approval mode for this task
    ///
    /// Priority: task override > project config > default (manual)
    pub fn get_effective_approval_mode(
        &self,
        project_config: &ProjectConfig,
    ) -> FeatureApprovalMode {
        self.feature_approval_mode
            .unwrap_or(project_config.feature_approval_mode)
    }
}

impl Default for TaskState {
    fn default() -> Self {
        Self::new()
    }
}

/// Summary of a task for listing
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskSummary {
    /// Row number for easy selection (1-based, assigned after sorting)
    pub row: usize,
    pub name: String,
    pub status: TaskStatus,
    pub unanswered_questions: usize,
    pub review_iterations: u32,
}

impl TaskSummary {
    /// Create a new TaskSummary
    pub fn new(
        name: impl Into<String>,
        status: TaskStatus,
        unanswered_questions: usize,
        review_iterations: u32,
    ) -> Self {
        Self {
            row: 0, // Will be assigned after sorting in list_tasks()
            name: name.into(),
            status,
            unanswered_questions,
            review_iterations,
        }
    }
}

/// Project context information gathered from analyzing a codebase
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ProjectContext {
    /// Root path of the project
    pub root_path: String,
    /// Detected programming languages
    #[serde(default)]
    pub languages: Vec<String>,
    /// Detected frameworks
    #[serde(default)]
    pub frameworks: Vec<String>,
    /// Whether the project has tests
    #[serde(default)]
    pub has_tests: bool,
    /// Test framework if detected
    #[serde(skip_serializing_if = "Option::is_none")]
    pub test_framework: Option<String>,
    /// Key directories in the project
    #[serde(default)]
    pub key_directories: Vec<String>,
    /// Notable patterns or conventions
    #[serde(default)]
    pub patterns: Vec<String>,
}

// ==================== Feature Types ====================

/// Complexity level for a feature
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum FeatureComplexity {
    /// Simple - straightforward implementation
    Simple,
    /// Moderate - some complexity involved
    #[default]
    Moderate,
    /// Complex - significant complexity
    Complex,
}

impl std::fmt::Display for FeatureComplexity {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Simple => write!(f, "simple"),
            Self::Moderate => write!(f, "moderate"),
            Self::Complex => write!(f, "complex"),
        }
    }
}

impl std::str::FromStr for FeatureComplexity {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "simple" => Ok(Self::Simple),
            "moderate" => Ok(Self::Moderate),
            "complex" => Ok(Self::Complex),
            _ => Err(format!("Invalid complexity: {}", s)),
        }
    }
}

/// A discrete feature extracted from a task description
///
/// Features are stored in feature-list.json with verification steps.
/// Priority is determined by array order (first = highest priority).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Feature {
    /// Unique identifier for the feature (e.g., "F1", "F2")
    pub id: String,
    /// Free-form category (e.g., "functional", "ui", "api", "security")
    pub category: String,
    /// Description of what this feature does
    pub description: String,
    /// Verification steps to confirm feature works (LLM provides these)
    #[serde(default)]
    pub steps: Vec<String>,
    /// Acceptance criteria for this feature
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub acceptance_criteria: Vec<String>,
    /// Whether all verification steps have been verified
    #[serde(default)]
    pub passes: bool,
    /// Complexity level (optional but encouraged)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub complexity: Option<FeatureComplexity>,
    /// IDs of features this depends on (optional but encouraged)
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub dependencies: Vec<String>,
}

impl Feature {
    /// Create a new feature with required fields
    pub fn new(
        id: impl Into<String>,
        category: impl Into<String>,
        description: impl Into<String>,
        steps: Vec<String>,
    ) -> Self {
        Self {
            id: id.into(),
            category: category.into(),
            description: description.into(),
            steps,
            acceptance_criteria: Vec::new(),
            passes: false,
            complexity: None,
            dependencies: Vec::new(),
        }
    }

    /// Set complexity
    pub fn with_complexity(mut self, complexity: FeatureComplexity) -> Self {
        self.complexity = Some(complexity);
        self
    }

    /// Set dependencies
    pub fn with_dependencies(mut self, dependencies: Vec<String>) -> Self {
        self.dependencies = dependencies;
        self
    }
}

// ==================== Project Configuration Types ====================

/// Feature approval mode for a project or task
///
/// Controls whether the agent can automatically proceed to the next feature
/// or must wait for human approval after completing each feature.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum FeatureApprovalMode {
    /// Agent proceeds to the next feature automatically after completing one
    Auto,
    /// Agent must wait for human approval after completing each feature (default)
    #[default]
    Manual,
}

impl std::fmt::Display for FeatureApprovalMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Auto => write!(f, "auto"),
            Self::Manual => write!(f, "manual"),
        }
    }
}

impl std::str::FromStr for FeatureApprovalMode {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "auto" => Ok(Self::Auto),
            "manual" => Ok(Self::Manual),
            _ => Err(format!(
                "Invalid feature approval mode: '{}'. Valid values: 'auto', 'manual'",
                s
            )),
        }
    }
}

/// Project-level configuration for K4000
///
/// Stored in `tasks/.config/config.toml`. Designed for extensibility -
/// new settings can be added without breaking existing configs.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectConfig {
    /// Feature approval mode: auto or manual (default: manual)
    #[serde(default)]
    pub feature_approval_mode: FeatureApprovalMode,
    /// Maximum number of acceptance criteria verification iterations
    #[serde(default = "default_acceptance_criteria_max_iterations")]
    pub acceptance_criteria_max_iterations: u32,
    /// Automatically create a git commit when a task is marked as done (default: false)
    #[serde(default)]
    pub auto_commit_on_done: bool,
}

impl Default for ProjectConfig {
    fn default() -> Self {
        Self {
            feature_approval_mode: FeatureApprovalMode::Manual,
            acceptance_criteria_max_iterations: default_acceptance_criteria_max_iterations(),
            auto_commit_on_done: false,
        }
    }
}

fn default_acceptance_criteria_max_iterations() -> u32 {
    5
}

/// Comment block for feature_approval_mode setting
const FEATURE_APPROVAL_MODE_COMMENT: &str = "\
# Feature approval mode controls whether the agent waits for human approval
# between features during implementation.
#
# Values:
#   - \"manual\" (default): Agent pauses after each feature and asks user
#                         for approval before starting the next feature
#   - \"auto\": Agent proceeds to the next feature automatically
#
";

/// Comment block for acceptance_criteria_max_iterations setting
const ACCEPTANCE_CRITERIA_MAX_ITERATIONS_COMMENT: &str = "\
# Maximum number of verification loops for acceptance criteria before failing.
#
# Values:
#   - Integer >= 1 (default: 5)
#
";

/// Comment block for auto_commit_on_done setting
const AUTO_COMMIT_ON_DONE_COMMENT: &str = "\
# Automatically create a git commit when a task is marked as done.
#
# When enabled, K4000 will stage only the files modified during the task
# (tracked via review files) and commit them with a message containing
# the task name and description.
#
# Values:
#   - false (default): No automatic commit, current behavior
#   - true: Automatically commit when task status is set to 'done'
#
# Note: If the commit fails (e.g., pre-commit hook fails), the status
# change to 'done' will be blocked.
#
";

impl ProjectConfig {
    /// Create a new project config with default values
    pub fn new() -> Self {
        Self::default()
    }

    /// Parse config from TOML string
    pub fn from_toml(toml_str: &str) -> Result<Self, toml::de::Error> {
        toml::from_str(toml_str)
    }

    /// Serialize config to TOML string
    pub fn to_toml(&self) -> Result<String, toml::ser::Error> {
        toml::to_string_pretty(self)
    }

    /// Serialize config to TOML string with helpful comments explaining each setting.
    /// This is used when generating the initial config file.
    pub fn to_toml_with_comments(&self) -> String {
        let mut output = String::new();

        output.push_str("# K4000 Project Configuration\n");
        output.push_str("# This file controls K4000 behavior for all tasks in this project.\n");
        output.push_str("#\n");
        output.push_str("# For documentation, see: https://github.com/anthropics/k4000\n");
        output.push_str("\n");

        // feature_approval_mode
        output.push_str(FEATURE_APPROVAL_MODE_COMMENT);
        output.push_str(&format!(
            "feature_approval_mode = \"{}\"\n",
            self.feature_approval_mode
        ));

        output.push('\n');

        // acceptance_criteria_max_iterations
        output.push_str(ACCEPTANCE_CRITERIA_MAX_ITERATIONS_COMMENT);
        output.push_str(&format!(
            "acceptance_criteria_max_iterations = {}\n",
            self.acceptance_criteria_max_iterations
        ));

        output.push('\n');

        // auto_commit_on_done
        output.push_str(AUTO_COMMIT_ON_DONE_COMMENT);
        output.push_str(&format!(
            "auto_commit_on_done = {}\n",
            self.auto_commit_on_done
        ));

        output
    }

    /// Merge this config with values from an existing config, preserving existing values.
    /// Only adds missing settings from self (the defaults) to the existing config.
    pub fn merge_with_existing(&self, existing_content: &str) -> String {
        // Parse existing content to check which settings are present
        let existing: Result<ProjectConfig, _> = toml::from_str(existing_content);

        match existing {
            Ok(_) => {
                // Config parses successfully - check if all settings are present
                let has_feature_approval_mode = existing_content.contains("feature_approval_mode");
                let has_acceptance_criteria_max_iterations =
                    existing_content.contains("acceptance_criteria_max_iterations");
                let has_auto_commit_on_done = existing_content.contains("auto_commit_on_done");

                if has_feature_approval_mode
                    && has_acceptance_criteria_max_iterations
                    && has_auto_commit_on_done
                {
                    // All settings present, return existing content unchanged
                    existing_content.to_string()
                } else {
                    // Missing settings, append them with comments
                    let mut output = existing_content.to_string();
                    if !output.ends_with('\n') {
                        output.push('\n');
                    }
                    output.push('\n');
                    if !has_feature_approval_mode {
                        output.push_str(FEATURE_APPROVAL_MODE_COMMENT);
                        output.push_str(&format!(
                            "feature_approval_mode = \"{}\"\n",
                            FeatureApprovalMode::default()
                        ));
                        output.push('\n');
                    }
                    if !has_acceptance_criteria_max_iterations {
                        output.push_str(ACCEPTANCE_CRITERIA_MAX_ITERATIONS_COMMENT);
                        output.push_str(&format!(
                            "acceptance_criteria_max_iterations = {}\n",
                            default_acceptance_criteria_max_iterations()
                        ));
                        output.push('\n');
                    }
                    if !has_auto_commit_on_done {
                        output.push_str(AUTO_COMMIT_ON_DONE_COMMENT);
                        output.push_str("auto_commit_on_done = false\n");
                    }
                    output
                }
            }
            Err(_) => {
                // Existing config is invalid/unparseable, preserve it and add a note
                let mut output = existing_content.to_string();
                if !output.ends_with('\n') {
                    output.push('\n');
                }
                output.push_str("\n# WARNING: K4000 could not parse the above config.\n");
                output.push_str("# Please fix any syntax errors. Default settings shown below:\n");
                output.push_str(&format!(
                    "# feature_approval_mode = \"{}\"\n",
                    FeatureApprovalMode::default()
                ));
                output.push_str(&format!(
                    "# acceptance_criteria_max_iterations = {}\n",
                    default_acceptance_criteria_max_iterations()
                ));
                output.push_str("# auto_commit_on_done = false\n");
                output
            }
        }
    }

    /// Check if the given config content has all required settings.
    /// Returns true if all settings are present (even if with different values).
    pub fn has_all_settings(content: &str) -> bool {
        content.contains("feature_approval_mode")
            && content.contains("acceptance_criteria_max_iterations")
            && content.contains("auto_commit_on_done")
    }
}

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

    #[test]
    fn test_feature_approval_mode_default() {
        assert_eq!(FeatureApprovalMode::default(), FeatureApprovalMode::Manual);
    }

    #[test]
    fn test_feature_approval_mode_from_str() {
        assert_eq!(
            "auto".parse::<FeatureApprovalMode>().unwrap(),
            FeatureApprovalMode::Auto
        );
        assert_eq!(
            "manual".parse::<FeatureApprovalMode>().unwrap(),
            FeatureApprovalMode::Manual
        );
        assert_eq!(
            "AUTO".parse::<FeatureApprovalMode>().unwrap(),
            FeatureApprovalMode::Auto
        );
        assert!("invalid".parse::<FeatureApprovalMode>().is_err());
    }

    #[test]
    fn test_feature_approval_mode_display() {
        assert_eq!(FeatureApprovalMode::Auto.to_string(), "auto");
        assert_eq!(FeatureApprovalMode::Manual.to_string(), "manual");
    }

    #[test]
    fn test_project_config_default() {
        let config = ProjectConfig::default();
        assert_eq!(config.feature_approval_mode, FeatureApprovalMode::Manual);
        assert_eq!(config.acceptance_criteria_max_iterations, 5);
        assert!(!config.auto_commit_on_done);
    }

    #[test]
    fn test_project_config_from_toml() {
        let toml_str = r#"
            feature_approval_mode = "auto"
            acceptance_criteria_max_iterations = 7
            auto_commit_on_done = true
        "#;
        let config = ProjectConfig::from_toml(toml_str).unwrap();
        assert_eq!(config.feature_approval_mode, FeatureApprovalMode::Auto);
        assert_eq!(config.acceptance_criteria_max_iterations, 7);
        assert!(config.auto_commit_on_done);
    }

    #[test]
    fn test_project_config_from_toml_empty() {
        // Empty config should use defaults
        let toml_str = "";
        let config = ProjectConfig::from_toml(toml_str).unwrap();
        assert_eq!(config.feature_approval_mode, FeatureApprovalMode::Manual);
        assert_eq!(config.acceptance_criteria_max_iterations, 5);
        assert!(!config.auto_commit_on_done);
    }

    #[test]
    fn test_project_config_to_toml() {
        let config = ProjectConfig {
            feature_approval_mode: FeatureApprovalMode::Auto,
            acceptance_criteria_max_iterations: 7,
            auto_commit_on_done: true,
        };
        let toml_str = config.to_toml().unwrap();
        assert!(toml_str.contains("feature_approval_mode = \"auto\""));
        assert!(toml_str.contains("acceptance_criteria_max_iterations = 7"));
        assert!(toml_str.contains("auto_commit_on_done = true"));
    }

    #[test]
    fn test_project_config_roundtrip() {
        let original = ProjectConfig {
            feature_approval_mode: FeatureApprovalMode::Auto,
            acceptance_criteria_max_iterations: 6,
            auto_commit_on_done: true,
        };
        let toml_str = original.to_toml().unwrap();
        let parsed = ProjectConfig::from_toml(&toml_str).unwrap();
        assert_eq!(original.feature_approval_mode, parsed.feature_approval_mode);
        assert_eq!(
            original.acceptance_criteria_max_iterations,
            parsed.acceptance_criteria_max_iterations
        );
        assert_eq!(original.auto_commit_on_done, parsed.auto_commit_on_done);
    }

    #[test]
    fn test_to_toml_with_comments() {
        let config = ProjectConfig::default();
        let output = config.to_toml_with_comments();

        // Check header comments
        assert!(output.contains("# K4000 Project Configuration"));
        assert!(output.contains("# This file controls K4000 behavior"));

        // Check feature_approval_mode comments
        assert!(output.contains("# Feature approval mode controls"));
        assert!(output.contains("# Values:"));
        assert!(output.contains("\"manual\" (default)"));
        assert!(output.contains("\"auto\""));

        // Check acceptance criteria comments
        assert!(output.contains("# Maximum number of verification loops"));

        // Check auto_commit_on_done comments
        assert!(output.contains("# Automatically create a git commit"));
        assert!(output.contains("auto_commit_on_done = false"));

        // Check the actual values
        assert!(output.contains("feature_approval_mode = \"manual\""));
        assert!(output.contains("acceptance_criteria_max_iterations = 5"));

        // Ensure it's valid TOML
        let parsed = ProjectConfig::from_toml(&output).unwrap();
        assert_eq!(parsed.feature_approval_mode, FeatureApprovalMode::Manual);
        assert_eq!(parsed.acceptance_criteria_max_iterations, 5);
        assert!(!parsed.auto_commit_on_done);
    }

    #[test]
    fn test_to_toml_with_comments_auto_mode() {
        let config = ProjectConfig {
            feature_approval_mode: FeatureApprovalMode::Auto,
            acceptance_criteria_max_iterations: 6,
            auto_commit_on_done: true,
        };
        let output = config.to_toml_with_comments();

        assert!(output.contains("feature_approval_mode = \"auto\""));
        assert!(output.contains("acceptance_criteria_max_iterations = 6"));
        assert!(output.contains("auto_commit_on_done = true"));

        // Ensure it's valid TOML
        let parsed = ProjectConfig::from_toml(&output).unwrap();
        assert_eq!(parsed.feature_approval_mode, FeatureApprovalMode::Auto);
        assert_eq!(parsed.acceptance_criteria_max_iterations, 6);
        assert!(parsed.auto_commit_on_done);
    }

    #[test]
    fn test_merge_with_existing_preserves_user_values() {
        let defaults = ProjectConfig::default();

        // User has set feature_approval_mode to "auto" and auto_commit_on_done to true
        let existing = r#"feature_approval_mode = "auto"
acceptance_criteria_max_iterations = 9
auto_commit_on_done = true"#;
        let merged = defaults.merge_with_existing(existing);

        // Should preserve the user's "auto" setting, not overwrite with "manual"
        assert!(merged.contains("feature_approval_mode = \"auto\""));
        assert!(!merged.contains("feature_approval_mode = \"manual\""));
        assert!(merged.contains("acceptance_criteria_max_iterations = 9"));
        assert!(merged.contains("auto_commit_on_done = true"));
    }

    #[test]
    fn test_merge_with_existing_adds_missing_settings() {
        let defaults = ProjectConfig::default();

        // Empty config file
        let existing = "# My custom comment\n";
        let merged = defaults.merge_with_existing(existing);

        // Should preserve custom comment
        assert!(merged.contains("# My custom comment"));

        // Should add missing settings with default values
        assert!(merged.contains("feature_approval_mode = \"manual\""));
        assert!(merged.contains("acceptance_criteria_max_iterations = 5"));
        assert!(merged.contains("auto_commit_on_done = false"));

        // Should add comments for the new settings
        assert!(merged.contains("# Feature approval mode controls"));
        assert!(merged.contains("# Maximum number of verification loops"));
        assert!(merged.contains("# Automatically create a git commit"));
    }

    #[test]
    fn test_merge_with_existing_handles_invalid_toml() {
        let defaults = ProjectConfig::default();

        // Invalid TOML
        let existing = "this is not valid [ toml";
        let merged = defaults.merge_with_existing(existing);

        // Should preserve the invalid content
        assert!(merged.contains("this is not valid [ toml"));

        // Should add a warning
        assert!(merged.contains("# WARNING: K4000 could not parse"));

        // Should show the defaults as comments
        assert!(merged.contains("# feature_approval_mode = \"manual\""));
        assert!(merged.contains("# acceptance_criteria_max_iterations = 5"));
        assert!(merged.contains("# auto_commit_on_done = false"));
    }

    #[test]
    fn test_has_all_settings() {
        assert!(ProjectConfig::has_all_settings(
            "feature_approval_mode = \"auto\"\nacceptance_criteria_max_iterations = 5\nauto_commit_on_done = false"
        ));
        assert!(ProjectConfig::has_all_settings(
            "# comment\nfeature_approval_mode = \"manual\"\nacceptance_criteria_max_iterations = 6\nauto_commit_on_done = true\n"
        ));
        assert!(!ProjectConfig::has_all_settings("# just a comment"));
        assert!(!ProjectConfig::has_all_settings(""));
        // Missing auto_commit_on_done
        assert!(!ProjectConfig::has_all_settings(
            "feature_approval_mode = \"auto\"\nacceptance_criteria_max_iterations = 5"
        ));
    }

    #[test]
    fn test_auto_commit_on_done_setting() {
        // Test parsing auto_commit_on_done = true
        let toml_str = r#"
            auto_commit_on_done = true
        "#;
        let config = ProjectConfig::from_toml(toml_str).unwrap();
        assert!(config.auto_commit_on_done);

        // Test parsing auto_commit_on_done = false
        let toml_str = r#"
            auto_commit_on_done = false
        "#;
        let config = ProjectConfig::from_toml(toml_str).unwrap();
        assert!(!config.auto_commit_on_done);

        // Test default (not specified)
        let toml_str = r#"
            feature_approval_mode = "auto"
        "#;
        let config = ProjectConfig::from_toml(toml_str).unwrap();
        assert!(!config.auto_commit_on_done); // Should default to false
    }
}

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

    #[test]
    fn test_task_state_default() {
        let state = TaskState::new();
        assert_eq!(state.status, TaskStatus::Draft);
        assert!(state.modified_files.is_empty());
    }

    #[test]
    fn test_add_modified_files() {
        let mut state = TaskState::new();

        // Add some files
        state.add_modified_files(&["src/foo.rs".to_string(), "src/bar.rs".to_string()]);
        assert_eq!(state.modified_files.len(), 2);
        assert!(state.modified_files.contains(&"src/foo.rs".to_string()));
        assert!(state.modified_files.contains(&"src/bar.rs".to_string()));

        // Add duplicate - should not increase count
        state.add_modified_files(&["src/foo.rs".to_string()]);
        assert_eq!(state.modified_files.len(), 2);

        // Add new file
        state.add_modified_files(&["src/baz.rs".to_string()]);
        assert_eq!(state.modified_files.len(), 3);
    }

    #[test]
    fn test_with_modified_files() {
        let files = vec!["a.rs".to_string(), "b.rs".to_string()];
        let state = TaskState::new().with_modified_files(files.clone());
        assert_eq!(state.modified_files, files);
    }

    #[test]
    fn test_modified_files_serialization() {
        let mut state = TaskState::new();
        state.add_modified_files(&["src/main.rs".to_string()]);

        // Serialize
        let json = serde_json::to_string(&state).unwrap();
        assert!(json.contains("modified_files"));
        assert!(json.contains("src/main.rs"));

        // Deserialize
        let loaded: TaskState = serde_json::from_str(&json).unwrap();
        assert_eq!(loaded.modified_files.len(), 1);
        assert_eq!(loaded.modified_files[0], "src/main.rs");
    }

    #[test]
    fn test_empty_modified_files_not_serialized() {
        let state = TaskState::new();

        // Empty vec should not appear in JSON due to skip_serializing_if
        let json = serde_json::to_string(&state).unwrap();
        assert!(!json.contains("modified_files"));
    }
}
