//! Command-line argument definitions using clap derive.

use clap::Parser;

/// Semantic code search -- like ripgrep but for meaning.
#[derive(Parser, Clone, Debug)]
#[command(name = "ripvec", version, about)]
pub struct Args {
    /// Natural language query to search for.
    ///
    /// Required for one-shot mode.
    #[arg(default_value = "")]
    pub query: String,

    /// Root directory to search (defaults to current directory).
    #[arg(default_value = ".")]
    pub path: String,

    /// Number of results to show (0 = all matches).
    #[arg(short = 'n', long, default_value_t = 0)]
    pub top_k: usize,

    /// `HuggingFace` repo for the cross-encoder rerank model.
    /// Defaults to `cross-encoder/ms-marco-TinyBERT-L-2-v2` (~22 MB).
    #[arg(long)]
    pub rerank_model: Option<String>,

    /// Cap on candidates the reranker sees. Larger = better quality,
    /// linearly more latency.
    #[arg(long, default_value_t = 100)]
    pub rerank_candidates: usize,

    /// Output format.
    #[arg(short, long, default_value = "color")]
    pub format: OutputFormat,

    /// Filter files by type (e.g., rust, python, js). Uses ripgrep's type definitions.
    #[arg(short = 't', long = "type")]
    pub file_type: Option<String>,

    /// Exclude files by extension during indexing/search walks (comma-separated, e.g. jsonl,md).
    #[arg(long, value_delimiter = ',', value_name = "EXTS")]
    pub exclude_extensions: Vec<String>,

    /// Include only files with these extensions (comma-separated, e.g. py,rs,md).
    /// Empty (default) means "no whitelist" -- other filters still apply.
    /// When set, overrides the implicit allow-list from `--scope`.
    #[arg(long, value_delimiter = ',', value_name = "EXTS")]
    pub include_extensions: Vec<String>,

    /// Intent-shaped scope for the search corpus.
    ///
    /// - `code` (no rerank): only code files; skips markdown/rst/txt.
    /// - `docs` (rerank on): only prose; cross-encoder fires on NL queries.
    /// - `all` (default): everything; rerank fires when the indexed
    ///   corpus is at least 30% prose chunks.
    #[arg(long, default_value = "all")]
    pub scope: ScopeArg,

    /// Number of threads for parallel processing (0 = cores).
    #[arg(short = 'j', long, default_value_t = 0)]
    pub threads: usize,

    /// Maximum chunk size in bytes before splitting into windows.
    #[arg(long, default_value_t = 4096)]
    pub max_chunk_bytes: usize,

    /// Sliding-window size in bytes for the fallback chunker.
    #[arg(long, default_value_t = 2048)]
    pub window_size: usize,

    /// Overlap between adjacent sliding windows in bytes.
    #[arg(long, default_value_t = 512)]
    pub window_overlap: usize,

    /// Treat all files as plain text (sliding-window chunking only).
    /// By default, recognized source files use tree-sitter semantic chunking
    /// and unrecognized files fall back to plain-text windows.
    #[arg(long)]
    pub text_mode: bool,

    /// Search mode: hybrid (semantic + BM25), semantic-only, or keyword-only.
    ///
    /// Hybrid (default) fuses results via Reciprocal Rank Fusion.
    #[arg(long, default_value = "hybrid")]
    pub mode: String,

    /// Enable pipeline profiling output to stderr.
    #[arg(long)]
    pub profile: bool,

    /// Profiling report interval in seconds.
    #[arg(long, default_value_t = 10.0)]
    pub profile_interval: f64,

    /// Write a Chrome trace JSON file (open in `chrome://tracing` or Perfetto).
    #[arg(long, value_name = "FILE")]
    pub trace: Option<String>,

    /// Log level: error, warn, info, debug, trace. Overrides `-v`, `--debug`, and `RIPVEC_LOG`.
    ///
    /// `--debug` is a shortcut for `--log-level debug`.
    #[arg(long, value_name = "LEVEL")]
    pub log_level: Option<String>,

    /// Shortcut for `--log-level debug`. Useful for diagnosing indexing/verify hangs.
    #[arg(long)]
    pub debug: bool,

    /// Increase diagnostic verbosity: -v=info, -vv=debug, -vvv=trace.
    ///
    /// Verbose modes print line-oriented phase diagnostics instead of spinners.
    #[arg(short, long, action = clap::ArgAction::Count)]
    pub verbose: u8,

    /// Write logs to a file instead of stderr (one line per event).
    #[arg(long, value_name = "FILE")]
    pub log_file: Option<String>,
}

/// Hard-coded `HuggingFace` repo for the ripvec engine's bi-encoder.
///
/// The ripvec engine (Model2Vec static encoder) is the only supported
/// engine post-v3.0.0; the `--model` and `--model-repo` flags were
/// removed in v3.0.0 along with the doomed transformer engines.
pub const DEFAULT_MODEL_REPO: &str = "minishlab/potion-base-32M";

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn verbose_flag_counts_repeated_occurrences() {
        let args = Args::try_parse_from(["ripvec", "-vvv", "query"]).unwrap();
        assert_eq!(args.verbose, 3);
    }

    #[test]
    fn exclude_extensions_accepts_comma_delimited_values() {
        let args =
            Args::try_parse_from(["ripvec", "--exclude-extensions=jsonl,md,.ipynb", "query"])
                .unwrap();
        assert_eq!(args.exclude_extensions, ["jsonl", "md", ".ipynb"]);
    }

    #[test]
    fn cli_fast_flag_rejected() {
        let result = Args::try_parse_from(["ripvec", "--fast", "q"]);
        assert!(result.is_err(), "--fast must be rejected by clap");
    }

    #[test]
    fn cli_text_flag_rejected() {
        let result = Args::try_parse_from(["ripvec", "--text", "q"]);
        assert!(result.is_err(), "--text must be rejected by clap");
    }

    #[test]
    fn cli_modern_flag_rejected() {
        let result = Args::try_parse_from(["ripvec", "--modern", "q"]);
        assert!(result.is_err(), "--modern must be rejected by clap");
    }
}

/// Output format for search results.
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum OutputFormat {
    /// Plain text without color.
    Plain,
    /// JSON output.
    Json,
    /// Colored terminal output (default).
    Color,
}

/// Intent-shaped scope for the search corpus.
///
/// Mirrors [`ripvec_core::embed::Scope`] in the CLI surface. The
/// `--scope` flag accepts these as kebab-case keywords.
#[derive(clap::ValueEnum, Clone, Copy, Debug, Default)]
pub enum ScopeArg {
    /// Only code files. Cross-encoder rerank skipped.
    Code,
    /// Only prose / documentation files. Rerank fires on NL queries.
    Docs,
    /// No extension whitelist. Rerank decided by corpus mix at index
    /// time (>=30% prose -> rerank).
    #[default]
    All,
}

impl From<ScopeArg> for ripvec_core::embed::Scope {
    fn from(value: ScopeArg) -> Self {
        match value {
            ScopeArg::Code => Self::Code,
            ScopeArg::Docs => Self::Docs,
            ScopeArg::All => Self::All,
        }
    }
}
