//! KOGRAL CLI
//!
//! Command-line interface for managing knowledge graphs with Logseq
//! compatibility.

#![forbid(unsafe_code)]

use std::path::{Path, PathBuf};

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use colored::Colorize;
use kogral_core::{
    config::KbConfig,
    models::{Edge, EdgeType, Graph, Node, NodeStatus, NodeType},
    storage::{filesystem::FilesystemStorage, Storage},
};
use tracing::{error, info};

/// KOGRAL CLI - Manage knowledge graphs with Logseq compatibility
#[derive(Parser, Debug)]
#[command(name = "kogral")]
#[command(version, about, long_about = None)]
struct Cli {
    /// Config file path (.ncl, .toml, or .json)
    #[arg(short, long, global = true)]
    config: Option<PathBuf>,

    /// Project directory (defaults to current directory)
    #[arg(short, long, global = true)]
    project: Option<PathBuf>,

    /// Enable verbose logging
    #[arg(short, long, global = true)]
    verbose: bool,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand, Debug)]
enum Commands {
    /// Initialize .kogral/ directory in current project
    Init {
        /// Project name
        #[arg(short, long)]
        name: Option<String>,

        /// Graph description
        #[arg(short, long)]
        description: Option<String>,

        /// Force initialization even if .kogral/ exists
        #[arg(short, long)]
        force: bool,
    },

    /// Add a new node to KOGRAL
    Add {
        /// Node type
        #[arg(value_enum)]
        node_type: CliNodeType,

        /// Node title
        title: String,

        /// Content (if not provided, opens editor)
        #[arg(short, long)]
        content: Option<String>,

        /// Tags (comma-separated)
        #[arg(short, long)]
        tags: Option<String>,

        /// Related node IDs (comma-separated)
        #[arg(short, long)]
        relates_to: Option<String>,
    },

    /// Search KOGRAL
    Search {
        /// Search query
        query: String,

        /// Node type filter
        #[arg(short = 't', long)]
        node_type: Option<CliNodeType>,

        /// Maximum number of results
        #[arg(short, long, default_value = "10")]
        limit: usize,

        /// Use semantic search (requires embeddings)
        #[arg(short, long)]
        semantic: bool,
    },

    /// Create a relationship between two nodes
    Link {
        /// Source node ID or title
        from: String,

        /// Target node ID or title
        to: String,

        /// Relationship type
        #[arg(value_enum)]
        relation: CliEdgeType,

        /// Relationship strength (0.0-1.0)
        #[arg(short, long, default_value = "1.0")]
        strength: f32,
    },

    /// Sync filesystem with storage backend
    Sync {
        /// Direction: to-storage, from-storage, or bidirectional
        #[arg(short, long, default_value = "bidirectional")]
        direction: String,

        /// Dry run (show what would be synced)
        #[arg(short, long)]
        dry_run: bool,
    },

    /// Start MCP server
    Serve {
        /// Transport type (stdio or sse)
        #[arg(short, long, default_value = "stdio")]
        transport: String,

        /// Port (for SSE transport)
        #[arg(short, long, default_value = "3000")]
        port: u16,
    },

    /// Show graph visualization
    Graph {
        /// Output format (ascii, json, or dot)
        #[arg(short, long, default_value = "ascii")]
        format: String,

        /// Filter by node type
        #[arg(short = 't', long)]
        node_type: Option<CliNodeType>,

        /// Show only nodes with specific tag
        #[arg(long)]
        tag: Option<String>,
    },

    /// Import from external sources
    Import {
        /// Source type
        #[arg(value_enum)]
        source: ImportSource,

        /// Source path
        path: PathBuf,

        /// Dry run (show what would be imported)
        #[arg(short, long)]
        dry_run: bool,
    },

    /// Export to external formats
    Export {
        /// Export format
        #[arg(value_enum)]
        format: ExportFormat,

        /// Output path
        output: PathBuf,

        /// Filter by node type
        #[arg(short = 't', long)]
        node_type: Option<CliNodeType>,
    },

    /// List all nodes
    List {
        /// Node type filter
        #[arg(short = 't', long)]
        node_type: Option<CliNodeType>,

        /// Status filter
        #[arg(short, long)]
        status: Option<CliNodeStatus>,

        /// Tag filter
        #[arg(long)]
        tag: Option<String>,
    },

    /// Show node details
    Show {
        /// Node ID or title
        node: String,

        /// Show related nodes
        #[arg(short, long)]
        relations: bool,
    },

    /// Delete a node
    Delete {
        /// Node ID or title
        node: String,

        /// Also delete relationships
        #[arg(short, long)]
        cascade: bool,

        /// Skip confirmation
        #[arg(short, long)]
        force: bool,
    },

