oxirs 0.2.4

Command-line interface for OxiRS - import, export, migration, and benchmarking tools
Documentation
//! Dataset initialization command

use super::stubs::Store;
use super::CommandResult;
use crate::cli::logging::CommandLogger;
use crate::cli::CliContext;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select};
use std::fs;
use std::path::{Path, PathBuf};

/// Initialize a new knowledge graph dataset
pub async fn run(name: String, format: String, location: Option<PathBuf>) -> CommandResult {
    let ctx = CliContext::new();
    let cmd_logger = CommandLogger::new("init", vec![name.clone(), format.clone()]);

    ctx.info(&format!(
        "Initializing dataset '{name}' with format '{format}'"
    ));

    // If in interactive mode, prompt for additional configuration
    let (final_name, final_format, final_location, features) = if ctx.interactive {
        interactive_init(&name, &format, location, &ctx)?
    } else {
        (
            name.clone(),
            format.clone(),
            location.unwrap_or_else(|| PathBuf::from(&name)),
            Features::default(),
        )
    };

    let dataset_path = final_location;

    // Validate format
    match format.as_str() {
        "tdb2" | "memory" => {}
        _ => {
            return Err(
                format!("Unsupported format '{format}'. Supported formats: tdb2, memory").into(),
            );
        }
    }

    // Create directory structure
    if dataset_path.exists() {
        if ctx.interactive {
            let overwrite = Confirm::with_theme(&ColorfulTheme::default())
                .with_prompt(format!(
                    "Directory '{}' already exists. Overwrite?",
                    dataset_path.display()
                ))
                .default(false)
                .interact()?;

            if !overwrite {
                ctx.info("Initialization cancelled.");
                return Ok(());
            }

            // Remove existing directory
            fs::remove_dir_all(&dataset_path)?;
        } else {
            return Err(format!(
                "Dataset directory '{}' already exists",
                dataset_path.display()
            )
            .into());
        }
    }

    fs::create_dir_all(&dataset_path)?;
    ctx.success(&format!(
        "Created dataset directory: {}",
        dataset_path.display()
    ));

    // Initialize storage backend
    let _store = match final_format.as_str() {
        "tdb2" => Store::create(&dataset_path)?,
        "memory" => Store::create(&dataset_path)?, // Use create for both
        _ => {
            // This should never happen due to validation at line 35-42,
            // but we handle it gracefully instead of using unreachable!()
            return Err(format!(
                "Internal error: unsupported format '{}' passed validation",
                final_format
            )
            .into());
        }
    };

    // Create configuration file with features
    let config_path = dataset_path.join("oxirs.toml");
    let config_content =
        create_config_with_features(&final_name, &final_format, &dataset_path, &features)?;
    fs::write(&config_path, config_content)?;
    ctx.success(&format!(
        "Created configuration file: {}",
        config_path.display()
    ));

    ctx.info("Dataset Initialized Successfully");
    ctx.info(&format!("Name: {final_name}"));
    ctx.info(&format!("Storage format: {final_format}"));
    ctx.info(&format!("Location: {}", dataset_path.display()));
    ctx.info(&format!("Configuration: {}", config_path.display()));

    if features.any_enabled() {
        ctx.info("Enabled Features");
        if features.reasoning {
            ctx.verbose("• Reasoning (RDFS/OWL-RL)");
        }
        if features.validation {
            ctx.verbose("• SHACL Validation");
        }
        if features.text_search {
            ctx.verbose("• Full-text Search");
        }
        if features.vector_search {
            ctx.verbose("• Vector Search");
        }
        if features.graphql {
            ctx.verbose("• GraphQL Endpoint");
        }
    }

    cmd_logger.success();
    Ok(())
}

/// Create default configuration file content
#[allow(dead_code)]
fn create_default_config(
    name: &str,
    format: &str,
    _path: &Path,
) -> Result<String, Box<dyn std::error::Error>> {
    let config = format!(
        r#"# OxiRS Configuration
# Generated by oxirs init

[general]
default_format = "turtle"

[server]
port = 3030
host = "localhost"
enable_cors = true
enable_graphql = false

[datasets.{}]
name = "{}"
location = "."
dataset_type = "{}"
read_only = false
enable_reasoning = false
enable_validation = false
enable_text_search = false
enable_vector_search = false
"#,
        name, name, format
    );

    Ok(config)
}

