//! Shared argument structs for CLI and batch commands.
//! Eliminates duplication between Commands and BatchCmd enums.
//!
//! #947: each variant in the user-facing command surface should embed one of
//! these structs via `#[command(flatten)]`. Both the CLI path and the daemon
//! batch path read from the same arg struct, so adding a flag or changing a
//! default happens once and both paths pick it up automatically.

use clap::Args;

use super::{parse_finite_f32, parse_nonzero_usize, parse_unit_f32};
use cqs::store::DeadConfidence;

// ============ #1373 / API-V1.33: depth-flag default rule ============
//
// Five graph commands take a "depth" knob. Pre-#1373, the spread
// (gather=1, impact=1, test-map=5, trace=10, onboard=3) read like
// accidents. The named constants below document the per-command
// rationale so the spread is auditable and intentional, even though
// the values themselves stay where they were (changing them is a
// behavior change for operators who depend on the current radii).
//
// Two rule classes:
//
//   BLAST   — depth=1 by default. The chunk is a "what does *this*
//             thing reach" query. Direct callers / direct callees
//             only; deeper traversals usually swamp the answer
//             with chunks that don't materially change the
//             decision. Used by `gather`, `impact`.
//
//   WALK    — depth=3 by default. The chunk is a "trace this
//             concept across a few hops" query. Three is the
//             smallest depth where you reliably catch a chain like
//             "feature → orchestrator → primitive" without
//             expanding beyond what an agent can read in one
//             context window. Used by `onboard`. `test-map` keeps
//             depth=5 because tests are leaves on the call graph,
//             so the WALK has to descend further to reach them.
//
// `trace` keeps `--max-depth` semantics: it's a path-search (find
// route from A to B), not BFS-traversal, so its 10-step ceiling
// expresses "give up after 10 hops" rather than "expand 10 deep."
// `--depth` alias added so the spelling matches the others.

/// Default for "blast radius" depth flags (gather, impact). One step
/// of direct callers / callees.
pub const DEFAULT_DEPTH_BLAST: usize = 1;

/// Default for "walk the call graph" depth flags (onboard). Three
/// hops covers feature → orchestrator → primitive without
/// over-expanding into chunks that don't materially change the
/// agent's mental model of the area.
pub const DEFAULT_DEPTH_WALK: usize = 3;

/// `test-map` walks deeper than the standard WALK because tests are
/// leaves on the call graph — depth 3 frequently misses test files
/// that are 4+ hops from a deep production chunk. Five is the smallest
/// value that catches typical project-level tests without dramatically
/// blowing up the response.
pub const DEFAULT_DEPTH_TEST_MAP: usize = 5;

/// `trace` is path-search, not BFS-traversal. The flag is
/// `--max-depth` (with `--depth` alias as of #1373) and means "give
/// up looking for a route between source and target after N hops."
/// 10 is the historical default, large enough to find paths through
/// long indirection chains but small enough that an unreachable pair
/// surfaces in <1s.
pub const DEFAULT_DEPTH_TRACE: u16 = 10;

/// Cross-encoder / LLM reranker mode for retrieval surfaces.
///
/// Lifted out of `src/cli/commands/eval/mod.rs` (P2-14, #1372) so search and
/// eval share the same flag shape. `cqs <q> --rerank` is preserved as a
/// boolean shorthand for `--reranker onnx`; `--reranker none|onnx` is the
/// canonical form. `Llm` was previously exposed as a placeholder for #1220
/// but errored at runtime — API-V1.36-2 dropped it from the surface so
/// `--help` lists only modes the binary actually supports.
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub(crate) enum RerankerMode {
    /// No reranking — stage-1 retrieval is the final answer (default).
    None,
    /// Cross-encoder reranker via [`cqs::OnnxReranker`].
    Onnx,
}

// API-V1.36-9 (#1459): `resolve_rerank_mode` removed along with the
// legacy `--rerank` bool. Callers now use
// `self.reranker.unwrap_or(RerankerMode::None)` directly.