    /// Manage configuration
    Config {
        /// Show current configuration
        #[arg(short, long)]
        show: bool,

        /// Get a specific config value
        #[arg(short, long)]
        get: Option<String>,

        /// Set a config value
        #[arg(short, long)]
        set: Option<String>,

        /// Config value (for --set)
        value: Option<String>,
    },
}

#[derive(Debug, Clone, clap::ValueEnum)]
enum CliNodeType {
    Note,
    Decision,
    Guideline,
    Pattern,
    Journal,
    Execution,
}

impl From<CliNodeType> for NodeType {
    fn from(cli_type: CliNodeType) -> Self {
        match cli_type {
            CliNodeType::Note => NodeType::Note,
            CliNodeType::Decision => NodeType::Decision,
            CliNodeType::Guideline => NodeType::Guideline,
            CliNodeType::Pattern => NodeType::Pattern,
            CliNodeType::Journal => NodeType::Journal,
            CliNodeType::Execution => NodeType::Execution,
        }
    }
}

#[derive(Debug, Clone, clap::ValueEnum)]
enum CliEdgeType {
    RelatesTo,
    DependsOn,
    Implements,
    Extends,
    Supersedes,
    Explains,
}

impl From<CliEdgeType> for EdgeType {
    fn from(cli_type: CliEdgeType) -> Self {
        match cli_type {
            CliEdgeType::RelatesTo => EdgeType::RelatesTo,
            CliEdgeType::DependsOn => EdgeType::DependsOn,
            CliEdgeType::Implements => EdgeType::Implements,
            CliEdgeType::Extends => EdgeType::Extends,
            CliEdgeType::Supersedes => EdgeType::Supersedes,
            CliEdgeType::Explains => EdgeType::Explains,
        }
    }
}

#[derive(Debug, Clone, clap::ValueEnum)]
enum CliNodeStatus {
    Draft,
    Active,
    Superseded,
    Archived,
}

impl From<CliNodeStatus> for NodeStatus {
    fn from(cli_status: CliNodeStatus) -> Self {
        match cli_status {
            CliNodeStatus::Draft => NodeStatus::Draft,
            CliNodeStatus::Active => NodeStatus::Active,
            CliNodeStatus::Superseded => NodeStatus::Superseded,
            CliNodeStatus::Archived => NodeStatus::Archived,
        }
    }
}

#[derive(Debug, Clone, clap::ValueEnum)]
enum ImportSource {
    Logseq,
    Markdown,
    Json,
}

#[derive(Debug, Clone, clap::ValueEnum)]
enum ExportFormat {
    Logseq,
    Markdown,
    Json,
    Summary,
}

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();

    // Initialize tracing
    let log_level = if cli.verbose { "debug" } else { "info" };
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(log_level)),
        )
        .init();

    // Determine project directory
    let project_dir = cli
        .project
        .clone()
        .unwrap_or_else(|| std::env::current_dir().expect("Failed to get current directory"));

    info!("Project directory: {}", project_dir.display());

    // Load configuration (if exists)
    let config = load_config(&project_dir, cli.config.as_deref()).ok();

    // Execute command
    match cli.command {
        Commands::Init {
            name,
            description,
            force,
        } => cmd_init(&project_dir, name, description, force).await,
        Commands::Add {
            node_type,
            title,
            content,
            tags,
            relates_to,
        } => {
            cmd_add(
                &project_dir,
                config,
                node_type,
                title,
                content,
                tags,
                relates_to,
            )
            .await
        }
        Commands::Search {
            query,
            node_type,
            limit,
            semantic,
        } => cmd_search(&project_dir, config, query, node_type, limit, semantic).await,
        Commands::Link {
            from,
            to,
            relation,
            strength,
        } => cmd_link(&project_dir, config, from, to, relation, strength).await,
        Commands::Sync { direction, dry_run } => {
            cmd_sync(&project_dir, config, direction, dry_run).await
        }
        Commands::Serve { transport, port } => {
            cmd_serve(&project_dir, config, transport, port).await
        }
        Commands::Graph {
            format,
            node_type,
            tag,
        } => cmd_graph(&project_dir, config, format, node_type, tag).await,
        Commands::Import {
            source,
            path,
            dry_run,
        } => cmd_import(&project_dir, config, source, path, dry_run).await,
        Commands::Export {
            format,
            output,
            node_type,
        } => cmd_export(&project_dir, config, format, output, node_type).await,
        Commands::List {
            node_type,
            status,
            tag,
        } => cmd_list(&project_dir, config, node_type, status, tag).await,
        Commands::Show { node, relations } => cmd_show(&project_dir, config, node, relations).await,
        Commands::Delete {
            node,
            cascade,
            force,
        } => cmd_delete(&project_dir, config, node, cascade, force).await,
        Commands::Config {
            show,
            get,
            set,
            value,
        } => cmd_config(&project_dir, config, show, get, set, value).await,
    }
}

