pub mod args;
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)
  LORE_ICONS         Override icon set: nerd, unicode, or ascii
  NERD_FONTS         Enable Nerd Font icons when set to a 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",
        after_help = "\x1b[1mExamples:\x1b[0m
  lore status                             # Show last sync times per project
  lore --robot status                     # JSON output for automation"
    )]
    Status,

    /// Verify GitLab authentication
    #[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore auth                               # Verify token and show user info
  lore --robot auth                       # JSON output for automation")]
    Auth,

    /// Check environment health
    #[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore doctor                             # Check config, token, database, Ollama
  lore --robot doctor                     # JSON output for automation")]
    Doctor,

    /// Show version information
    Version,

    /// Initialize configuration and database
    #[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore init                                                   # Interactive setup
  lore init --refresh                                         # Register projects from existing config
  lore init --force                                           # Overwrite existing config
  lore --robot init --gitlab-url https://gitlab.com \\
    --token-env-var GITLAB_TOKEN --projects group/repo        # Non-interactive setup")]
    Init {
        /// Re-read config and register any new projects in the database
        #[arg(long, conflicts_with = "force")]
        refresh: bool,

        /// 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>,
    },

    /// Back up local database (not yet implemented)
    #[command(hide = true)]
    Backup,

    /// Reset local database (not yet implemented)
    #[command(hide = true)]
    Reset {
        /// Skip confirmation prompt
        #[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
    #[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore migrate                            # Apply pending migrations
  lore --robot migrate                    # JSON output for automation")]
    Migrate,

    /// Quick health check: config, database, schema version
    #[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore health                             # Quick pre-flight check (exit 0 = healthy)
  lore --robot health                     # JSON output for automation")]
    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),

    /// Personal work dashboard: open issues, authored/reviewing MRs, activity
    Me(MeArgs),

    /// 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),

    /// Auto-generate a structured narrative of an issue or MR
    #[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore explain issues 42                  # Narrative for issue #42
  lore explain mrs 99 -p group/repo       # Narrative for MR !99 in specific project
  lore -J explain issues 42               # JSON output for automation
  lore explain issues 42 --sections key_decisions,open_threads  # Specific sections only
  lore explain issues 42 --since 30d      # Narrative scoped to last 30 days
  lore explain issues 42 --no-timeline    # Skip timeline (faster)")]
    Explain {
        /// Entity type: "issues" or "mrs" (singular forms also accepted)
        #[arg(value_parser = ["issues", "mrs", "issue", "mr"])]
        entity_type: String,

        /// Entity IID
        iid: i64,

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

        /// Select specific sections (comma-separated)
        /// Valid: entity, description, key_decisions, activity, open_threads, related, timeline
        #[arg(long, value_delimiter = ',', help_heading = "Output")]
        sections: Option<Vec<String>>,

        /// Skip timeline excerpt (faster execution)
        #[arg(long, help_heading = "Output")]
        no_timeline: bool,

        /// Maximum key decisions to include
        #[arg(long, default_value = "10", help_heading = "Output")]
        max_decisions: usize,

        /// Time scope for events/notes (e.g. 7d, 2w, 1m, or YYYY-MM-DD)
        #[arg(long, help_heading = "Filters")]
        since: Option<String>,
    },

    /// Detect discussion divergence from original intent
    #[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore drift issues 42                    # Check drift on issue #42
  lore drift issues 42 --threshold 0.3    # Custom similarity threshold
  lore --robot drift issues 42 -p group/repo  # JSON output, scoped to project")]
    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>,
    },

    /// Find semantically related entities via vector search
    #[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore related issues 42                  # Find entities related to issue #42
  lore related mrs 99 -p group/repo       # Related to MR #99 in specific project
  lore related 'authentication flow'      # Find entities matching free text query
  lore --robot related issues 42 -n 5     # JSON output, limit 5 results")]
    Related {
        /// Entity type (issues, mrs) or free text query
        query_or_type: String,

        /// Entity IID (required when first arg is entity type)
        iid: Option<i64>,

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

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

    /// Manage cron-based automatic syncing
    #[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore cron install                       # Install cron job (every 8 minutes)
  lore cron install --interval 15         # Custom interval
  lore cron status                        # Check if cron is installed
  lore cron uninstall                     # Remove cron job")]
    Cron(CronArgs),

    /// Manage stored GitLab token
    #[command(after_help = "\x1b[1mExamples:\x1b[0m
  lore token set                          # Interactive token entry + validation
  lore token set --token glpat-xxx        # Non-interactive token storage
  echo glpat-xxx | lore token set         # Pipe token from stdin
  lore token show                         # Show token (masked)
  lore token show --unmask                # Show full token")]
    Token(TokenArgs),

    #[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, name = "auth-test")]
    AuthTest,

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

pub use args::{
    CountArgs, CronAction, CronArgs, EmbedArgs, FileHistoryArgs, GenerateDocsArgs, IngestArgs,
    IssuesArgs, MeArgs, MrsArgs, NotesArgs, SearchArgs, StatsArgs, SyncArgs, TimelineArgs,
    TokenAction, TokenArgs, TraceArgs, WhoArgs,
};