/// Shared `--limit / -n` argument for graph commands that previously had no
/// per-subcommand limit (callers, callees, deps, impact, test-map, trace,
/// onboard, explain). Default mirrors the top-level `Cli::limit` (= 5) so a
/// bare `cqs <query>` and `cqs callers <name> -n N` agree on the cap.
///
/// Task A3: standardises `--limit` across every graph subcommand. Previously
/// only the top-level `Cli` accepted `--limit`, so batch users had no way to
/// cap graph output (`echo 'callers Foo --limit 5' | cqs batch` errored with
/// "unexpected argument"). Embedding this struct via `#[command(flatten)]`
/// gives every subcommand its own `--limit` while keeping the default in one
/// place.
///
/// API-V1.38-10 (#1463): now also rejects `--limit 0` at parse time so
/// every flattened consumer (impact / trace / onboard / explain / test_map /
/// deps / callers + the 7 search-shaped commands once they migrate)
/// inherits the same parse-time validation that `cqs <q>`'s top-level Cli
/// got in #1544. limit=0 is meaningless everywhere.
#[derive(Args, Debug, Clone)]
pub(crate) struct LimitArg {
    /// Max results to return (per category for impact/explain)
    #[arg(short = 'n', long, default_value = "5", value_parser = parse_nonzero_usize)]
    pub limit: usize,
}

/// Arguments for semantic search: the flagship command. Shared between CLI
/// `search` (top-level + `cqs search …`) and batch `search`.
///
/// CQ-V1.25-1/4: this struct is the single source of truth for every search
/// knob. Previously `BatchCmd::Search` inline-duplicated 21 fields and
/// individual fields drifted (missing `--threshold`, missing `--pattern`,
/// etc.). If a flag is valid for search, it lives here.
#[derive(Args, Debug, Clone)]
pub(crate) struct SearchArgs {
    /// Search query (quote multi-word queries)
    pub query: String,

    /// API-V1.38-10 (#1463): shared `--limit` arg via `LimitArg` flatten.
    /// Pre-fix, every `*Args` struct inline-defined its own `pub limit: usize`
    /// — 8 copies that drifted on validation rules (only some had
    /// `parse_nonzero_usize`). Centralised here so future limit-shape
    /// changes (default, cap, parser) are one-line edits.
    #[command(flatten)]
    pub limit_arg: LimitArg,

    /// Min similarity threshold
    ///
    /// NOTE: `-t` is intentionally overloaded across subcommands.
    /// In search/similar, it means "min similarity threshold" (default 0.3).
    /// In diff/drift, it means "match threshold" for identity (default 0.95).
    #[arg(short = 't', long, default_value = "0.3", value_parser = parse_finite_f32)]
    pub threshold: f32,

    /// Weight for name matching in hybrid search (0.0-1.0)
    ///
    /// AC-V1.29-5: value_parser is `parse_unit_f32` (bounded [0.0, 1.0]) to
    /// reject out-of-range values at parse time. Previously accepted e.g.
    /// `--name-boost 1.5`, which silently subtracted > 1.0 from embedding
    /// weight and degraded search with no warning.
    #[arg(long, default_value = "0.2", value_parser = parse_unit_f32)]
    pub name_boost: f32,

    /// Filter by language
    #[arg(short = 'l', long)]
    pub lang: Option<String>,

    /// Include only these chunk types in results (e.g., function, struct, test, endpoint)
    #[arg(long, alias = "chunk-type")]
    pub include_type: Option<Vec<String>>,

    /// Exclude these chunk types from results (e.g., test, variable, configkey)
    #[arg(long)]
    pub exclude_type: Option<Vec<String>>,

    /// Filter by path pattern (glob)
    #[arg(short = 'p', long)]
    pub path: Option<String>,

    /// Filter by structural pattern (builder, error_swallow, async, mutex, unsafe, recursion)
    #[arg(long)]
    pub pattern: Option<String>,

    /// Definition search: find by name only, skip embedding (faster)
    #[arg(long)]
    pub name_only: bool,

    /// Enable RRF hybrid search (keyword + semantic fusion).
    #[arg(long)]
    pub rrf: bool,

    /// Include documentation, markdown, and config chunks in search results.
    #[arg(long)]
    pub include_docs: bool,

    /// Reranker mode: `none|onnx` (#1372).
    ///
    /// Mirrors `cqs eval --reranker`. `none` is the default; `onnx` runs the
    /// cross-encoder configured by `[reranker]` / `CQS_RERANKER_MODEL`.
    ///
    /// API-V1.36-9 (#1459): the legacy `--rerank` bool was a "two flags for
    /// one knob" shorthand for `--reranker onnx`. Per "no external users"
    /// project policy, dropped without an alias — `--reranker onnx` is the
    /// canonical form.
    #[arg(long = "reranker", value_enum)]
    pub reranker: Option<RerankerMode>,

