//! VT Code - Research-preview Rust coding agent
//!
//! Thin binary entry point that delegates to modular CLI handlers.

use anyhow::{Context, Result};

#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
use clap::FromArgMatches;
use colorchoice::ColorChoice as GlobalColorChoice;
use vtcode_commons::color_policy;
#[cfg(target_os = "macos")]
use vtcode_commons::env_lock;
use vtcode_core::cli::args::Cli;
use vtcode_core::config::api_keys::load_dotenv;
use vtcode_tui::panic_hook;

mod agent;
mod cli; // local CLI handlers in src/cli // agent runloops (single-agent only)
mod codex_app_server;
mod main_helpers;
mod startup;
mod updater;

use main_helpers::{
    build_augmented_cli_command, configure_debug_session_routing,
    configure_runtime_relaunch_context, debug_runtime_flag_enabled,
    initialize_default_error_tracing, initialize_tracing, initialize_tracing_from_config,
    perform_queued_runtime_relaunch, resolve_runtime_color_policy, resolve_startup_context,
};

struct PreparedRun {
    args: Cli,
    startup: startup::StartupContext,
    print_mode: Option<String>,
}

enum BootstrapOutcome {
    ExitEarly,
    Ready(Box<PreparedRun>),
}

fn main() -> std::process::ExitCode {
    // Apply process hardening before any other operations.
    // This disables core dumps, caps RLIMIT_STACK (defense-in-depth complement
    // to Rust's built-in stack clash protection — see rustc exploit-mitigations
    // docs), removes dangerous env vars, and prevents ptrace attach.
    vtcode_process_hardening::pre_main_hardening();

    // The hardening layer caps RLIMIT_STACK at 8 MiB (only when unlimited).
    // The spawned thread below uses 16 MiB — this is safe because
    // RLIMIT_STACK only constrains the main thread's stack on Linux/macOS;
    // thread stacks allocated via pthread_attr_setstacksize come from the
    // heap and are unaffected by the rlimit.
    const MAIN_THREAD_STACK_BYTES: usize = 16 * 1024 * 1024;

    let handle = match std::thread::Builder::new()
        .name("vtcode-main".to_string())
        .stack_size(MAIN_THREAD_STACK_BYTES)
        .spawn(|| -> Result<()> {
            match bootstrap_main()? {
                BootstrapOutcome::ExitEarly => Ok(()),
                BootstrapOutcome::Ready(prepared) => {
                    let runtime = tokio::runtime::Builder::new_multi_thread()
                        .enable_all()
                        .build()
                        .context("failed to build Tokio runtime")?;
                    runtime.block_on(run(*prepared))
                }
            }
        }) {
        Ok(handle) => handle,
        Err(err) => {
            eprintln!("Error: failed to spawn vtcode main thread: {err}");
            return std::process::ExitCode::FAILURE;
        }
    };

    match handle.join() {
        Ok(Ok(_)) => std::process::ExitCode::SUCCESS,
        Ok(Err(err)) => {
            panic_hook::print_error_report(err);
            std::process::ExitCode::FAILURE
        }
        Err(_) => {
            eprintln!("Error: vtcode main thread panicked");
            std::process::ExitCode::FAILURE
        }
    }
}

#[cfg(target_os = "macos")]
fn remove_runtime_env_var(key: &str) {
    // Delegates to the process-wide env lock so this mutation cannot race with any
    // other VT Code env mutator, even though it runs on the dedicated main thread
    // before any worker threads exist.
    env_lock::remove_var(key);
}