#[derive(Default)]
struct Features {
    reasoning: bool,
    validation: bool,
    text_search: bool,
    vector_search: bool,
    graphql: bool,
}

impl Features {
    fn any_enabled(&self) -> bool {
        self.reasoning || self.validation || self.text_search || self.vector_search || self.graphql
    }
}

/// Interactive initialization wizard
fn interactive_init(
    default_name: &str,
    default_format: &str,
    default_location: Option<PathBuf>,
    ctx: &CliContext,
) -> Result<(String, String, PathBuf, Features), Box<dyn std::error::Error>> {
    let theme = ColorfulTheme::default();

    ctx.info("Dataset Initialization Wizard");

    // Dataset name
    let name: String = Input::with_theme(&theme)
        .with_prompt("Dataset name")
        .default(default_name.to_string())
        .interact_text()?;

    // Storage format
    let formats = vec!["tdb2", "memory"];
    let format_index = formats
        .iter()
        .position(|&f| f == default_format)
        .unwrap_or(0);
    let format_idx = Select::with_theme(&theme)
        .with_prompt("Storage format")
        .items(&formats)
        .default(format_index)
        .interact()?;
    let format = formats[format_idx].to_string();

    // Location
    let default_loc = default_location.unwrap_or_else(|| PathBuf::from(&name));
    let location_str: String = Input::with_theme(&theme)
        .with_prompt("Dataset location")
        .default(default_loc.display().to_string())
        .interact_text()?;
    let location = PathBuf::from(location_str);

    // Features
    ctx.info("Optional Features");
    ctx.verbose("Select features to enable for this dataset:");

    let reasoning = Confirm::with_theme(&theme)
        .with_prompt("Enable RDFS/OWL reasoning?")
        .default(false)
        .interact()?;

    let validation = Confirm::with_theme(&theme)
        .with_prompt("Enable SHACL validation?")
        .default(false)
        .interact()?;

    let text_search = Confirm::with_theme(&theme)
        .with_prompt("Enable full-text search?")
        .default(false)
        .interact()?;

    let vector_search = Confirm::with_theme(&theme)
        .with_prompt("Enable vector search (semantic similarity)?")
        .default(false)
        .interact()?;

    let graphql = Confirm::with_theme(&theme)
        .with_prompt("Enable GraphQL endpoint?")
        .default(false)
        .interact()?;

    let features = Features {
        reasoning,
        validation,
        text_search,
        vector_search,
        graphql,
    };

    // Summary
    ctx.info("Configuration Summary");
    ctx.info(&format!("Name: {name}"));
    ctx.info(&format!("Format: {format}"));
    ctx.info(&format!("Location: {}", location.display()));

    if features.any_enabled() {
        ctx.verbose("Enabled features:");
        if features.reasoning {
            ctx.verbose("• Reasoning");
        }
        if features.validation {
            ctx.verbose("• Validation");
        }
        if features.text_search {
            ctx.verbose("• Text search");
        }
        if features.vector_search {
            ctx.verbose("• Vector search");
        }
        if features.graphql {
            ctx.verbose("• GraphQL");
        }
    }

    let confirm = Confirm::with_theme(&theme)
        .with_prompt("Proceed with initialization?")
        .default(true)
        .interact()?;

    if !confirm {
        return Err("Initialization cancelled by user".into());
    }

    Ok((name, format, location, features))
}

/// Create configuration file with selected features
fn create_config_with_features(
    name: &str,
    format: &str,
    _path: &Path,
    features: &Features,
) -> Result<String, Box<dyn std::error::Error>> {
    let config = format!(
        r#"# OxiRS Configuration
# Generated by oxirs init

[general]
default_format = "turtle"

[server]
port = 3030
host = "localhost"
enable_cors = true
enable_graphql = {}

[datasets.{}]
name = "{}"
location = "."
dataset_type = "{}"
read_only = false
enable_reasoning = {}
enable_validation = {}
enable_text_search = {}
enable_vector_search = {}
"#,
        features.graphql,
        name,
        name,
        format,
        features.reasoning,
        features.validation,
        features.text_search,
        features.vector_search,
    );

    Ok(config)
}