    /// Force-enable SPLADE sparse-dense hybrid search.
    ///
    /// Default behavior already runs SPLADE with per-category routing when the
    /// classifier matches a known category. This flag forces SPLADE on even
    /// for Unknown-category queries. Combine with `--splade-alpha` to pin
    /// a specific fusion weight across all categories.
    #[arg(long)]
    pub splade: bool,

    /// SPLADE fusion weight (None = use per-category router).
    ///
    /// When set, overrides the per-category router with a constant α for all
    /// queries: 1.0 = pure cosine, 0.0 = pure sparse, 0.7 was the legacy
    /// one-size default. Leaving this unset lets `classify_query` pick per
    /// category (the production path).
    #[arg(long, value_parser = parse_finite_f32)]
    pub splade_alpha: Option<f32>,

    /// Show only file:line, no code
    #[arg(long)]
    pub no_content: bool,

    /// Show N lines of context before/after the chunk
    #[arg(short = 'C', long)]
    pub context: Option<usize>,

    /// Expand results with parent context (small-to-big retrieval)
    ///
    /// API-V1.29-9: renamed from `--expand` to `--expand-parent` so it aligns
    /// with the top-level `Cli::expand_parent` flag (`src/cli/definitions.rs`).
    /// The old `--expand` spelling is kept as a visible alias for now so batch
    /// scripts that still pass it don't break.
    #[arg(long = "expand-parent", visible_alias = "expand")]
    pub expand_parent: bool,

    /// Search only this reference index (skip project index)
    #[arg(long = "ref")]
    pub ref_name: Option<String>,

    /// Include reference indexes in search results (default: project only)
    #[arg(long)]
    pub include_refs: bool,

    /// Maximum token budget for results (packs highest-scoring into budget)
    #[arg(long, value_parser = parse_nonzero_usize)]
    pub tokens: Option<usize>,

    /// Disable staleness checks (skip per-file mtime comparison)
    #[arg(long)]
    pub no_stale_check: bool,

    /// Disable search-time demotion of test functions and underscore-prefixed names
    #[arg(long)]
    pub no_demote: bool,
}

impl SearchArgs {
    /// Effective reranker mode. Returns `RerankerMode::None` when no flag is set.
    /// API-V1.36-9 (#1459): legacy `--rerank` bool gone; `--reranker` is canonical.
    pub(crate) fn rerank_mode(&self) -> RerankerMode {
        self.reranker.unwrap_or(RerankerMode::None)
    }

    /// `true` if any reranker stage is selected (Onnx or Llm).
    pub(crate) fn rerank_active(&self) -> bool {
        !matches!(self.rerank_mode(), RerankerMode::None)
    }
}

/// Arguments shared between CLI `gather` and batch `gather`.
#[derive(Args, Debug, Clone)]
pub(crate) struct GatherArgs {
    /// Search query / question
    pub query: String,
    /// Call-graph BFS depth for gather expansion (0=seeds only, max 5).
    /// Aligned with `onboard`/`impact`/`test-map` which already use `--depth`;
    /// the legacy `--expand` form is kept as a visible alias.
    /// #1373: BLAST default — direct callers / callees only.
    /// API-V1.36-1: -d short flag for parity with impact/onboard/test-map.
    #[arg(short = 'd', long, default_value_t = DEFAULT_DEPTH_BLAST, visible_alias = "expand")]
    pub depth: usize,
    /// Expansion direction: both, callers, callees
    #[arg(long, default_value = "both")]
    pub direction: cqs::GatherDirection,
    /// API-V1.38-10 (#1463): shared `--limit` arg via `LimitArg` flatten.
    /// Default 5 across all sister args.
    #[command(flatten)]
    pub limit_arg: LimitArg,
    /// Maximum token budget (overrides --limit with token-based packing)
    #[arg(long, value_parser = parse_nonzero_usize)]
    pub tokens: Option<usize>,
    /// Cross-index gather: seed from reference, bridge into project code
    #[arg(long = "ref")]
    pub ref_name: Option<String>,
}

