pub mod autocorrect;
pub mod commands;
pub mod progress;
pub mod render;
pub mod robot;

use clap::{Parser, Subcommand};
use std::io::IsTerminal;

#[derive(Parser)]
#[command(name = "lore")]
#[command(version = env!("LORE_VERSION"), about = "Local GitLab data management with semantic search", long_about = None)]
#[command(subcommand_required = false)]
#[command(infer_subcommands = true)]
#[command(after_long_help = "\x1b[1mEnvironment:\x1b[0m
  GITLAB_TOKEN       GitLab personal access token (or name set in config)
  LORE_ROBOT         Enable robot/JSON mode (non-empty, non-zero value)
  LORE_CONFIG_PATH   Override config file location
  NO_COLOR           Disable color output (any non-empty value)")]
pub struct Cli {
    /// Path to config file
    #[arg(short = 'c', long, global = true, help = "Path to config file")]
    pub config: Option<String>,

    /// Machine-readable JSON output (auto-enabled when piped)
    #[arg(
        long,
        global = true,
        env = "LORE_ROBOT",
        help = "Machine-readable JSON output (auto-enabled when piped)"
    )]
    pub robot: bool,

    /// JSON output (global shorthand)
    #[arg(
        short = 'J',
        long = "json",
        global = true,
        help = "JSON output (global shorthand)"
    )]
    pub json: bool,

    /// Color output: auto (default), always, or never
    #[arg(long, global = true, value_parser = ["auto", "always", "never"], default_value = "auto", help = "Color output: auto (default), always, or never")]
    pub color: String,

    /// Icon set: nerd (Nerd Fonts), unicode, or ascii
    #[arg(long, global = true, value_parser = ["nerd", "unicode", "ascii"], help = "Icon set: nerd (Nerd Fonts), unicode, or ascii")]
    pub icons: Option<String>,

    /// Suppress non-essential output
    #[arg(
        short = 'q',
        long,
        global = true,
        overrides_with = "no_quiet",
        help = "Suppress non-essential output"
    )]
    pub quiet: bool,

    #[arg(
        long = "no-quiet",
        global = true,
        hide = true,
        overrides_with = "quiet"
    )]
    pub no_quiet: bool,

    /// Increase log verbosity (-v, -vv, -vvv)
    #[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count, global = true, help = "Increase log verbosity (-v, -vv, -vvv)", overrides_with = "no_verbose")]
    pub verbose: u8,

    #[arg(
        long = "no-verbose",
        global = true,
        hide = true,
        overrides_with = "verbose"
    )]
    pub no_verbose: bool,

    /// Log format for stderr output: text (default) or json
    #[arg(long = "log-format", global = true, value_parser = ["text", "json"], default_value = "text", help = "Log format for stderr output: text (default) or json")]
    pub log_format: String,

    #[command(subcommand)]
    pub command: Option<Commands>,
}

impl Cli {
    pub fn is_robot_mode(&self) -> bool {
        self.robot || self.json || !std::io::stdout().is_terminal()
    }

    /// Detect robot mode from environment before parsing succeeds.
    /// Used for structured error output when clap parsing fails.
    /// Also catches common agent typos like `-robot` and `--Robot`.
    pub fn detect_robot_mode_from_env() -> bool {
        let args: Vec<String> = std::env::args().collect();
        args.iter().any(|a| {
            a == "-J"
                || a.eq_ignore_ascii_case("--robot")
                || a.eq_ignore_ascii_case("-robot")
                || a.eq_ignore_ascii_case("--json")
                || a.eq_ignore_ascii_case("-json")
        }) || std::env::var("LORE_ROBOT")
            .ok()
            .is_some_and(|v| !v.is_empty() && v != "0" && v != "false")
            || !std::io::stdout().is_terminal()
    }
}

#[derive(Subcommand)]
#[allow(clippy::large_enum_variant)]
pub enum Commands {
    /// List or show issues
    #[command(visible_alias = "issue")]
    Issues(IssuesArgs),