/// Load configuration from project directory
fn load_config(project_dir: &Path, config_path: Option<&Path>) -> Result<KbConfig> {
    kogral_core::config::load_config(Some(project_dir), config_path)
        .context("Failed to load configuration")
}

/// Initialize .kogral/ directory
async fn cmd_init(
    project_dir: &Path,
    name: Option<String>,
    description: Option<String>,
    force: bool,
) -> Result<()> {
    let kogral_dir = project_dir.join(".kogral");

    if kogral_dir.exists() && !force {
        error!("Directory .kogral/ already exists. Use --force to reinitialize.");
        return Err(anyhow::anyhow!("KOGRAL directory already exists"));
    }

    info!("Initializing KOGRAL in {}", project_dir.display());

    // Create directory structure
    std::fs::create_dir_all(&kogral_dir)?;
    std::fs::create_dir_all(kogral_dir.join("notes"))?;
    std::fs::create_dir_all(kogral_dir.join("decisions"))?;
    std::fs::create_dir_all(kogral_dir.join("guidelines"))?;
    std::fs::create_dir_all(kogral_dir.join("patterns"))?;
    std::fs::create_dir_all(kogral_dir.join("journal"))?;

    // Create default config.toml
    let project_name = name.unwrap_or_else(|| {
        project_dir
            .file_name()
            .and_then(|s| s.to_str())
            .unwrap_or("my-kb")
            .to_string()
    });

    let config_content = format!(
        r#"[graph]
name = "{}"
version = "1.0.0"
description = "{}"

[storage]
primary = "filesystem"

[embeddings]
enabled = false

[templates]
templates_dir = "templates"

[query]
similarity_threshold = 0.4
max_results = 10

[mcp.server]
name = "kogral-mcp"
transport = "stdio"

[sync]
auto_index = false
watch_paths = ["notes", "decisions", "guidelines", "patterns", "journal"]
"#,
        project_name,
        description.unwrap_or_else(|| format!("{} KOGRAL", project_name))
    );

    std::fs::write(kogral_dir.join("config.toml"), config_content)?;

    println!("{}", "✓ Knowledge base initialized successfully".green());
    println!("  Project: {}", project_name.bright_blue());
    println!(
        "  Location: {}",
        kogral_dir.display().to_string().bright_black()
    );
    println!("\nNext steps:");
    println!(
        "  {} Add your first note",
        "kb add note \"My First Note\"".bright_yellow()
    );
    println!("  {} Search KOGRAL", "kb search \"topic\"".bright_yellow());
    println!("  {} List all nodes", "kb list".bright_yellow());

    Ok(())
}

/// Add a new node
async fn cmd_add(
    project_dir: &Path,
    config: Option<KbConfig>,
    node_type: CliNodeType,
    title: String,
    content: Option<String>,
    tags: Option<String>,
    relates_to: Option<String>,
) -> Result<()> {
    info!("Adding new {:?} node: {}", node_type, title);

    let mut node = Node::new(node_type.into(), title.clone());

    // Set content
    if let Some(content_text) = content {
        node.content = content_text;
    }

    // Parse tags
    if let Some(tags_str) = tags {
        node.tags = tags_str.split(',').map(|s| s.trim().to_string()).collect();
    }

    // Parse relationships
    if let Some(relates_str) = relates_to {
        node.relates_to = relates_str
            .split(',')
            .map(|s| s.trim().to_string())
            .collect();
    }

    // Create storage and save node
    let kogral_dir = project_dir.join(".kogral");
    let mut storage = FilesystemStorage::new(kogral_dir);
    let graph_name = config
        .as_ref()
        .map(|c| c.graph.name.clone())
        .unwrap_or_else(|| "default".to_string());

    let mut graph = storage
        .load_graph(&graph_name)
        .await
        .unwrap_or_else(|_| Graph::new(graph_name.clone()));

    graph
        .add_node(node.clone())
        .map_err(|e| anyhow::anyhow!(e))?;
    storage.save_graph(&graph).await?;

    println!("{}", "✓ Node added successfully".green());
    println!("  ID: {}", node.id.bright_blue());
    println!("  Type: {:?}", node.node_type);
    println!("  Title: {}", node.title);

    Ok(())
}