/// Arguments shared between CLI `impact` and batch `impact`.
#[derive(Args, Debug, Clone)]
pub(crate) struct ImpactArgs {
    /// Function name or file:function
    pub name: String,
    /// Caller depth (1=direct, 2+=transitive)
    ///
    /// API-V1.29-10: `-d` short flag added for parity with `OnboardArgs::depth`
    /// which already accepts it.
    /// #1373: BLAST default — direct callers only.
    #[arg(short = 'd', long, default_value_t = DEFAULT_DEPTH_BLAST)]
    pub depth: usize,
    /// Suggest tests for untested callers
    #[arg(long)]
    pub suggest_tests: bool,
    /// Include type-impacted functions (via shared type dependencies)
    #[arg(long)]
    pub type_impact: bool,
    /// Query callers/impact across all configured reference projects
    #[arg(long)]
    pub cross_project: bool,
    /// Task A3: per-section truncation cap (callers, transitive_callers,
    /// tests, type_impacted). Defaults to 5 to match the top-level `Cli`.
    #[command(flatten)]
    pub limit_arg: LimitArg,
}

/// Arguments shared between CLI `scout` and batch `scout`.
#[derive(Args, Debug, Clone)]
pub(crate) struct ScoutArgs {
    /// Search query to investigate
    pub query: String,
    /// API-V1.38-10 (#1463): shared `--limit` arg via `LimitArg` flatten.
    #[command(flatten)]
    pub limit_arg: LimitArg,
    /// Maximum token budget (includes chunk content within budget)
    #[arg(long, value_parser = parse_nonzero_usize)]
    pub tokens: Option<usize>,
}

/// Arguments shared between CLI `context` and batch `context`.
#[derive(Args, Debug, Clone)]
pub(crate) struct ContextArgs {
    /// File path relative to project root
    pub path: String,
    /// Return summary counts instead of full details
    #[arg(long)]
    pub summary: bool,
    /// Signatures-only TOC with caller/callee counts (no code bodies)
    #[arg(long)]
    pub compact: bool,
    /// Maximum token budget (includes chunk content within budget)
    #[arg(long, value_parser = parse_nonzero_usize)]
    pub tokens: Option<usize>,
}

/// Arguments shared between CLI `dead` and batch `dead`.
#[derive(Args, Debug, Clone)]
pub(crate) struct DeadArgs {
    /// Include public API functions in the main list
    #[arg(long)]
    pub include_pub: bool,
    /// Minimum confidence level to report
    #[arg(long, default_value = "low")]
    pub min_confidence: DeadConfidence,
}

/// Arguments shared between CLI `similar` and batch `similar`.
#[derive(Args, Debug, Clone)]
pub(crate) struct SimilarArgs {
    /// Function name or file:function (e.g., "search_filtered" or "src/search.rs:search_filtered")
    pub name: String,
    /// API-V1.38-10 (#1463): shared `--limit` arg via `LimitArg` flatten.
    #[command(flatten)]
    pub limit_arg: LimitArg,
    /// Min similarity threshold
    #[arg(short = 't', long, default_value = "0.3", value_parser = parse_finite_f32)]
    pub threshold: f32,
}

/// Arguments shared between CLI `blame` and batch `blame`.
#[derive(Args, Debug, Clone)]
pub(crate) struct BlameArgs {
    /// Function name or file:function
    pub name: String,
    /// Max commits to show.
    ///
    /// API-V1.22-4: renamed from `--depth`/`-d` to `--commits`/`-n` so blame
    /// stops sharing the `--depth` spelling with `onboard` (callee expansion
    /// depth) and `test-map` (call-chain BFS depth) — three commands had three
    /// different semantics under the same flag name. Hard rename, no alias —
    /// internal-only tool, see CLAUDE.md "No External Users".
    #[arg(short = 'n', long, default_value = "10")]
    pub commits: usize,
    /// Also show callers of the function
    #[arg(long)]
    pub callers: bool,
}