    /// List or show merge requests
    #[command(
        visible_alias = "mr",
        alias = "merge-requests",
        alias = "merge-request"
    )]
    Mrs(MrsArgs),

    /// List notes from discussions
    #[command(visible_alias = "note")]
    Notes(NotesArgs),

    /// Ingest data from GitLab
    Ingest(IngestArgs),

    /// Count entities in local database
    Count(CountArgs),

    /// Show sync state
    #[command(visible_alias = "st")]
    Status,

    /// Verify GitLab authentication
    Auth,

    /// Check environment health
    Doctor,

    /// Show version information
    Version,

    /// Initialize configuration and database
    Init {
        /// Skip overwrite confirmation
        #[arg(short = 'f', long)]
        force: bool,

        /// Fail if prompts would be shown
        #[arg(long)]
        non_interactive: bool,

        /// GitLab base URL (required in robot mode)
        #[arg(long)]
        gitlab_url: Option<String>,

        /// Environment variable name holding GitLab token (required in robot mode)
        #[arg(long)]
        token_env_var: Option<String>,

        /// Comma-separated project paths (required in robot mode)
        #[arg(long)]
        projects: Option<String>,

        /// Default project path (used when -p is omitted)
        #[arg(long)]
        default_project: Option<String>,
    },

    #[command(hide = true)]
    Backup,

    #[command(hide = true)]
    Reset {
        #[arg(short = 'y', long)]
        yes: bool,
    },

    /// Search indexed documents
    #[command(visible_alias = "find", alias = "query")]
    Search(SearchArgs),

    /// Show document and index statistics
    #[command(visible_alias = "stat")]
    Stats(StatsArgs),

    /// Generate searchable documents from ingested data
    #[command(name = "generate-docs")]
    GenerateDocs(GenerateDocsArgs),

    /// Generate vector embeddings for documents via Ollama
    Embed(EmbedArgs),

    /// Run full sync pipeline: ingest -> generate-docs -> embed
    Sync(SyncArgs),

    /// Run pending database migrations
    Migrate,

    /// Quick health check: config, database, schema version
    Health,

    /// Machine-readable command manifest for agent self-discovery
    #[command(name = "robot-docs")]
    RobotDocs {
        /// Omit response_schema from output (~60% smaller)
        #[arg(long)]
        brief: bool,
    },

    /// Generate shell completions
    #[command(long_about = "Generate shell completions for lore.\n\n\
        Installation:\n  \
        bash:  lore completions bash > ~/.local/share/bash-completion/completions/lore\n  \
        zsh:   lore completions zsh > ~/.zfunc/_lore && echo 'fpath+=~/.zfunc' >> ~/.zshrc\n  \
        fish:  lore completions fish > ~/.config/fish/completions/lore.fish\n  \
        pwsh:  lore completions powershell >> $PROFILE")]
    Completions {
        /// Shell to generate completions for
        #[arg(value_parser = ["bash", "zsh", "fish", "powershell"])]
        shell: String,
    },

    /// Show a chronological timeline of events matching a query
    Timeline(TimelineArgs),

    /// People intelligence: experts, workload, active discussions, overlap
    Who(WhoArgs),

    /// Show MRs that touched a file, with linked discussions
    #[command(name = "file-history")]
    FileHistory(FileHistoryArgs),

    /// Trace why code was introduced: file -> MR -> issue -> discussion
    Trace(TraceArgs),

    /// Detect discussion divergence from original intent
    Drift {
        /// Entity type (currently only "issues" supported)
        #[arg(value_parser = ["issues"])]
        entity_type: String,

        /// Entity IID
        iid: i64,

        /// Similarity threshold for drift detection (0.0-1.0)
        #[arg(long, default_value = "0.4")]
        threshold: f32,

        /// Scope to project (fuzzy match)
        #[arg(short, long)]
        project: Option<String>,
    },

    #[command(hide = true)]
    List {
        #[arg(value_parser = ["issues", "mrs"])]
        entity: String,

        #[arg(long, default_value = "50")]
        limit: usize,
        #[arg(long)]
        project: Option<String>,
        #[arg(long)]
        state: Option<String>,
        #[arg(long)]
        author: Option<String>,
        #[arg(long)]
        assignee: Option<String>,
        #[arg(long)]
        label: Option<Vec<String>>,
        #[arg(long)]
        milestone: Option<String>,
        #[arg(long)]
        since: Option<String>,
        #[arg(long)]
        due_before: Option<String>,
        #[arg(long)]
        has_due_date: bool,
        #[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated")]
        sort: String,
        #[arg(long, value_parser = ["desc", "asc"], default_value = "desc")]
        order: String,
        #[arg(long)]
        open: bool,
        #[arg(long, conflicts_with = "no_draft")]
        draft: bool,
        #[arg(long, conflicts_with = "draft")]
        no_draft: bool,
        #[arg(long)]
        reviewer: Option<String>,
        #[arg(long)]
        target_branch: Option<String>,
        #[arg(long)]
        source_branch: Option<String>,
    },

    #[command(hide = true)]
    Show {
        #[arg(value_parser = ["issue", "mr"])]
        entity: String,

        iid: i64,

        #[arg(long)]
        project: Option<String>,
    },

    #[command(hide = true, name = "auth-test")]
    AuthTest,

    #[command(hide = true, name = "sync-status")]
    SyncStatus,
}

