gdelt 0.1.0

CLI for GDELT Project - optimized for agentic usage with local data caching
//! Skill installation and management.

use crate::error::{GdeltError, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

use super::manifest::SkillManifest;

/// Status of the installed skill
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkillStatus {
    pub installed: bool,
    pub location: Option<String>,
    pub version: Option<String>,
    pub is_project_local: bool,
}

/// Get the global skills directory (~/.claude/skills/)
fn get_global_skills_dir() -> Result<PathBuf> {
    let home = dirs::home_dir()
        .ok_or_else(|| GdeltError::Config("Could not determine home directory".into()))?;
    Ok(home.join(".claude").join("skills"))
}

/// Get the project-local skills directory (.claude/skills/)
fn get_project_skills_dir() -> PathBuf {
    PathBuf::from(".claude").join("skills")
}

/// Get the skill directory path
fn get_skill_path(project_local: bool) -> Result<PathBuf> {
    let base = if project_local {
        get_project_skills_dir()
    } else {
        get_global_skills_dir()?
    };
    Ok(base.join("gdelt"))
}

/// Install the GDELT skill
pub fn install_skill(project_local: bool, force: bool) -> Result<SkillStatus> {
    let skill_dir = get_skill_path(project_local)?;

    // Check if already installed
    if skill_dir.exists() && !force {
        return Err(GdeltError::Config(format!(
            "Skill already installed at {}. Use --force to overwrite.",
            skill_dir.display()
        )));
    }

    // Create directory
    std::fs::create_dir_all(&skill_dir)?;

    // Generate and write CLAUDE.md
    let manifest = SkillManifest::default();
    let claude_md = manifest.generate_claude_md();
    let claude_md_path = skill_dir.join("CLAUDE.md");
    std::fs::write(&claude_md_path, claude_md)?;

    // Write manifest for tracking
    let manifest_path = skill_dir.join("manifest.json");
    let manifest_json = serde_json::to_string_pretty(&manifest)?;
    std::fs::write(&manifest_path, manifest_json)?;

    Ok(SkillStatus {
        installed: true,
        location: Some(skill_dir.to_string_lossy().to_string()),
        version: Some(manifest.version),
        is_project_local: project_local,
    })
}

/// Uninstall the GDELT skill
pub fn uninstall_skill() -> Result<SkillStatus> {
    // Check project-local first
    let project_path = get_skill_path(true)?;
    if project_path.exists() {
        std::fs::remove_dir_all(&project_path)?;
        return Ok(SkillStatus {
            installed: false,
            location: Some(project_path.to_string_lossy().to_string()),
            version: None,
            is_project_local: true,
        });
    }

    // Check global
    let global_path = get_skill_path(false)?;
    if global_path.exists() {
        std::fs::remove_dir_all(&global_path)?;
        return Ok(SkillStatus {
            installed: false,
            location: Some(global_path.to_string_lossy().to_string()),
            version: None,
            is_project_local: false,
        });
    }

    Err(GdeltError::NotFound("GDELT skill is not installed".into()))
}

/// Update the skill to the current version
pub fn update_skill() -> Result<SkillStatus> {
    // Find existing installation
    let (skill_dir, is_project_local) = {
        let project_path = get_skill_path(true)?;
        if project_path.exists() {
            (project_path, true)
        } else {
            let global_path = get_skill_path(false)?;
            if global_path.exists() {
                (global_path, false)
            } else {
                return Err(GdeltError::NotFound(
                    "GDELT skill is not installed. Run 'gdelt skill install' first.".into(),
                ));
            }
        }
    };

    // Regenerate CLAUDE.md with current version
    let manifest = SkillManifest::default();
    let claude_md = manifest.generate_claude_md();
    let claude_md_path = skill_dir.join("CLAUDE.md");
    std::fs::write(&claude_md_path, claude_md)?;

    // Update manifest
    let manifest_path = skill_dir.join("manifest.json");
    let manifest_json = serde_json::to_string_pretty(&manifest)?;
    std::fs::write(&manifest_path, manifest_json)?;

    Ok(SkillStatus {
        installed: true,
        location: Some(skill_dir.to_string_lossy().to_string()),
        version: Some(manifest.version),
        is_project_local,
    })
}

/// Get the current skill status
pub fn get_skill_status() -> Result<SkillStatus> {
    // Check project-local first
    let project_path = get_skill_path(true)?;
    if project_path.exists() {
        let version = read_installed_version(&project_path);
        return Ok(SkillStatus {
            installed: true,
            location: Some(project_path.to_string_lossy().to_string()),
            version,
            is_project_local: true,
        });
    }

    // Check global
    let global_path = get_skill_path(false)?;
    if global_path.exists() {
        let version = read_installed_version(&global_path);
        return Ok(SkillStatus {
            installed: true,
            location: Some(global_path.to_string_lossy().to_string()),
            version,
            is_project_local: false,
        });
    }

    Ok(SkillStatus {
        installed: false,
        location: None,
        version: None,
        is_project_local: false,
    })
}

fn read_installed_version(skill_dir: &PathBuf) -> Option<String> {
    let manifest_path = skill_dir.join("manifest.json");
    if let Ok(content) = std::fs::read_to_string(manifest_path) {
        if let Ok(manifest) = serde_json::from_str::<SkillManifest>(&content) {
            return Some(manifest.version);
        }
    }
    None
}