/// Arguments shared between CLI `trace` and batch `trace`.
#[derive(Args, Debug, Clone)]
pub(crate) struct TraceArgs {
    /// Source function name or file:function
    pub source: String,
    /// Target function name or file:function
    pub target: String,
    /// Max search depth (1-50). `trace` is path-search (find route
    /// A → B), not BFS-traversal — `--max-depth` means "give up looking
    /// for a path after N hops." `--depth` is accepted as an alias
    /// (#1373) so the spelling matches the rest of the depth-knob
    /// family even though the semantic differs.
    #[arg(
        short = 'd',
        long,
        visible_alias = "depth",
        default_value_t = DEFAULT_DEPTH_TRACE,
        value_parser = clap::value_parser!(u16).range(1..=50)
    )]
    pub max_depth: u16,
    /// Trace across all configured reference projects
    #[arg(long)]
    pub cross_project: bool,
    /// Task A3: cap on intermediate hops in the rendered path. Trace
    /// returns a single shortest path today; the cap applies to future
    /// k-shortest variants and to defensive truncation when path length
    /// exceeds expectation. Accepted for parity with other graph commands.
    #[command(flatten)]
    pub limit_arg: LimitArg,
}

/// Arguments shared between CLI `callers`/`callees` and batch equivalents.
#[derive(Args, Debug, Clone)]
pub(crate) struct CallersArgs {
    /// Function name to search for
    pub name: String,
    /// Query callers across all configured reference projects
    #[arg(long)]
    pub cross_project: bool,
    /// Task A3: cap on callers/callees returned. Defaults to 5 to match the
    /// top-level `Cli`. The handler truncates the post-resolution list before
    /// rendering — both text and JSON paths respect the cap.
    #[command(flatten)]
    pub limit_arg: LimitArg,
}

/// Arguments shared between CLI `deps` and batch `deps`.
#[derive(Args, Debug, Clone)]
pub(crate) struct DepsArgs {
    /// Type name (forward) or function name (with --reverse)
    pub name: String,
    /// Reverse: show types used by a function instead of type users
    #[arg(long)]
    pub reverse: bool,
    /// Query across all configured reference projects
    #[arg(long)]
    pub cross_project: bool,
    /// Task A3: cap on type users (forward) or used types (reverse). Defaults
    /// to 5 to match the top-level `Cli`. Truncated after fetch.
    #[command(flatten)]
    pub limit_arg: LimitArg,
}

/// Arguments shared between CLI `test-map` and batch `test-map`.
#[derive(Args, Debug, Clone)]
pub(crate) struct TestMapArgs {
    /// Function name or file:function
    pub name: String,
    /// Max call chain depth to search
    ///
    /// API-V1.29-10: `-d` short flag added for parity with `OnboardArgs::depth`
    /// which already accepts it.
    /// #1373: deeper than the WALK default because tests are leaves on
    /// the call graph; depth=3 frequently misses test files 4+ hops from
    /// a deep production chunk.
    #[arg(short = 'd', long, default_value_t = DEFAULT_DEPTH_TEST_MAP)]
    pub depth: usize,
    /// Search for tests across all configured reference projects
    #[arg(long)]
    pub cross_project: bool,
    /// Task A3: cap on test matches returned. Defaults to 5 to match the
    /// top-level `Cli`. Applied after BFS, before rendering.
    #[command(flatten)]
    pub limit_arg: LimitArg,
}

/// Arguments shared between CLI `related` and batch `related`.
#[derive(Args, Debug, Clone)]
pub(crate) struct RelatedArgs {
    /// Function name or file:function
    pub name: String,
    /// API-V1.38-10 (#1463): shared `--limit` arg via `LimitArg` flatten.
    #[command(flatten)]
    pub limit_arg: LimitArg,
}

/// Arguments shared between CLI `onboard` and batch `onboard`.
#[derive(Args, Debug, Clone)]
pub(crate) struct OnboardArgs {
    /// Concept or query to explore
    pub query: String,
    /// Call-chain expansion depth — #1373: WALK default.
    #[arg(short = 'd', long, default_value_t = DEFAULT_DEPTH_WALK)]
    pub depth: usize,
    /// Expansion direction: both, callers, callees.
    /// API-V1.36-4 (#1459): default `callees` preserves prior behavior;
    /// gather/test-map cross-command muscle memory now transfers.
    #[arg(long, default_value = "callees")]
    pub direction: cqs::GatherDirection,
    /// Maximum token budget
    #[arg(long, value_parser = parse_nonzero_usize)]
    pub tokens: Option<usize>,
    /// Task A3: cap on call_chain + callers entries (entry_point always
    /// kept). Defaults to 5 to match the top-level `Cli`. Applies after
    /// `--depth` traversal and before `--tokens` packing.
    #[command(flatten)]
    pub limit_arg: LimitArg,
}