#[derive(Parser)]
#[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore issues -n 10                     # List 10 most recently updated issues
  lore issues -s opened -l bug          # Open issues labeled 'bug'
  lore issues 42 -p group/repo          # Show issue #42 in a specific project
  lore issues --since 7d -a jsmith      # Issues updated in last 7 days by jsmith")]
pub struct IssuesArgs {
    /// Issue IID (omit to list, provide to show details)
    pub iid: Option<i64>,

    /// Maximum results
    #[arg(
        short = 'n',
        long = "limit",
        default_value = "50",
        help_heading = "Output"
    )]
    pub limit: usize,

    /// Select output fields (comma-separated, or 'minimal' preset: iid,title,state,updated_at_iso)
    #[arg(long, help_heading = "Output", value_delimiter = ',')]
    pub fields: Option<Vec<String>>,

    /// Filter by state (opened, closed, all)
    #[arg(short = 's', long, help_heading = "Filters")]
    pub state: Option<String>,

    /// Filter by project path
    #[arg(short = 'p', long, help_heading = "Filters")]
    pub project: Option<String>,

    /// Filter by author username
    #[arg(short = 'a', long, help_heading = "Filters")]
    pub author: Option<String>,

    /// Filter by assignee username
    #[arg(short = 'A', long, help_heading = "Filters")]
    pub assignee: Option<String>,

    /// Filter by label (repeatable, AND logic)
    #[arg(short = 'l', long, help_heading = "Filters")]
    pub label: Option<Vec<String>>,

    /// Filter by milestone title
    #[arg(short = 'm', long, help_heading = "Filters")]
    pub milestone: Option<String>,

    /// Filter by work-item status name (repeatable, OR logic)
    #[arg(long, help_heading = "Filters")]
    pub status: Vec<String>,

    /// Filter by time (7d, 2w, 1m, or YYYY-MM-DD)
    #[arg(long, help_heading = "Filters")]
    pub since: Option<String>,

    /// Filter by due date (before this date, YYYY-MM-DD)
    #[arg(long = "due-before", help_heading = "Filters")]
    pub due_before: Option<String>,

    /// Show only issues with a due date
    #[arg(
        long = "has-due",
        help_heading = "Filters",
        overrides_with = "no_has_due"
    )]
    pub has_due: bool,

    #[arg(long = "no-has-due", hide = true, overrides_with = "has_due")]
    pub no_has_due: bool,

    /// Sort field (updated, created, iid)
    #[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated", help_heading = "Sorting")]
    pub sort: String,

    /// Sort ascending (default: descending)
    #[arg(long, help_heading = "Sorting", overrides_with = "no_asc")]
    pub asc: bool,

    #[arg(long = "no-asc", hide = true, overrides_with = "asc")]
    pub no_asc: bool,

    /// Open first matching item in browser
    #[arg(
        short = 'o',
        long,
        help_heading = "Actions",
        overrides_with = "no_open"
    )]
    pub open: bool,

    #[arg(long = "no-open", hide = true, overrides_with = "open")]
    pub no_open: bool,
}

#[derive(Parser)]
#[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore mrs -s opened                    # List open merge requests
  lore mrs -s merged --since 2w         # MRs merged in the last 2 weeks
  lore mrs 99 -p group/repo             # Show MR !99 in a specific project
  lore mrs -D --reviewer jsmith         # Non-draft MRs reviewed by jsmith")]