/// Search KOGRAL
async fn cmd_search(
    project_dir: &Path,
    config: Option<KbConfig>,
    query: String,
    node_type: Option<CliNodeType>,
    limit: usize,
    semantic: bool,
) -> Result<()> {
    info!("Searching for: {}", query);

    if semantic {
        println!(
            "{}",
            "⚠ Semantic search requires embeddings configuration".yellow()
        );
    }

    // Create storage and load graph
    let kogral_dir = project_dir.join(".kogral");
    let storage = FilesystemStorage::new(kogral_dir);
    let graph_name = config
        .as_ref()
        .map(|c| c.graph.name.clone())
        .unwrap_or_else(|| "default".to_string());

    let graph = storage.load_graph(&graph_name).await?;

    // Simple text search
    let results: Vec<&Node> = graph
        .nodes
        .values()
        .filter(|node| {
            let matches_query = node.title.to_lowercase().contains(&query.to_lowercase())
                || node.content.to_lowercase().contains(&query.to_lowercase())
                || node
                    .tags
                    .iter()
                    .any(|t| t.to_lowercase().contains(&query.to_lowercase()));

            let matches_type = node_type
                .as_ref()
                .map(|t| {
                    let node_type: NodeType = t.clone().into();
                    node.node_type == node_type
                })
                .unwrap_or(true);

            matches_query && matches_type
        })
        .take(limit)
        .collect();

    if results.is_empty() {
        println!("{}", "No results found".yellow());
        return Ok(());
    }

    println!("\n{} {} result(s):", "Found".green(), results.len());
    for (i, node) in results.iter().enumerate() {
        println!(
            "\n{}. {} ({})",
            i + 1,
            node.title.bright_blue(),
            node.id.bright_black()
        );
        println!("   Type: {:?} | Status: {:?}", node.node_type, node.status);
        if !node.tags.is_empty() {
            println!("   Tags: {}", node.tags.join(", ").bright_yellow());
        }
        if !node.content.is_empty() {
            let preview = node
                .content
                .lines()
                .next()
                .unwrap_or("")
                .chars()
                .take(80)
                .collect::<String>();
            println!("   {}", preview.bright_black());
        }
    }

    Ok(())
}

/// Create a relationship between nodes
async fn cmd_link(
    project_dir: &Path,
    config: Option<KbConfig>,
    from: String,
    to: String,
    relation: CliEdgeType,
    strength: f32,
) -> Result<()> {
    info!(
        "Creating link: {} -> {} (relation: {:?})",
        from, to, relation
    );

    let kogral_dir = project_dir.join(".kogral");
    let mut storage = FilesystemStorage::new(kogral_dir);
    let graph_name = config
        .as_ref()
        .map(|c| c.graph.name.clone())
        .unwrap_or_else(|| "default".to_string());

    let mut graph = storage.load_graph(&graph_name).await?;

    // Find nodes by ID or title and clone data we need
    let from_node_data = graph
        .nodes
        .values()
        .find(|n| n.id == from || n.title == from)
        .map(|n| (n.id.clone(), n.title.clone()))
        .ok_or_else(|| anyhow::anyhow!("Source node not found: {}", from))?;

    let to_node_data = graph
        .nodes
        .values()
        .find(|n| n.id == to || n.title == to)
        .map(|n| (n.id.clone(), n.title.clone()))
        .ok_or_else(|| anyhow::anyhow!("Target node not found: {}", to))?;

    let mut edge = Edge::new(
        from_node_data.0.clone(),
        to_node_data.0.clone(),
        relation.into(),
    );
    edge.strength = strength;

    graph.add_edge(edge).map_err(|e| anyhow::anyhow!(e))?;
    storage.save_graph(&graph).await?;

    println!("{}", "✓ Relationship created successfully".green());
    println!(
        "  {} -> {}",
        from_node_data.1.bright_blue(),
        to_node_data.1.bright_blue()
    );

    Ok(())
}

/// Sync filesystem with storage
async fn cmd_sync(
    _project_dir: &Path,
    config: Option<KbConfig>,
    direction: String,
    dry_run: bool,
) -> Result<()> {
    info!("Sync direction: {}, dry_run: {}", direction, dry_run);

    if dry_run {
        println!("{}", "Dry run mode - no changes will be made".yellow());
    }

    // For now, sync is not fully functional without both filesystem and remote
    // storage configured. This is a stub that shows the intended workflow.
    println!(
        "{}",
        "⚠ Sync requires SurrealDB backend configuration".yellow()
    );
    println!("  Current direction: {}", direction);

    if let Some(cfg) = config {
        println!("  Graph: {}", cfg.graph.name);
        println!("  Storage: {:?}", cfg.storage.primary);
    }

    println!(
        "\n{}",
        "To enable sync, configure both filesystem and SurrealDB backends".italic()
    );

    Ok(())
}