/// Arguments shared between CLI `explain` and batch `explain`.
#[derive(Args, Debug, Clone)]
pub(crate) struct ExplainArgs {
    /// Function name or file:function
    pub name: String,
    /// Maximum token budget (includes source content within budget)
    #[arg(long, value_parser = parse_nonzero_usize)]
    pub tokens: Option<usize>,
    /// Task A3: cap on callers/callees/similar lists in the function card.
    /// Defaults to 5 to match the top-level `Cli`. Applied per-section.
    #[command(flatten)]
    pub limit_arg: LimitArg,
}

/// Arguments shared between CLI `where` and batch `where`.
#[derive(Args, Debug, Clone)]
pub(crate) struct WhereArgs {
    /// Description of the code to add
    pub description: String,
    /// API-V1.38-10 (#1463): shared `--limit` arg via `LimitArg` flatten.
    #[command(flatten)]
    pub limit_arg: LimitArg,
}

/// Arguments shared between CLI `plan` and batch `plan`.
#[derive(Args, Debug, Clone)]
pub(crate) struct PlanArgs {
    /// Task description to plan
    pub description: String,
    /// API-V1.38-10 (#1463): shared `--limit` arg via `LimitArg` flatten.
    #[command(flatten)]
    pub limit_arg: LimitArg,
    /// Maximum token budget
    #[arg(long, value_parser = parse_nonzero_usize)]
    pub tokens: Option<usize>,
}

/// Arguments shared between CLI `task` and batch `task`.
///
/// The `brief` flag is CLI-only for now (batch `task` doesn't surface it),
/// but lives here so a future flip to enabling it in batch is a no-op.
#[derive(Args, Debug, Clone)]
pub(crate) struct TaskArgs {
    /// Task description
    pub description: String,
    /// API-V1.38-10 (#1463): shared `--limit` arg via `LimitArg` flatten.
    #[command(flatten)]
    pub limit_arg: LimitArg,
    /// Maximum token budget (waterfall across sections)
    #[arg(long, value_parser = parse_nonzero_usize)]
    pub tokens: Option<usize>,
    /// Compact output (~200 tokens): files, at-risk functions, test coverage
    #[arg(long)]
    pub brief: bool,
}

/// Arguments shared between CLI `read` and batch `read`.
#[derive(Args, Debug, Clone)]
pub(crate) struct ReadArgs {
    /// File path relative to project root
    pub path: String,
    /// Focus on a specific function (returns only that function + type deps)
    #[arg(long)]
    pub focus: Option<String>,
}

/// Arguments shared between CLI `stale` and batch `stale`.
#[derive(Args, Debug, Clone)]
pub(crate) struct StaleArgs {
    /// Show counts only, skip file list
    #[arg(long)]
    pub count_only: bool,
}

/// Arguments shared between CLI `suggest` and batch `suggest`.
#[derive(Args, Debug, Clone)]
pub(crate) struct SuggestArgs {
    /// Apply suggestions (add notes to docs/notes.toml)
    #[arg(long)]
    pub apply: bool,
}

/// Arguments shared between CLI `diff` and batch `diff`.
#[derive(Args, Debug, Clone)]
pub(crate) struct DiffArgs {
    /// Source reference name
    pub source: String,
    /// Target reference (default: project)
    pub target: Option<String>,
    /// Similarity threshold for "modified" (default: 0.95)
    ///
    /// `-t` here means "match threshold" — pairs above this are "unchanged",
    /// below are "modified". Different from search's `-t` (min similarity 0.3).
    #[arg(short = 't', long, default_value = "0.95", value_parser = parse_finite_f32)]
    pub threshold: f32,
    /// Filter by language
    #[arg(short = 'l', long)]
    pub lang: Option<String>,
}

/// Arguments shared between CLI `drift` and batch `drift`.
#[derive(Args, Debug, Clone)]
pub(crate) struct DriftArgs {
    /// Reference name to compare against
    pub reference: String,
    /// Similarity threshold (default: 0.95). See Diff's `-t` doc.
    #[arg(short = 't', long, default_value = "0.95", value_parser = parse_finite_f32)]
    pub threshold: f32,
    /// Minimum drift to show (default: 0.0)
    #[arg(long, default_value = "0.0", value_parser = parse_finite_f32)]
    pub min_drift: f32,
    /// Filter by language
    #[arg(short = 'l', long)]
    pub lang: Option<String>,
    /// Maximum entries to show
    #[arg(short = 'n', long)]
    pub limit: Option<usize>,
}