pub struct MrsArgs {
    /// MR IID (omit to list, provide to show details)
    pub iid: Option<i64>,

    /// Maximum results
    #[arg(
        short = 'n',
        long = "limit",
        default_value = "50",
        help_heading = "Output"
    )]
    pub limit: usize,

    /// Select output fields (comma-separated, or 'minimal' preset: iid,title,state,updated_at_iso)
    #[arg(long, help_heading = "Output", value_delimiter = ',')]
    pub fields: Option<Vec<String>>,

    /// Filter by state (opened, merged, closed, locked, all)
    #[arg(short = 's', long, help_heading = "Filters")]
    pub state: Option<String>,

    /// Filter by project path
    #[arg(short = 'p', long, help_heading = "Filters")]
    pub project: Option<String>,

    /// Filter by author username
    #[arg(short = 'a', long, help_heading = "Filters")]
    pub author: Option<String>,

    /// Filter by assignee username
    #[arg(short = 'A', long, help_heading = "Filters")]
    pub assignee: Option<String>,

    /// Filter by reviewer username
    #[arg(short = 'r', long, help_heading = "Filters")]
    pub reviewer: Option<String>,

    /// Filter by label (repeatable, AND logic)
    #[arg(short = 'l', long, help_heading = "Filters")]
    pub label: Option<Vec<String>>,

    /// Filter by time (7d, 2w, 1m, or YYYY-MM-DD)
    #[arg(long, help_heading = "Filters")]
    pub since: Option<String>,

    /// Show only draft MRs
    #[arg(
        short = 'd',
        long,
        conflicts_with = "no_draft",
        help_heading = "Filters"
    )]
    pub draft: bool,

    /// Exclude draft MRs
    #[arg(
        short = 'D',
        long = "no-draft",
        conflicts_with = "draft",
        help_heading = "Filters"
    )]
    pub no_draft: bool,

    /// Filter by target branch
    #[arg(long, help_heading = "Filters")]
    pub target: Option<String>,

    /// Filter by source branch
    #[arg(long, help_heading = "Filters")]
    pub source: Option<String>,

    /// Sort field (updated, created, iid)
    #[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated", help_heading = "Sorting")]
    pub sort: String,

    /// Sort ascending (default: descending)
    #[arg(long, help_heading = "Sorting", overrides_with = "no_asc")]
    pub asc: bool,

    #[arg(long = "no-asc", hide = true, overrides_with = "asc")]
    pub no_asc: bool,

    /// Open first matching item in browser
    #[arg(
        short = 'o',
        long,
        help_heading = "Actions",
        overrides_with = "no_open"
    )]
    pub open: bool,

    #[arg(long = "no-open", hide = true, overrides_with = "open")]
    pub no_open: bool,
}

#[derive(Parser)]
#[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore notes                                  # List 50 most recent notes
  lore notes --author alice --since 7d        # Notes by alice in last 7 days
  lore notes --for-issue 42 -p group/repo     # Notes on issue #42
  lore notes --path src/ --resolution unresolved  # Unresolved diff notes in src/")]