/// Start MCP server
async fn cmd_serve(
    _project_dir: &Path,
    _config: Option<KbConfig>,
    transport: String,
    port: u16,
) -> Result<()> {
    info!(
        "Starting MCP server: transport={}, port={}",
        transport, port
    );

    println!("{}", "Starting MCP server...".green());
    println!("  Transport: {}", transport);
    if transport == "sse" {
        println!("  Port: {}", port);
    }
    println!("  Protocol: JSON-RPC 2.0");

    println!(
        "\n{}",
        "Configure in ~/.config/claude/config.json:".bright_black()
    );
    println!(
        "  \"mcpServers\": {{\n    \"kogral\": {{\n      \"command\": \"kogral serve --transport \
         {}\"\n    }}\n  }}",
        transport
    );

    // In a real implementation, this would start the MCP server
    // For now, just show the configuration
    println!(
        "{}",
        "\n⚠ MCP server implementation in kogral-mcp crate".yellow()
    );

    Ok(())
}

/// Show graph visualization
#[allow(clippy::excessive_nesting)]
async fn cmd_graph(
    project_dir: &Path,
    config: Option<KbConfig>,
    format: String,
    node_type: Option<CliNodeType>,
    tag: Option<String>,
) -> Result<()> {
    info!("Graph format: {}", format);

    let kogral_dir = project_dir.join(".kogral");
    let storage = FilesystemStorage::new(kogral_dir);
    let graph_name = config
        .as_ref()
        .map(|c| c.graph.name.clone())
        .unwrap_or_else(|| "default".to_string());

    let graph = storage.load_graph(&graph_name).await?;

    // Filter nodes
    let nodes: Vec<&Node> = graph
        .nodes
        .values()
        .filter(|node| {
            let type_match = node_type
                .as_ref()
                .map(|t| {
                    let nt: NodeType = t.clone().into();
                    node.node_type == nt
                })
                .unwrap_or(true);

            let tag_match = tag.as_ref().map(|t| node.tags.contains(t)).unwrap_or(true);

            type_match && tag_match
        })
        .collect();

    match format.as_str() {
        "json" => {
            let json = serde_json::to_string_pretty(&graph)?;
            println!("{}", json);
        }
        "dot" => {
            println!("digraph KB {{");
            for node in &nodes {
                println!("  \"{}\" [label=\"{}\"];", node.id, node.title);
            }
            for edge in &graph.edges {
                println!(
                    "  \"{}\" -> \"{}\" [label=\"{:?}\"];",
                    edge.from, edge.to, edge.edge_type
                );
            }
            println!("}}");
        }
        _ => {
            println!("\n{} Knowledge Base Graph", "═".repeat(50));
            println!("Graph: {}", graph.name.bright_blue());
            println!("Nodes: {} | Edges: {}", nodes.len(), graph.edges.len());
            println!("{}\n", "═".repeat(50));

            for node in &nodes {
                println!(
                    "● {} ({})",
                    node.title.bright_blue(),
                    format!("{:?}", node.node_type).bright_black()
                );
                println!("  ID: {}", node.id.bright_black());
                if !node.tags.is_empty() {
                    println!("  Tags: {}", node.tags.join(", ").bright_yellow());
                }
                // Show outgoing relationships
                let out_edges: Vec<_> = graph.edges.iter().filter(|e| e.from == node.id).collect();
                if !out_edges.is_empty() {
                    for edge in out_edges {
                        if let Some(target) = graph.get_node(&edge.to) {
                            println!("  └─ {:?} → {}", edge.edge_type, target.title.bright_cyan());
                        }
                    }
                }
                println!();
            }
        }
    }

    Ok(())
}