/// Arguments shared between CLI `review` and batch `review`.
///
/// The `stdin` flag is CLI-only (batch `review` reads the diff itself via
/// `base` and the working tree). Keeping it on the shared struct costs one
/// flag on the batch grammar but keeps the path symmetric.
#[derive(Args, Debug, Clone)]
pub(crate) struct ReviewArgs {
    /// Git ref to diff against (default: unstaged changes)
    #[arg(long)]
    pub base: Option<String>,
    /// Read diff from stdin instead of running git
    #[arg(long)]
    pub stdin: bool,
    /// Maximum token budget for output (truncates callers/tests lists)
    #[arg(long, value_parser = parse_nonzero_usize)]
    pub tokens: Option<usize>,
}

/// Arguments shared between CLI `ci` and batch `ci`.
#[derive(Args, Debug, Clone)]
pub(crate) struct CiArgs {
    /// Git ref to diff against (default: unstaged changes)
    #[arg(long)]
    pub base: Option<String>,
    /// Read diff from stdin instead of running git
    #[arg(long)]
    pub stdin: bool,
    /// Gate threshold: high, medium, off
    #[arg(long, default_value = "high")]
    pub gate: super::GateThreshold,
    /// Maximum token budget for output
    #[arg(long, value_parser = parse_nonzero_usize)]
    pub tokens: Option<usize>,
}

/// Arguments shared between CLI `impact-diff` and batch `impact-diff`.
#[derive(Args, Debug, Clone)]
pub(crate) struct ImpactDiffArgs {
    /// Git ref to diff against (default: unstaged changes)
    #[arg(long)]
    pub base: Option<String>,
    /// Read diff from stdin instead of running git
    #[arg(long)]
    pub stdin: bool,
}

/// Arguments shared between CLI `notes` (list subcommand) and batch `notes`.
///
/// Subcommand mutations (`add` / `update` / `remove`) remain on the CLI
/// `NotesCommand` subcommand enum and are not batch-dispatchable — see the
/// `BatchSupport` classifier for the policy.
///
/// EX-V1.29-5 / API-V1.29-4: `NotesCommand::List` flattens this struct
/// (same pattern as `Commands::Search { args: SearchArgs }`). The flattened
/// fields include `check`, which the daemon batch path picks up via
/// `BatchCmd::Notes { args, .. }` — previously `NotesCommand::List` had
/// `check: bool` inline and the daemon dropped it silently.
#[derive(Args, Debug, Clone)]
pub(crate) struct NotesListArgs {
    /// Show only warnings (negative sentiment)
    #[arg(long)]
    pub warnings: bool,
    /// Show only patterns (positive sentiment)
    #[arg(long)]
    pub patterns: bool,
    /// Filter by kind tag (e.g. `todo`, `design-decision`, `known-bug`).
    /// Matches against the v25 `notes.kind` column (kebab-case lowercase).
    /// ANDs with `--warnings` / `--patterns` when combined.
    #[arg(long)]
    pub kind: Option<String>,
    /// Check mentions for staleness (verifies files exist and symbols are in index)
    #[arg(long)]
    pub check: bool,
}

