velesdb-cli 3.12.0

Interactive CLI and REPL for VelesDB with VelesQL support
//! REPL output formatting module (EPIC-061/US-004 refactoring).
//!
//! Extracted from repl.rs to reduce file size and improve modularity.

use colored::Colorize;
use comfy_table::presets::UTF8_FULL;
use comfy_table::{Cell, Color, ContentArrangement, Table};
use std::collections::HashMap;

use crate::repl::{QueryKind, QueryResult};

/// Print query results in the specified format
pub fn print_result(result: &QueryResult, format: &str) {
    if result.rows.is_empty() {
        let msg = match result.kind {
            QueryKind::Ddl => "DDL statement executed successfully.",
            QueryKind::Dml => "DML statement executed successfully.",
            QueryKind::Train => "TRAIN statement executed successfully.",
            QueryKind::Admin => "Admin statement executed successfully.",
            QueryKind::Introspection => "No collections found.",
            QueryKind::Select => "No results.",
        };
        println!("{}", msg.dimmed());
        return;
    }

    match format.to_lowercase().as_str() {
        "json" => match serde_json::to_string_pretty(&result.rows) {
            Ok(json) => println!("{json}"),
            Err(e) => eprintln!("{}", format!("Failed to serialize to JSON: {e}").red()),
        },
        _ => {
            print_table(&result.rows);
        }
    }
}

/// Print results as a formatted table
pub fn print_table(rows: &[HashMap<String, serde_json::Value>]) {
    if rows.is_empty() {
        return;
    }

    // Collect all column names
    let mut columns: Vec<String> = Vec::new();
    for row in rows {
        for key in row.keys() {
            if !columns.contains(key) {
                columns.push(key.clone());
            }
        }
    }
    columns.sort();

    // Ensure 'id' is first if present
    if let Some(pos) = columns.iter().position(|c| c == "id") {
        columns.remove(pos);
        columns.insert(0, "id".to_string());
    }

    let mut table = Table::new();
    table
        .load_preset(UTF8_FULL)
        .set_content_arrangement(ContentArrangement::Dynamic);

    // Header
    let header: Vec<Cell> = columns
        .iter()
        .map(|c| Cell::new(c).fg(Color::Cyan))
        .collect();
    table.set_header(header);

    // Rows
    for row in rows {
        let cells: Vec<Cell> = columns
            .iter()
            .map(|col| {
                let value = row.get(col).map_or("-".to_string(), |v| match v {
                    serde_json::Value::String(s) => s.clone(),
                    serde_json::Value::Null => "-".to_string(),
                    other => other.to_string(),
                });
                Cell::new(value)
            })
            .collect();
        table.add_row(cells);
    }

    println!("{table}");
}