/// Import from external sources
async fn cmd_import(
    _project_dir: &Path,
    config: Option<KbConfig>,
    source: ImportSource,
    path: PathBuf,
    dry_run: bool,
) -> Result<()> {
    info!("Importing from {:?}: {}", source, path.display());

    if dry_run {
        println!("{}", "Dry run mode - no changes will be made".yellow());
    }

    if !path.exists() {
        return Err(anyhow::anyhow!("Path does not exist: {}", path.display()));
    }

    match source {
        ImportSource::Logseq => {
            println!("{}", "Importing from Logseq...".bright_cyan());
            println!("  Source: {}", path.display());

            // Use logseq importer from core
            let _content = std::fs::read_to_string(&path).context("Failed to read Logseq file")?;

            // Parse the Logseq markdown and import nodes
            // This would use kogral_core::import::logseq::*
            println!("  Status: Parsing Logseq format...");
            println!("{}", "✓ Import prepared (dry run)".green());

            if !dry_run {
                println!("  Would import nodes to graph...");
            }
        }
        ImportSource::Markdown => {
            println!("{}", "Importing from Markdown...".bright_cyan());
            println!("  Source: {}", path.display());

            let content = std::fs::read_to_string(&path).context("Failed to read Markdown file")?;

            println!("  Status: Parsing Markdown format...");
            println!("  Metadata: {} bytes", content.len());
            println!("{}", "✓ Import prepared (dry run)".green());

            if !dry_run {
                println!("  Would import content as single node...");
            }
        }
        ImportSource::Json => {
            println!("{}", "Importing from JSON...".bright_cyan());
            println!("  Source: {}", path.display());

            let content = std::fs::read_to_string(&path).context("Failed to read JSON file")?;

            let json: serde_json::Value =
                serde_json::from_str(&content).context("Invalid JSON format")?;

            println!("  Status: Validating JSON structure...");
            println!("  Objects: {}", count_json_objects(&json));
            println!("{}", "✓ Import prepared (dry run)".green());

            if !dry_run {
                println!("  Would import JSON as graph...");
            }
        }
    }

    if let Some(cfg) = config {
        println!("  Target graph: {}", cfg.graph.name);
    }

    Ok(())
}

/// Export to external formats
async fn cmd_export(
    project_dir: &Path,
    config: Option<KbConfig>,
    format: ExportFormat,
    output: PathBuf,
    node_type: Option<CliNodeType>,
) -> Result<()> {
    info!("Exporting to {:?}: {}", format, output.display());

    // Create storage and load graph
    let kogral_dir = project_dir.join(".kogral");
    let storage = FilesystemStorage::new(kogral_dir);
    let graph_name = config
        .as_ref()
        .map(|c| c.graph.name.clone())
        .unwrap_or_else(|| "default".to_string());

    let graph = storage.load_graph(&graph_name).await?;

    // Filter nodes by type if specified
    let nodes: Vec<_> = if let Some(nt) = &node_type {
        let node_type_filter: NodeType = nt.clone().into();
        graph
            .nodes
            .values()
            .filter(|n| n.node_type == node_type_filter)
            .collect()
    } else {
        graph.nodes.values().collect()
    };

    match format {
        ExportFormat::Json => {
            println!("{}", "Exporting to JSON...".bright_cyan());
            let json = serde_json::to_string_pretty(&graph)?;
            std::fs::write(&output, json).context("Failed to write JSON file")?;
            println!("  Format: JSON");
            println!("  Nodes: {}", nodes.len());
            println!("  Output: {}", output.display());
            println!("{}", "✓ Export completed".green());
        }
        ExportFormat::Markdown => {
            println!("{}", "Exporting to Markdown...".bright_cyan());
            let mut content = String::new();
            content.push_str(&format!("# {}\n\n", graph.name));
            for node in &nodes {
                content.push_str(&format!("## {}\n\n", node.title));
                content.push_str(&format!("**Type:** {:?}\n\n", node.node_type));
                if !node.tags.is_empty() {
                    content.push_str(&format!("**Tags:** {}\n\n", node.tags.join(", ")));
                }
                content.push_str(&format!("{}\n\n", node.content));
                content.push_str("---\n\n");
            }
            std::fs::write(&output, content).context("Failed to write Markdown file")?;
            println!("  Format: Markdown");
            println!("  Nodes: {}", nodes.len());
            println!("  Output: {}", output.display());
            println!("{}", "✓ Export completed".green());
        }
        ExportFormat::Logseq => {
            println!("{}", "Exporting to Logseq...".bright_cyan());
            // Convert to Logseq format
            let mut content = String::new();
            for node in &nodes {
                content.push_str(&format!("- {}\n", node.title));
                if !node.tags.is_empty() {
                    content.push_str(&format!("  tags:: {}\n", node.tags.join(", ")));
                }
                for line in node.content.lines() {
                    content.push_str(&format!("  {}\n", line));
                }
                content.push('\n');
            }
            std::fs::write(&output, content).context("Failed to write Logseq file")?;
            println!("  Format: Logseq");
            println!("  Nodes: {}", nodes.len());
            println!("  Output: {}", output.display());
            println!("{}", "✓ Export completed".green());
        }
        ExportFormat::Summary => {
            println!("{}", "Generating summary...".bright_cyan());
            let mut content = String::new();
            content.push_str("# Knowledge Base Summary\n\n");
            content.push_str(&format!("**Name:** {}\n", graph.name));
            content.push_str(&format!("**Total Nodes:** {}\n", graph.nodes.len()));
            content.push_str(&format!("**Total Edges:** {}\n\n", graph.edges.len()));

            content.push_str("## Nodes by Type\n\n");
            for node_type in &[
                NodeType::Note,
                NodeType::Decision,
                NodeType::Guideline,
                NodeType::Pattern,
                NodeType::Journal,
                NodeType::Execution,
            ] {
                let count = graph
                    .nodes
                    .values()
                    .filter(|n| n.node_type == *node_type)
                    .count();
                if count > 0 {
                    content.push_str(&format!("- {:?}: {}\n", node_type, count));
                }
            }

            std::fs::write(&output, content).context("Failed to write summary file")?;
            println!("  Format: Summary");
            println!("  Output: {}", output.display());
            println!("{}", "✓ Summary generated".green());
        }
    }

    Ok(())
}