fn bootstrap_main() -> Result<BootstrapOutcome> {
    let launch_argv = std::env::args_os().collect::<Vec<_>>();
    let launch_cwd = std::env::current_dir().context("failed to resolve current directory")?;
    configure_runtime_relaunch_context(launch_argv, launch_cwd);

    // Suppress macOS malloc warnings that appear as stderr output
    // IMPORTANT: Remove the variables rather than setting to "0"
    // Setting to "0" triggers macOS to output "can't turn off malloc stack logging"
    // which corrupts the TUI display
    #[cfg(target_os = "macos")]
    {
        for key in [
            "MallocStackLogging",
            "MallocStackLoggingDirectory",
            "MallocScribble",
            "MallocGuardEdges",
            "MallocCheckHeapStart",
            "MallocCheckHeapEach",
            "MallocCheckHeapAbort",
            "MallocCheckHeapSleep",
            "MallocErrorAbort",
            "MallocCorruptionAbort",
            "MallocStackLoggingNoCompact",
            "MallocDoNotProtectSentinel",
            "MallocQuiet",
        ] {
            remove_runtime_env_var(key);
        }
    }

    panic_hook::set_app_metadata(
        env!("CARGO_PKG_NAME"),
        env!("CARGO_PKG_VERSION"),
        env!("CARGO_PKG_AUTHORS"),
        Some(env!("CARGO_PKG_REPOSITORY")),
    );
    panic_hook::init_panic_hook();

    if vtcode_core::maybe_run_zsh_exec_wrapper_mode()? {
        return Ok(BootstrapOutcome::ExitEarly);
    }

    let matches = build_augmented_cli_command().get_matches();
    let args = Cli::from_arg_matches(&matches)?;
    panic_hook::set_debug_mode(args.debug);
    let color_eyre_enabled = debug_runtime_flag_enabled(args.debug, "VTCODE_COLOR_EYRE");
    panic_hook::set_color_eyre_enabled(color_eyre_enabled);
    let tui_log_capture_enabled = debug_runtime_flag_enabled(args.debug, "VTCODE_TUI_LOGS");
    vtcode_tui::log::set_tui_log_capture_enabled(tui_log_capture_enabled);

    // Load .env (non-fatal if missing)
    if let Err(_err) = load_dotenv()
        && !args.quiet
    {}

    if args.print.is_some() && args.command.is_some() {
        anyhow::bail!(
            "The --print/-p flag cannot be combined with subcommands. Use print mode without a subcommand."
        );
    }

    let print_mode = args.print.clone();
    let color_policy = resolve_runtime_color_policy(&args);
    color_policy::set_color_output_policy(color_policy);

    args.color.write_global();
    if !color_policy.enabled {
        GlobalColorChoice::Never.write_global();
    }

    let startup_runtime = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .context("failed to build startup Tokio runtime")?;
    let startup = startup_runtime.block_on(resolve_startup_context(&args))?;

    Ok(BootstrapOutcome::Ready(Box::new(PreparedRun {
        args,
        startup,
        print_mode,
    })))
}

async fn run(prepared: PreparedRun) -> Result<()> {
    let PreparedRun {
        args,
        startup,
        print_mode,
    } = prepared;

    configure_debug_session_routing(&args, &startup, &print_mode).await;

    // Initialize tracing based on both RUST_LOG env var and config
    let env_tracing_initialized = initialize_tracing().await.unwrap_or_default();

    if startup.config.debug.enable_tracing
        && !env_tracing_initialized
        && let Err(err) = initialize_tracing_from_config(&startup.config)
    {
        tracing::warn!(error = %err, "failed to initialize tracing from config");
    } else if !env_tracing_initialized && !startup.config.debug.enable_tracing {
        // Always collect ERROR-level logs into the session archive for post-mortem debugging
        if let Err(err) = initialize_default_error_tracing() {
            eprintln!("warning: failed to initialize default error tracing: {err}");
        }
    }

    // Sync global diagnostics flag so TuiLogLayer respects ui.show_diagnostics_in_transcript
    panic_hook::set_show_diagnostics(startup.config.ui.show_diagnostics_in_transcript);

    let dispatch_result = cli::dispatch(&args, &startup, print_mode).await;
    perform_queued_runtime_relaunch();
    vtcode_core::utils::trace_writer::flush_trace_log();
    dispatch_result?;

    Ok(())
}