pub struct NotesArgs {
    /// Maximum results
    #[arg(
        short = 'n',
        long = "limit",
        default_value = "50",
        help_heading = "Output"
    )]
    pub limit: usize,

    /// Select output fields (comma-separated, or 'minimal' preset: id,author_username,body,created_at_iso)
    #[arg(long, help_heading = "Output", value_delimiter = ',')]
    pub fields: Option<Vec<String>>,

    /// Output format (table, json, jsonl, csv)
    #[arg(
        long,
        default_value = "table",
        value_parser = ["table", "json", "jsonl", "csv"],
        help_heading = "Output"
    )]
    pub format: String,

    /// Filter by author username
    #[arg(short = 'a', long, help_heading = "Filters")]
    pub author: Option<String>,

    /// Filter by note type (DiffNote, DiscussionNote)
    #[arg(long, help_heading = "Filters")]
    pub note_type: Option<String>,

    /// Filter by body text (substring match)
    #[arg(long, help_heading = "Filters")]
    pub contains: Option<String>,

    /// Filter by internal note ID
    #[arg(long, help_heading = "Filters")]
    pub note_id: Option<i64>,

    /// Filter by GitLab note ID
    #[arg(long, help_heading = "Filters")]
    pub gitlab_note_id: Option<i64>,

    /// Filter by discussion ID
    #[arg(long, help_heading = "Filters")]
    pub discussion_id: Option<String>,

    /// Include system notes (excluded by default)
    #[arg(long, help_heading = "Filters")]
    pub include_system: bool,

    /// Filter to notes on a specific issue IID (requires --project or default_project)
    #[arg(long, conflicts_with = "for_mr", help_heading = "Filters")]
    pub for_issue: Option<i64>,

    /// Filter to notes on a specific MR IID (requires --project or default_project)
    #[arg(long, conflicts_with = "for_issue", help_heading = "Filters")]
    pub for_mr: Option<i64>,

    /// Filter by project path
    #[arg(short = 'p', long, help_heading = "Filters")]
    pub project: Option<String>,

    /// Filter by time (7d, 2w, 1m, or YYYY-MM-DD)
    #[arg(long, help_heading = "Filters")]
    pub since: Option<String>,

    /// Filter until date (YYYY-MM-DD, inclusive end-of-day)
    #[arg(long, help_heading = "Filters")]
    pub until: Option<String>,

    /// Filter by file path (exact match or prefix with trailing /)
    #[arg(long, help_heading = "Filters")]
    pub path: Option<String>,

    /// Filter by resolution status (any, unresolved, resolved)
    #[arg(
        long,
        value_parser = ["any", "unresolved", "resolved"],
        help_heading = "Filters"
    )]
    pub resolution: Option<String>,

    /// Sort field (created, updated)
    #[arg(
        long,
        value_parser = ["created", "updated"],
        default_value = "created",
        help_heading = "Sorting"
    )]
    pub sort: String,

    /// Sort ascending (default: descending)
    #[arg(long, help_heading = "Sorting")]
    pub asc: bool,

    /// Open first matching item in browser
    #[arg(long, help_heading = "Actions")]
    pub open: bool,
}

#[derive(Parser)]
pub struct IngestArgs {
    /// Entity to ingest (issues, mrs). Omit to ingest everything
    #[arg(value_parser = ["issues", "mrs"])]
    pub entity: Option<String>,

    /// Filter to single project
    #[arg(short = 'p', long)]
    pub project: Option<String>,

    /// Override stale sync lock
    #[arg(short = 'f', long, overrides_with = "no_force")]
    pub force: bool,

    #[arg(long = "no-force", hide = true, overrides_with = "force")]
    pub no_force: bool,

    /// Full re-sync: reset cursors and fetch all data from scratch
    #[arg(long, overrides_with = "no_full")]
    pub full: bool,

    #[arg(long = "no-full", hide = true, overrides_with = "full")]
    pub no_full: bool,

    /// Preview what would be synced without making changes
    #[arg(long, overrides_with = "no_dry_run")]
    pub dry_run: bool,

    #[arg(long = "no-dry-run", hide = true, overrides_with = "dry_run")]
    pub no_dry_run: bool,
}

#[derive(Parser)]
pub struct StatsArgs {
    /// Run integrity checks
    #[arg(long, overrides_with = "no_check")]
    pub check: bool,

    #[arg(long = "no-check", hide = true, overrides_with = "check")]
    pub no_check: bool,

    /// Repair integrity issues (auto-enables --check)
    #[arg(long)]
    pub repair: bool,

    /// Preview what would be repaired without making changes (requires --repair)
    #[arg(long, overrides_with = "no_dry_run")]
    pub dry_run: bool,

    #[arg(long = "no-dry-run", hide = true, overrides_with = "dry_run")]
    pub no_dry_run: bool,
}

#[derive(Parser)]
#[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore search 'authentication bug'               # Hybrid search (default)
  lore search 'deploy' --mode lexical --type mr   # Lexical search, MRs only
  lore search 'API rate limit' --since 30d        # Recent results only
  lore search 'config' -p group/repo --explain    # With ranking explanation")]
pub struct SearchArgs {
    /// Search query string
    pub query: String,

    /// Search mode (lexical, hybrid, semantic)
    #[arg(long, default_value = "hybrid", value_parser = ["lexical", "hybrid", "semantic"], help_heading = "Mode")]
    pub mode: String,