/// Print REPL help message
pub fn print_help() {
    println!("\n{}", "VelesQL REPL Commands".bold().underline());
    println!();
    println!("  {}           Show this help", ".help".yellow());
    println!("  {}           Exit the REPL", ".quit".yellow());
    println!("  {}     List all collections", ".collections".yellow());
    println!("  {}     Show collection schema", ".schema <name>".yellow());
    println!(
        "  {}   Detailed collection stats",
        ".describe <name>".yellow()
    );
    println!(
        "  {}      Count records in collection",
        ".count <name>".yellow()
    );
    println!("  {}  Show N sample records", ".sample <name> [n]".yellow());
    println!(
        "  {} Browse with pagination",
        ".browse <name> [page]".yellow()
    );
    println!("  {} Export to JSON file", ".export <name> [file]".yellow());
    println!(
        "  {} Collection health snapshot",
        ".diagnostics <name>".yellow()
    );
    println!(
        "  {}       Toggle timing display",
        ".timing on|off".yellow()
    );
    println!(
        "  {}        Set output format",
        ".format table|json".yellow()
    );
    println!("  {}          Clear screen", ".clear".yellow());
    println!();
    println!("{}", "Data & Index Commands:".bold().underline());
    println!();
    println!(
        "  {}      Show query execution plan",
        ".explain <query>".yellow()
    );
    println!(
        "  {} Scroll with cursor pagination",
        ".scroll <name> [batch_size] [cursor]".yellow()
    );
    println!(
        "  {}    Analyze collection statistics",
        ".analyze <name>".yellow()
    );
    println!(
        "  {}    List indexes on collection",
        ".indexes <name>".yellow()
    );
    println!(
        "  {} Delete points by ID",
        ".delete <name> <id> [id2..]".yellow()
    );
    println!(
        "  {} Upsert a point (vector + payload)",
        ".upsert <col> <id> <vector> [payload]".yellow()
    );
    println!(
        "  {}      Flush collection to disk",
        ".flush <name>".yellow()
    );
    println!(
        "  {} Create index",
        ".create-index <name> <field> [--type secondary|property|range]".yellow()
    );
    println!(
        "  {}  Drop index",
        ".drop-index <name> <label> <prop>".yellow()
    );
    println!();
    println!("{}", "Advanced Search:".bold().underline());
    println!();
    println!(
        "  {} Sparse-only search",
        ".sparse-search <name> <idx> <json> [k]".yellow()
    );
    println!(
        "  {} Dense+sparse hybrid",
        ".hybrid-sparse <name> <dense> <sparse> [k] [--strategy rrf|average|max|weighted|relative_score]"
            .yellow()
    );
    println!("  {}        Show query guard-rails", ".guardrails".yellow());
    println!("  {}   Agent memory (preview)", ".agent [cmd]".yellow());
    println!();
    println!("{}", "Graph Commands:".bold().underline());
    println!();
    println!(
        "  {} Add edge",
        ".graph add-edge <col> <id> <src> <tgt> <label>".yellow()
    );
    println!(
        "  {}       List edges",
        ".graph edges <col> [--label X]".yellow()
    );
    println!(
        "  {}         Node degree",
        ".graph degree <col> <node>".yellow()
    );
    println!(
        "  {}   Traverse graph",
        ".graph traverse <col> <src> [--algo bfs|dfs]".yellow()
    );
    println!(
        "  {} Neighbors",
        ".graph neighbors <col> <node> [--direction in|out|both]".yellow()
    );
    println!(
        "  {}  Remove edge",
        ".graph remove-edge <col> <edge_id>".yellow()
    );
    println!("  {}          Graph stats", ".graph count <col>".yellow());
    println!(
        "  {} Embedding search",
        ".graph search <col> <vector> [k]".yellow()
    );
    println!(
        "  {} Store payload",
        ".graph store-payload <col> <node> <json>".yellow()
    );
    println!(
        "  {}  Get payload",
        ".graph get-payload <col> <node>".yellow()
    );
    println!(
        "  {}    List nodes",
        ".graph nodes <col> [--page N]".yellow()
    );
    println!("  {}           Full graph help", ".graph help".yellow());
    println!();
    println!("{}", "Session Commands:".bold().underline());
    println!();
    println!(
        "  {}   Set session parameter",
        "\\set <key> <value>".yellow()
    );
    println!("  {}       Show session settings", "\\show [key]".yellow());
    println!("  {}      Reset settings", "\\reset [key]".yellow());
    println!(
        "  {}     Select active collection",
        "\\use <collection>".yellow()
    );
    println!("  {}             Database information", "\\info".yellow());
    println!("  {} Quick benchmark", "\\bench <col> [n] [k]".yellow());
    println!();
    println!("{}", "Session Settings:".bold().underline());
    println!();
    println!("  {} fast, balanced, accurate, perfect", "mode".cyan());
    println!("  {} 16-4096 (or auto from mode)", "ef_search".cyan());
    println!("  {} Query timeout in ms", "timeout_ms".cyan());
    println!("  {} Enable reranking (true/false)", "rerank".cyan());
    println!("  {} Max results per query", "max_results".cyan());
    println!();
    println!("{}", "VelesQL (type any SQL directly):".bold().underline());
    println!();
    println!(
        "  {}",
        "SELECT * FROM docs WHERE category = 'tech' LIMIT 10;"
            .italic()
            .white()
    );
    println!(
        "  {}",
        "SELECT * FROM docs WHERE vector NEAR [0.1, 0.2, 0.3] LIMIT 5;"
            .italic()
            .white()
    );
    println!(
        "  {}",
        "SELECT * FROM docs WHERE similarity(vector, [0.1, 0.2]) > 0.8 LIMIT 10;"
            .italic()
            .white()
    );
    println!(
        "  {}",
        "SELECT * FROM docs WHERE content MATCH 'rust' LIMIT 10;"
            .italic()
            .white()
    );
    println!(
        "  {}",
        "CREATE COLLECTION test (dimension = 4, metric = 'cosine');"
            .italic()
            .white()
    );
    println!("  {}", "DROP COLLECTION test;".italic().white());
    println!(
        "  {}",
        "SELECT EDGES FROM kg WHERE label = 'KNOWS';"
            .italic()
            .white()
    );
    println!(
        "  {}",
        "MATCH (a)-[:KNOWS]->(b) RETURN a, b LIMIT 10;  (needs \\use <col>)"
            .italic()
            .white()
    );
    println!(
        "  {}",
        "SELECT category, COUNT(*) FROM docs GROUP BY category;"
            .italic()
            .white()
    );
    println!();
    println!(
        "  {}",
        "Tip: Use '.upsert <col> <id> <vector> [payload]' to insert vectors.".dimmed()
    );
    println!(
        "  {}",
        "Note: $parameter vectors require REST API. Use literal [0.1, ...] in REPL.".dimmed()
    );
    println!();
}