/// List all nodes
async fn cmd_list(
    project_dir: &Path,
    config: Option<KbConfig>,
    node_type: Option<CliNodeType>,
    status: Option<CliNodeStatus>,
    tag: Option<String>,
) -> Result<()> {
    let kogral_dir = project_dir.join(".kogral");
    let storage = FilesystemStorage::new(kogral_dir);
    let graph_name = config
        .as_ref()
        .map(|c| c.graph.name.clone())
        .unwrap_or_else(|| "default".to_string());

    let graph = storage.load_graph(&graph_name).await?;

    let nodes: Vec<&Node> = graph
        .nodes
        .values()
        .filter(|node| {
            let type_match = node_type
                .as_ref()
                .map(|t| {
                    let nt: NodeType = t.clone().into();
                    node.node_type == nt
                })
                .unwrap_or(true);

            let status_match = status
                .as_ref()
                .map(|s| {
                    let ns: NodeStatus = s.clone().into();
                    node.status == ns
                })
                .unwrap_or(true);

            let tag_match = tag.as_ref().map(|t| node.tags.contains(t)).unwrap_or(true);

            type_match && status_match && tag_match
        })
        .collect();

    println!("\n{} {} node(s):", "Found".green(), nodes.len());
    for node in nodes {
        println!(
            "  {} {} ({})",
            match node.node_type {
                NodeType::Note => "📝",
                NodeType::Decision => "🔀",
                NodeType::Guideline => "📋",
                NodeType::Pattern => "🔧",
                NodeType::Journal => "📅",
                NodeType::Execution => "⚡",
            },
            node.title.bright_blue(),
            node.id.bright_black()
        );
        println!(
            "     Type: {:?} | Status: {:?}",
            node.node_type, node.status
        );
    }

    Ok(())
}

/// Show node details
#[allow(clippy::excessive_nesting)]
async fn cmd_show(
    project_dir: &Path,
    config: Option<KbConfig>,
    node: String,
    relations: bool,
) -> Result<()> {
    let kogral_dir = project_dir.join(".kogral");
    let storage = FilesystemStorage::new(kogral_dir);
    let graph_name = config
        .as_ref()
        .map(|c| c.graph.name.clone())
        .unwrap_or_else(|| "default".to_string());

    let graph = storage.load_graph(&graph_name).await?;

    let found_node = graph
        .nodes
        .values()
        .find(|n| n.id == node || n.title == node)
        .ok_or_else(|| anyhow::anyhow!("Node not found: {}", node))?;

    println!(
        "\n{} {}",
        "Node:".bright_green(),
        found_node.title.bright_blue()
    );
    println!("{}", "═".repeat(60));
    println!("ID:       {}", found_node.id.bright_black());
    println!("Type:     {:?}", found_node.node_type);
    println!("Status:   {:?}", found_node.status);
    println!("Created:  {}", found_node.created);
    println!("Modified: {}", found_node.modified);
    if !found_node.tags.is_empty() {
        println!("Tags:     {}", found_node.tags.join(", ").bright_yellow());
    }
    println!("\n{}", "Content:".bright_cyan());
    println!("{}", found_node.content);

    if relations {
        println!("\n{}", "Relationships:".bright_cyan());
        let edges: Vec<_> = graph
            .edges
            .iter()
            .filter(|e| e.from == found_node.id || e.to == found_node.id)
            .collect();

        if edges.is_empty() {
            println!("  No relationships");
        } else {
            for edge in edges {
                if edge.from == found_node.id {
                    if let Some(target) = graph.get_node(&edge.to) {
                        println!("  {:?} → {}", edge.edge_type, target.title.bright_blue());
                    }
                } else if let Some(source) = graph.get_node(&edge.from) {
                    println!("  {} ← {:?}", source.title.bright_blue(), edge.edge_type);
                }
            }
        }
    }

    Ok(())
}