/// Arguments for the `index` command.
#[derive(Args, Debug, Clone)]
pub(crate) struct IndexArgs {
    /// Re-index all files, ignore mtime cache
    #[arg(long)]
    pub force: bool,
    /// Show what would be indexed (default writes the index).
    ///
    /// Audit P2 #38: per the CONTRIBUTING "Dry-Run vs Apply" rule, side-effect
    /// commands (`index`, `convert`) default to mutating; analyser commands
    /// (`doctor`, `suggest`) default to read-only and require `--fix`/`--apply`
    /// to mutate. TODO(docs-agent): document this rule in CONTRIBUTING.md.
    #[arg(long)]
    pub dry_run: bool,
    /// Index files ignored by .gitignore
    #[arg(long)]
    pub no_ignore: bool,
    /// Skip the first-encounter prompt for committed `docs/notes.toml`.
    ///
    /// On the first index of a fresh repo containing `docs/notes.toml`, cqs
    /// prompts to confirm — committed notes affect search rankings and surface
    /// in agent context. Pass `--accept-shared-notes` to bypass the prompt for
    /// non-interactive use (CI, scripts). Acceptance is persisted to
    /// `.cqs/.accepted-shared-notes` so the prompt doesn't repeat. (#1168)
    #[arg(long)]
    pub accept_shared_notes: bool,
    /// Generate LLM summaries for functions (requires ANTHROPIC_API_KEY)
    #[cfg(feature = "llm-summaries")]
    #[arg(long)]
    pub llm_summaries: bool,
    /// Generate doc comments for undocumented functions (requires --llm-summaries).
    ///
    /// By default, writes proposed edits as unified-diff patches to
    /// `.cqs/proposed-docs/<rel>.patch` for human review. Apply with
    /// `git apply .cqs/proposed-docs/**/*.patch`. Pass `--apply` to write
    /// directly to source files without review.
    #[cfg(feature = "llm-summaries")]
    #[arg(long)]
    pub improve_docs: bool,
    /// Write generated doc comments directly to source files instead of producing
    /// review patches under `.cqs/proposed-docs/`. Requires `--improve-docs`.
    #[cfg(feature = "llm-summaries")]
    #[arg(long)]
    pub apply: bool,
    /// Regenerate doc comments for all functions, even those with existing docs (requires --improve-docs)
    #[cfg(feature = "llm-summaries")]
    #[arg(long)]
    pub improve_all: bool,
    /// Maximum number of functions to generate docs for (used with --improve-docs)
    #[cfg(feature = "llm-summaries")]
    #[arg(long)]
    pub max_docs: Option<usize>,
    /// Generate hyde query predictions for functions (requires ANTHROPIC_API_KEY)
    #[cfg(feature = "llm-summaries")]
    #[arg(long)]
    pub hyde_queries: bool,
    /// Maximum number of functions to generate hyde predictions for
    #[cfg(feature = "llm-summaries")]
    #[arg(long)]
    pub max_hyde: Option<usize>,
    /// Project chunk embeddings into 2D via UMAP and write to `chunks.umap_x/umap_y`.
    ///
    /// Enables the `cqs serve` cluster view (`?view=cluster`). Requires
    /// `umap-learn` Python package (`pip install umap-learn`). Skipped with
    /// a warning if Python or umap-learn is missing. Runs once per `cqs index`
    /// invocation; on large corpora (50k+ chunks) can take ~2 minutes CPU.
    #[arg(long)]
    pub umap: bool,
    /// P2.12: emit a structured JSON envelope summarizing the index run on
    /// completion. Suppresses progress prints in favor of a single
    /// `{indexed_files, indexed_chunks, took_ms, model, …}` summary so
    /// JSON-driven agents can chain `cqs init && cqs index --json`.
    #[arg(long)]
    pub json: bool,
}

/// #1216: args for `BatchCmd::Reconcile`. Wrapped in a struct so the
/// dispatch table can hand the variant straight to its handler the same
/// way every other variant does (`dispatch_x(ctx, &args)`). Fields are
/// advisory — they ride along for tracing/logging and don't change the
/// reconcile algorithm itself.
#[derive(Args, Debug, Clone)]
pub(crate) struct ReconcileArgs {
    /// Name of the hook that fired this reconcile (e.g. `post-checkout`).
    /// Logged for operator diagnostics; not used for the walk itself.
    #[arg(long)]
    pub hook: Option<String>,
    /// Free-form positional payload from the hook (e.g. previous and
    /// current commit SHAs). Captured for tracing only.
    #[arg(long = "arg", value_name = "ARG")]
    pub args: Vec<String>,
}

/// #1216 + #1228: args for `BatchCmd::WaitFresh`. Wrapped in a struct
/// (originally inline `wait_secs: u64`) so the dispatch table can hand
/// the variant straight to its handler under the uniform `&args` shape.
#[derive(Args, Debug, Clone)]
pub(crate) struct WaitFreshArgs {
    /// Maximum seconds to block before returning the current
    /// (still-stale) snapshot. Capped server-side at 86_400 (24 h) for
    /// parity with the client-side cap in `wait_for_fresh`.
    #[arg(long, default_value_t = 60)]
    pub wait_secs: u64,
}