    /// Filter by source type (issue, mr, discussion, note)
    #[arg(long = "type", value_name = "TYPE", value_parser = ["issue", "mr", "discussion", "note"], help_heading = "Filters")]
    pub source_type: Option<String>,

    /// Filter by author username
    #[arg(long, help_heading = "Filters")]
    pub author: Option<String>,

    /// Filter by project path
    #[arg(short = 'p', long, help_heading = "Filters")]
    pub project: Option<String>,

    /// Filter by label (repeatable, AND logic)
    #[arg(long, action = clap::ArgAction::Append, help_heading = "Filters")]
    pub label: Vec<String>,

    /// Filter by file path (trailing / for prefix match)
    #[arg(long, help_heading = "Filters")]
    pub path: Option<String>,

    /// Filter by created since (7d, 2w, or YYYY-MM-DD)
    #[arg(long, help_heading = "Filters")]
    pub since: Option<String>,

    /// Filter by updated since (7d, 2w, or YYYY-MM-DD)
    #[arg(long = "updated-since", help_heading = "Filters")]
    pub updated_since: Option<String>,

    /// Maximum results (default 20, max 100)
    #[arg(
        short = 'n',
        long = "limit",
        default_value = "20",
        help_heading = "Output"
    )]
    pub limit: usize,

    /// Select output fields (comma-separated, or 'minimal' preset: document_id,title,source_type,score)
    #[arg(long, help_heading = "Output", value_delimiter = ',')]
    pub fields: Option<Vec<String>>,

    /// Show ranking explanation per result
    #[arg(long, help_heading = "Output", overrides_with = "no_explain")]
    pub explain: bool,

    #[arg(long = "no-explain", hide = true, overrides_with = "explain")]
    pub no_explain: bool,

    /// FTS query mode: safe (default) or raw
    #[arg(long = "fts-mode", default_value = "safe", value_parser = ["safe", "raw"], help_heading = "Mode")]
    pub fts_mode: String,
}

#[derive(Parser)]
pub struct GenerateDocsArgs {
    /// Full rebuild: seed all entities into dirty queue, then drain
    #[arg(long)]
    pub full: bool,

    /// Filter to single project
    #[arg(short = 'p', long)]
    pub project: Option<String>,
}

#[derive(Parser)]
#[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore sync                             # Full pipeline: ingest + docs + embed
  lore sync --no-embed                  # Skip embedding step
  lore sync --no-status                 # Skip work-item status enrichment
  lore sync --full --force              # Full re-sync, override stale lock
  lore sync --dry-run                   # Preview what would change")]
pub struct SyncArgs {
    /// Reset cursors, fetch everything
    #[arg(long, overrides_with = "no_full")]
    pub full: bool,

    #[arg(long = "no-full", hide = true, overrides_with = "full")]
    pub no_full: bool,

    /// Override stale lock
    #[arg(long, overrides_with = "no_force")]
    pub force: bool,

    #[arg(long = "no-force", hide = true, overrides_with = "force")]
    pub no_force: bool,

    /// Skip embedding step
    #[arg(long)]
    pub no_embed: bool,

    /// Skip document regeneration
    #[arg(long)]
    pub no_docs: bool,

    /// Skip resource event fetching (overrides config)
    #[arg(long = "no-events")]
    pub no_events: bool,

    /// Skip MR file change fetching (overrides config)
    #[arg(long = "no-file-changes")]
    pub no_file_changes: bool,

    /// Skip work-item status enrichment via GraphQL (overrides config)
    #[arg(long = "no-status")]
    pub no_status: bool,

    /// Preview what would be synced without making changes
    #[arg(long, overrides_with = "no_dry_run")]
    pub dry_run: bool,

    #[arg(long = "no-dry-run", hide = true, overrides_with = "dry_run")]
    pub no_dry_run: bool,

    /// Show detailed timing breakdown for sync stages
    #[arg(short = 't', long = "timings")]
    pub timings: bool,
}

#[derive(Parser)]
pub struct EmbedArgs {
    /// Re-embed all documents (clears existing embeddings first)
    #[arg(long, overrides_with = "no_full")]
    pub full: bool,

    #[arg(long = "no-full", hide = true, overrides_with = "full")]
    pub no_full: bool,

