//! Plans command - list and view Claude Code plans from ~/.claude/plans/

use crate::output::OutputFormatter;
use serde::Serialize;
use std::fs;
use std::path::PathBuf;

/// Plan item for serialization
#[derive(Debug, Serialize, schemars::JsonSchema)]
struct PlanListItem {
    name: String,
    title: String,
    modified: String,
    size: u64,
}

/// Plans list report
#[derive(Debug, Serialize, schemars::JsonSchema)]
pub struct PlansListReport {
    plans: Vec<PlanListItem>,
}

impl OutputFormatter for PlansListReport {
    fn format_text(&self) -> String {
        let mut lines = Vec::new();
        for plan in &self.plans {
            lines.push(format!(
                "{} [{}] {} ({}B)",
                plan.modified, plan.name, plan.title, plan.size
            ));
        }
        lines.push(String::new());
        lines.push(format!("{} plans found", self.plans.len()));
        lines.join("\n")
    }
}

/// Get the plans directory path
fn plans_dir() -> Option<PathBuf> {
    dirs::home_dir().map(|h| h.join(".claude").join("plans"))
}

/// Plan metadata extracted from a plan file
struct PlanInfo {
    name: String,
    title: String,
    modified: std::time::SystemTime,
    size: u64,
}

/// Extract title from plan file (first line: "# Plan: <title>")
fn extract_title(content: &str) -> String {
    // Not chained: second strip_prefix is a fallback, would require duplicating first check
    if let Some(first_line) = content.lines().next() {
        if let Some(title) = first_line.strip_prefix("# Plan: ") {
            return title.to_string();
        }
        if let Some(title) = first_line.strip_prefix("# ") {
            return title.to_string();
        }
    }
    "(untitled)".to_string()
}

/// List all plans
fn list_plans(limit: usize) -> Vec<PlanInfo> {
    let Some(dir) = plans_dir() else {
        return vec![];
    };

    let Ok(entries) = fs::read_dir(&dir) else {
        return vec![];
    };

    let mut plans: Vec<PlanInfo> = entries
        .filter_map(|e| e.ok())
        .filter(|e| e.path().extension().map(|ext| ext == "md").unwrap_or(false))
        .filter_map(|e| {
            let path = e.path();
            let name = path.file_stem()?.to_string_lossy().to_string();
            let meta = e.metadata().ok()?;
            let content = fs::read_to_string(&path).ok()?;
            let title = extract_title(&content);
            Some(PlanInfo {
                name,
                title,
                modified: meta.modified().ok()?,
                size: meta.len(),
            })
        })
        .collect();

    // Sort by modification time (newest first)
    plans.sort_by(|a, b| b.modified.cmp(&a.modified));
    plans.truncate(limit);
    plans
}

/// Format a SystemTime as a human-readable date
fn format_time(time: std::time::SystemTime) -> String {
    let duration = time
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default();
    let secs = duration.as_secs();

    // Simple date formatting (YYYY-MM-DD HH:MM)
    let days_since_epoch = secs / 86400;
    let remaining_secs = secs % 86400;
    let hours = remaining_secs / 3600;
    let minutes = (remaining_secs % 3600) / 60;

    // Calculate date (simplified, doesn't account for leap years perfectly)
    let mut year = 1970;
    let mut remaining_days = days_since_epoch;

    while remaining_days >= 365 {
        let days_in_year = if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
            366
        } else {
            365
        };
        if remaining_days >= days_in_year {
            remaining_days -= days_in_year;
            year += 1;
        } else {
            break;
        }
    }

    let days_in_months: [u64; 12] = if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    } else {
        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    };

    let mut month = 1;
    for days in days_in_months {
        if remaining_days < days {
            break;
        }
        remaining_days -= days;
        month += 1;
    }

    let day = remaining_days + 1;

    format!(
        "{:04}-{:02}-{:02} {:02}:{:02}",
        year, month, day, hours, minutes
    )
}

/// Plan content for viewing a single plan.
#[derive(Debug, Serialize, schemars::JsonSchema)]
pub struct PlanContent {
    pub name: String,
    pub title: String,
    pub content: String,
}

impl std::fmt::Display for PlanContent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.content)
    }
}

/// Build plan list report (data only).
pub fn build_plans_list(limit: usize) -> Result<PlansListReport, String> {
    let dir = plans_dir().ok_or_else(|| "Could not find home directory".to_string())?;
    if !dir.exists() {
        return Ok(PlansListReport { plans: vec![] });
    }

    let plans = list_plans(limit);
    let items: Vec<PlanListItem> = plans
        .iter()
        .map(|p| PlanListItem {
            name: p.name.clone(),
            title: p.title.clone(),
            modified: format_time(p.modified),
            size: p.size,
        })
        .collect();

    Ok(PlansListReport { plans: items })
}

/// Read a single plan's content (data only).
pub fn build_plan_content(plan_name: &str) -> Result<PlanContent, String> {
    let dir = plans_dir().ok_or_else(|| "Could not find home directory".to_string())?;
    let plan_path = dir.join(format!("{}.md", plan_name));

    if plan_path.exists() {
        let content =
            fs::read_to_string(&plan_path).map_err(|e| format!("Error reading plan: {}", e))?;
        let title = extract_title(&content);
        return Ok(PlanContent {
            name: plan_name.to_string(),
            title,
            content,
        });
    }

    // Try fuzzy match
    let plans = list_plans(100);
    let matches: Vec<_> = plans
        .iter()
        .filter(|p| {
            p.name.contains(plan_name) || p.title.to_lowercase().contains(&plan_name.to_lowercase())
        })
        .collect();

    if matches.is_empty() {
        return Err(format!("Plan not found: {}", plan_name));
    }
    if matches.len() > 1 {
        let names: Vec<_> = matches.iter().map(|m| m.name.as_str()).collect();
        return Err(format!(
            "Multiple matches for '{}': {}",
            plan_name,
            names.join(", ")
        ));
    }

    let plan_path = dir.join(format!("{}.md", matches[0].name));
    let content =
        fs::read_to_string(&plan_path).map_err(|e| format!("Error reading plan: {}", e))?;
    let title = extract_title(&content);
    Ok(PlanContent {
        name: matches[0].name.clone(),
        title,
        content,
    })
}