/// Delete a node
async fn cmd_delete(
    project_dir: &Path,
    config: Option<KbConfig>,
    node: String,
    cascade: bool,
    force: bool,
) -> Result<()> {
    let kogral_dir = project_dir.join(".kogral");
    let mut storage = FilesystemStorage::new(kogral_dir);
    let graph_name = config
        .as_ref()
        .map(|c| c.graph.name.clone())
        .unwrap_or_else(|| "default".to_string());

    let mut graph = storage.load_graph(&graph_name).await?;

    let found_node = graph
        .nodes
        .values()
        .find(|n| n.id == node || n.title == node)
        .ok_or_else(|| anyhow::anyhow!("Node not found: {}", node))?
        .clone();

    if !force {
        println!("{}", "Are you sure you want to delete this node?".yellow());
        println!("  Title: {}", found_node.title.bright_blue());
        println!("  ID: {}", found_node.id);
        println!("\nThis action cannot be undone. Use --force to skip this confirmation.");
        return Ok(());
    }

    graph
        .remove_node(&found_node.id)
        .ok_or_else(|| anyhow::anyhow!("Failed to remove node"))?;

    if cascade {
        // Remove all edges connected to this node
        graph
            .edges
            .retain(|e| e.from != found_node.id && e.to != found_node.id);
    }

    storage.save_graph(&graph).await?;

    println!("{}", "✓ Node deleted successfully".green());

    Ok(())
}

/// Manage configuration
async fn cmd_config(
    _project_dir: &Path,
    config: Option<KbConfig>,
    show: bool,
    get: Option<String>,
    set: Option<String>,
    value: Option<String>,
) -> Result<()> {
    if show {
        // Show current configuration
        if let Some(cfg) = config {
            println!("\n{}", "Current Configuration:".bright_cyan());
            println!("{}", "═".repeat(50));
            println!("Graph:");
            println!("  name: {}", cfg.graph.name.bright_blue());
            println!("  version: {}", cfg.graph.version);
            if !cfg.graph.description.is_empty() {
                println!("  description: {}", cfg.graph.description);
            }
            println!("\nStorage:");
            println!("  primary: {:?}", cfg.storage.primary);
            println!("\nEmbeddings:");
            println!("  provider: {:?}", cfg.embeddings.provider);
            println!("  enabled: {}", cfg.embeddings.enabled);
            println!("\nQuery:");
            println!("  similarity_threshold: {}", cfg.query.similarity_threshold);
            println!("  max_results: {}", cfg.query.max_results);
            println!("\nMCP Server:");
            println!("  name: {}", cfg.mcp.server.name);
            println!("  transport: {:?}", cfg.mcp.server.transport);
            println!("{}", "═".repeat(50));
        } else {
            println!("{}", "⚠ No configuration found".yellow());
        }
    } else if let Some(key) = get {
        // Get specific config value
        if let Some(cfg) = config {
            match key.as_str() {
                "graph.name" => println!("graph.name = {}", cfg.graph.name),
                "graph.version" => println!("graph.version = {}", cfg.graph.version),
                "storage.primary" => println!("storage.primary = {:?}", cfg.storage.primary),
                "embeddings.provider" => {
                    println!("embeddings.provider = {:?}", cfg.embeddings.provider)
                }
                "query.similarity_threshold" => {
                    println!(
                        "query.similarity_threshold = {}",
                        cfg.query.similarity_threshold
                    )
                }
                _ => println!("{}", format!("Unknown config key: {}", key).yellow()),
            }
        } else {
            println!("{}", "⚠ No configuration found".yellow());
        }
    } else if set.is_some() && value.is_some() {
        println!(
            "{}",
            "⚠ Configuration modification not yet implemented".yellow()
        );
        println!("  Edit configuration files directly:");
        println!("  - .kogral/config.toml (local)");
        println!("  - .kogral/config.ncl (Nickel source)");
    } else {
        println!("Usage: kogral config [--show|--get KEY|--set KEY VALUE]");
    }

    Ok(())
}

/// Count JSON objects recursively
fn count_json_objects(value: &serde_json::Value) -> usize {
    match value {
        serde_json::Value::Object(obj) => 1 + obj.values().map(count_json_objects).sum::<usize>(),
        serde_json::Value::Array(arr) => arr.iter().map(count_json_objects).sum::<usize>(),
        _ => 0,
    }
}