    /// Retry previously failed embeddings
    #[arg(long, overrides_with = "no_retry_failed")]
    pub retry_failed: bool,

    #[arg(long = "no-retry-failed", hide = true, overrides_with = "retry_failed")]
    pub no_retry_failed: bool,
}

#[derive(Parser)]
#[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore timeline 'deployment'                          # Search-based seeding
  lore timeline issue:42                              # Direct: issue #42 and related entities
  lore timeline i:42                                  # Shorthand for issue:42
  lore timeline mr:99                                 # Direct: MR !99 and related entities
  lore timeline 'auth' --since 30d -p group/repo      # Scoped to project and time
  lore timeline 'migration' --depth 2              # Deep cross-reference expansion
  lore timeline 'auth' --no-mentions               # Only 'closes' and 'related' edges")]
pub struct TimelineArgs {
    /// Search text or entity reference (issue:N, i:N, mr:N, m:N)
    pub query: String,

    /// Scope to a specific project (fuzzy match)
    #[arg(short = 'p', long, help_heading = "Filters")]
    pub project: Option<String>,

    /// Only show events after this date (e.g. "6m", "2w", "2024-01-01")
    #[arg(long, help_heading = "Filters")]
    pub since: Option<String>,

    /// Cross-reference expansion depth (0 = no expansion)
    #[arg(long, default_value = "1", help_heading = "Expansion")]
    pub depth: u32,

    /// Skip 'mentioned' edges during expansion (only follow 'closes' and 'related')
    #[arg(long = "no-mentions", help_heading = "Expansion")]
    pub no_mentions: bool,

    /// Maximum number of events to display
    #[arg(
        short = 'n',
        long = "limit",
        default_value = "100",
        help_heading = "Output"
    )]
    pub limit: usize,

    /// Select output fields (comma-separated, or 'minimal' preset: timestamp,type,entity_iid,detail)
    #[arg(long, help_heading = "Output", value_delimiter = ',')]
    pub fields: Option<Vec<String>>,

    /// Maximum seed entities from search
    #[arg(long = "max-seeds", default_value = "10", help_heading = "Expansion")]
    pub max_seeds: usize,

    /// Maximum expanded entities via cross-references
    #[arg(
        long = "max-entities",
        default_value = "50",
        help_heading = "Expansion"
    )]
    pub max_entities: usize,

    /// Maximum evidence notes included
    #[arg(
        long = "max-evidence",
        default_value = "10",
        help_heading = "Expansion"
    )]
    pub max_evidence: usize,
}

#[derive(Parser)]
#[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore who src/features/auth/           # Who knows about this area?
  lore who @asmith                      # What is asmith working on?
  lore who @asmith --reviews            # What review patterns does asmith have?
  lore who --active                     # What discussions need attention?
  lore who --overlap src/features/auth/ # Who else is touching these files?
  lore who --path README.md             # Expert lookup for a root file
  lore who --path Makefile              # Expert lookup for a dotless root file")]
pub struct WhoArgs {
    /// Username or file path (path if contains /)
    pub target: Option<String>,

    /// Force expert mode for a file/directory path.
    /// Root files (README.md, LICENSE, Makefile) are treated as exact matches.
    /// Use a trailing `/` to force directory-prefix matching.
    #[arg(long, help_heading = "Mode", conflicts_with_all = ["active", "overlap", "reviews"])]
    pub path: Option<String>,

    /// Show active unresolved discussions
    #[arg(long, help_heading = "Mode", conflicts_with_all = ["target", "overlap", "reviews", "path"])]
    pub active: bool,

    /// Find users with MRs/notes touching this file path
    #[arg(long, help_heading = "Mode", conflicts_with_all = ["target", "active", "reviews", "path"])]
    pub overlap: Option<String>,

    /// Show review pattern analysis (requires username target)
    #[arg(long, help_heading = "Mode", requires = "target", conflicts_with_all = ["active", "overlap", "path"])]
    pub reviews: bool,

    /// Time window (7d, 2w, 6m, YYYY-MM-DD). Default varies by mode.
    #[arg(long, help_heading = "Filters")]
    pub since: Option<String>,

    /// Scope to a project (supports fuzzy matching)
    #[arg(short = 'p', long, help_heading = "Filters")]
    pub project: Option<String>,

