//! Bivvy CLI entry point.

use std::process::ExitCode;

use bivvy::cli::{Cli, CommandDispatcher, Commands};
use bivvy::shell::is_ci;
use bivvy::ui::{create_ui, OutputMode};
use bivvy::updates::{
    apply_staged_update, is_notification_suppressed, perform_background_update,
    should_spawn_background_update, show_update_notification, spawn_background_update,
};
use clap::Parser;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};

/// Initialize the tracing subscriber for logging.
///
/// Log level is controlled by:
/// 1. `--debug` flag sets level to DEBUG
/// 2. `RUST_LOG` environment variable (if set)
/// 3. Default is INFO
fn init_tracing(debug: bool) {
    let filter = if debug {
        EnvFilter::new("bivvy=debug")
    } else {
        EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("bivvy=info"))
    };

    tracing_subscriber::registry()
        .with(fmt::layer().with_target(false))
        .with(filter)
        .init();
}

fn main() -> ExitCode {
    // Safety net: claim the foreground process group for terminal I/O.
    //
    // When `cargo run` (or other launchers) spawn bivvy, the child may
    // not be the terminal's foreground process group. We ignore SIGTTOU
    // (so tcsetpgrp doesn't suspend us) and claim the foreground.
    //
    // The primary defense against SIGTTOU is using `-lc` (not `-lic`)
    // for child shell commands — see shell/command.rs. Without `-i`,
    // child shells don't set up job control and don't steal the
    // foreground group. These signal ignores and tcsetpgrp call are a
    // safety net for edge cases (e.g., `cargo run` wrappers).
    #[cfg(unix)]
    unsafe {
        libc::signal(libc::SIGTTOU, libc::SIG_IGN);
        libc::signal(libc::SIGTTIN, libc::SIG_IGN);

        let tty_fd = libc::open(c"/dev/tty".as_ptr(), libc::O_RDWR);
        if tty_fd >= 0 {
            libc::tcsetpgrp(tty_fd, libc::getpgrp());
            libc::close(tty_fd);
        }
    }

    // Background self-update process: when invoked with this env var,
    // perform the update check/download and exit immediately.
    if std::env::var("BIVVY_SELF_UPDATE_BG").is_ok() {
        let _ = perform_background_update();
        return ExitCode::SUCCESS;
    }

    let cli = Cli::parse();
    init_tracing(cli.debug);

    // Ensure ~/.bivvy/config.yml exists (first-run bootstrap).
    bivvy::config::ensure_global_config();

    tracing::debug!("Bivvy starting with args: {:?}", cli);

    // Determine output mode
    let output_mode = if cli.quiet {
        OutputMode::Quiet
    } else if cli.verbose {
        OutputMode::Verbose
    } else {
        OutputMode::Normal
    };

    // Handle --no-color
    if cli.no_color {
        std::env::set_var("NO_COLOR", "1");
    }

    // Determine project root
    let project_root = cli
        .project
        .as_ref()
        .cloned()
        .unwrap_or_else(|| std::env::current_dir().unwrap_or_default());

    // Check if non-interactive (CI mode or explicit flag)
    let is_interactive = match &cli.command {
        Some(Commands::Run(args)) => !args.non_interactive && !args.ci && !is_ci(),
        _ => !is_ci(),
    };

    // Create UI
    let mut ui = create_ui(is_interactive, output_mode);

    // Apply any previously staged update (manual installs only).
    // This replaces the current binary before running the command,
    // so the user gets the new version immediately.
    match apply_staged_update() {
        Ok(Some(version)) => {
            tracing::info!("Auto-updated to v{}", version);
            ui.message(&format!("bivvy has been auto-updated to v{}", version));
        }
        Ok(None) => {} // No staged update
        Err(e) => {
            tracing::warn!("Failed to apply staged update: {}", e);
        }
    }

    // Dispatch command
    let dispatcher = CommandDispatcher::new(project_root);

    let exit_code = match dispatcher.dispatch(&cli, ui.as_mut()) {
        Ok(result) => {
            // Show update notification after successful runs
            if result.success && is_interactive && !is_notification_suppressed() {
                show_update_notification(ui.as_mut());
            }
            result.exit_code as u8
        }
        Err(e) => {
            ui.error(&format!("Error: {}", e));
            1
        }
    };

    // Spawn background update check after command completes.
    // This runs in a detached process so it doesn't delay the CLI exit.
    if should_spawn_background_update(is_interactive, is_ci()) {
        if let Err(e) = spawn_background_update() {
            tracing::debug!("Failed to spawn background update: {}", e);
        }
    }

    ExitCode::from(exit_code)
}