    /// Maximum results per section (1..=500, bounded for output safety)
    #[arg(
        short = 'n',
        long = "limit",
        default_value = "20",
        value_parser = clap::value_parser!(u16).range(1..=500),
        help_heading = "Output"
    )]
    pub limit: u16,

    /// Select output fields (comma-separated, or 'minimal' preset; varies by mode)
    #[arg(long, help_heading = "Output", value_delimiter = ',')]
    pub fields: Option<Vec<String>>,

    /// Show per-MR detail breakdown (expert mode only)
    #[arg(
        long,
        help_heading = "Output",
        overrides_with = "no_detail",
        conflicts_with = "explain_score"
    )]
    pub detail: bool,

    #[arg(long = "no-detail", hide = true, overrides_with = "detail")]
    pub no_detail: bool,

    /// Score as if "now" is this date (ISO 8601 or duration like 30d). Expert mode only.
    #[arg(long = "as-of", help_heading = "Scoring")]
    pub as_of: Option<String>,

    /// Show per-component score breakdown in output. Expert mode only.
    #[arg(long = "explain-score", help_heading = "Scoring")]
    pub explain_score: bool,

    /// Include bot users in results (normally excluded via scoring.excluded_usernames).
    #[arg(long = "include-bots", help_heading = "Scoring")]
    pub include_bots: bool,

    /// Include discussions on closed issues and merged/closed MRs
    #[arg(long, help_heading = "Filters")]
    pub include_closed: bool,

    /// Remove the default time window (query all history). Conflicts with --since.
    #[arg(
        long = "all-history",
        help_heading = "Filters",
        conflicts_with = "since"
    )]
    pub all_history: bool,
}

#[derive(Parser)]
#[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore file-history src/main.rs                  # MRs that touched this file
  lore file-history src/auth/ -p group/repo      # Scoped to project
  lore file-history src/foo.rs --discussions      # Include DiffNote snippets
  lore file-history src/bar.rs --no-follow-renames  # Skip rename chain")]
pub struct FileHistoryArgs {
    /// File path to trace history for
    pub path: String,

    /// Scope to a specific project (fuzzy match)
    #[arg(short = 'p', long, help_heading = "Filters")]
    pub project: Option<String>,

    /// Include discussion snippets from DiffNotes on this file
    #[arg(long, help_heading = "Output")]
    pub discussions: bool,

    /// Disable rename chain resolution
    #[arg(long = "no-follow-renames", help_heading = "Filters")]
    pub no_follow_renames: bool,

    /// Only show merged MRs
    #[arg(long, help_heading = "Filters")]
    pub merged: bool,

    /// Maximum results
    #[arg(
        short = 'n',
        long = "limit",
        default_value = "50",
        help_heading = "Output"
    )]
    pub limit: usize,
}

#[derive(Parser)]
#[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore trace src/main.rs                      # Why was this file changed?
  lore trace src/auth/ -p group/repo          # Scoped to project
  lore trace src/foo.rs --discussions          # Include DiffNote context
  lore trace src/bar.rs:42                    # Line hint (Tier 2 warning)")]
pub struct TraceArgs {
    /// File path to trace (supports :line suffix for future Tier 2)
    pub path: String,

    /// Scope to a specific project (fuzzy match)
    #[arg(short = 'p', long, help_heading = "Filters")]
    pub project: Option<String>,

    /// Include DiffNote discussion snippets
    #[arg(long, help_heading = "Output")]
    pub discussions: bool,

    /// Disable rename chain resolution
    #[arg(long = "no-follow-renames", help_heading = "Filters")]
    pub no_follow_renames: bool,

    /// Maximum trace chains to display
    #[arg(
        short = 'n',
        long = "limit",
        default_value = "20",
        help_heading = "Output"
    )]
    pub limit: usize,
}

#[derive(Parser)]
pub struct CountArgs {
    /// Entity type to count (issues, mrs, discussions, notes, events)
    #[arg(value_parser = ["issues", "mrs", "discussions", "notes", "events"])]
    pub entity: String,

    /// Parent type filter: issue or mr (for discussions/notes)
    #[arg(short = 'f', long = "for", value_parser = ["issue", "mr"])]
    pub for_entity: Option<String>,
}
