use std::ffi::OsString;
use std::fmt::Write;
use std::io::stdout;
use std::process::ExitCode;

use anstream::eprintln;
use anyhow::Result;
use clap::error::{ContextKind, ContextValue};
use clap::{CommandFactory, Parser};
use owo_colors::OwoColorize;
use settings::PipTreeSettings;
use tracing::{debug, instrument};
use uv_cache::{Cache, Refresh};
use uv_cache_info::Timestamp;
use uv_cli::{
    compat::CompatArgs, CacheCommand, CacheNamespace, Cli, Commands, PipCommand, PipNamespace,
    ProjectCommand,
};
use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace};
#[cfg(feature = "self-update")]
use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs};
use uv_fs::CWD;
use uv_requirements::RequirementsSource;
use uv_scripts::Pep723Script;
use uv_settings::{Combine, FilesystemOptions, Options};
use uv_warnings::{warn_user, warn_user_once};
use uv_workspace::{DiscoveryOptions, Workspace};

use crate::commands::{ExitStatus, RunCommand, ToolRunCommand};
use crate::printer::Printer;
use crate::settings::{
    CacheSettings, GlobalSettings, PipCheckSettings, PipCompileSettings, PipFreezeSettings,
    PipInstallSettings, PipListSettings, PipShowSettings, PipSyncSettings, PipUninstallSettings,
};

#[cfg(target_os = "windows")]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;

#[cfg(all(
    not(target_os = "windows"),
    not(target_os = "openbsd"),
    any(
        target_arch = "x86_64",
        target_arch = "aarch64",
        target_arch = "powerpc64"
    )
))]
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;

pub(crate) mod commands;
pub(crate) mod logging;
pub(crate) mod printer;
pub(crate) mod settings;
pub(crate) mod version;

#[instrument(skip_all)]
async fn run(cli: Cli) -> Result<ExitStatus> {
    // Enable flag to pick up warnings generated by workspace loading.
    if !cli.global_args.quiet {
        uv_warnings::enable();
    }

    // Switch directories as early as possible.
    if let Some(directory) = cli.global_args.directory.as_ref() {
        std::env::set_current_dir(directory)?;
    }

    // The `--isolated` argument is deprecated on preview APIs, and warns on non-preview APIs.
    let deprecated_isolated = if cli.global_args.isolated {
        match &*cli.command {
            // Supports `--isolated` as its own argument, so we can't warn either way.
            Commands::Tool(ToolNamespace {
                command: ToolCommand::Uvx(_) | ToolCommand::Run(_),
            }) => false,

            // Supports `--isolated` as its own argument, so we can't warn either way.
            Commands::Project(command) if matches!(**command, ProjectCommand::Run(_)) => false,

            // `--isolated` moved to `--no-workspace`.
            Commands::Project(command) if matches!(**command, ProjectCommand::Init(_)) => {
                warn_user!("The `--isolated` flag is deprecated and has no effect. Instead, use `--no-config` to prevent uv from discovering configuration files or `--no-workspace` to prevent uv from adding the initialized project to the containing workspace.");
                false
            }

            // Preview APIs. Ignore `--isolated` and warn.
            Commands::Project(_) | Commands::Tool(_) | Commands::Python(_) => {
                warn_user!("The `--isolated` flag is deprecated and has no effect. Instead, use `--no-config` to prevent uv from discovering configuration files.");
                false
            }

            // Non-preview APIs. Continue to support `--isolated`, but warn.
            _ => {
                warn_user!("The `--isolated` flag is deprecated. Instead, use `--no-config` to prevent uv from discovering configuration files.");
                true
            }
        }
    } else {
        false
    };

    // Load configuration from the filesystem, prioritizing (in order):
    // 1. The configuration file specified on the command-line.
    // 2. The nearest configuration file (`uv.toml` or `pyproject.toml`) above the workspace root.
    //    If found, this file is combined with the user configuration file.
    // 3. The nearest configuration file (`uv.toml` or `pyproject.toml`) in the directory tree,
    //    starting from the current directory.
    let filesystem = if let Some(config_file) = cli.config_file.as_ref() {
        if config_file
            .file_name()
            .is_some_and(|file_name| file_name == "pyproject.toml")
        {
            warn_user!("The `--config-file` argument expects to receive a `uv.toml` file, not a `pyproject.toml`. If you're trying to run a command from another project, use the `--directory` argument instead.");
        }
        Some(FilesystemOptions::from_file(config_file)?)
    } else if deprecated_isolated || cli.no_config {
        None
    } else if matches!(&*cli.command, Commands::Tool(_)) {
        // For commands that operate at the user-level, ignore local configuration.
        FilesystemOptions::user()?
    } else if let Ok(workspace) = Workspace::discover(&CWD, &DiscoveryOptions::default()).await {
        let project = FilesystemOptions::find(workspace.install_path())?;
        let user = FilesystemOptions::user()?;
        project.combine(user)
    } else {
        let project = FilesystemOptions::find(&*CWD)?;
        let user = FilesystemOptions::user()?;
        project.combine(user)
    };

    // Parse the external command, if necessary.
    let run_command = if let Commands::Project(command) = &*cli.command {
        if let ProjectCommand::Run(uv_cli::RunArgs { command, .. }) = &**command {
            Some(RunCommand::try_from(command)?)
        } else {
            None
        }
    } else {
        None
    };

    // If the target is a PEP 723 script, parse it.
    let script = if let Commands::Project(command) = &*cli.command {
        if let ProjectCommand::Run(uv_cli::RunArgs { .. }) = &**command {
            if let Some(
                RunCommand::PythonScript(script, _) | RunCommand::PythonGuiScript(script, _),
            ) = run_command.as_ref()
            {
                Pep723Script::read(&script).await?
            } else {
                None
            }
        } else if let ProjectCommand::Remove(uv_cli::RemoveArgs {
            script: Some(script),
            ..
        }) = &**command
        {
            Pep723Script::read(&script).await?
        } else {
            None
        }
    } else {
        None
    };

    // If the target is a PEP 723 script, merge the metadata into the filesystem metadata.
    let filesystem = script
        .as_ref()
        .map(|script| &script.metadata)
        .and_then(|metadata| metadata.tool.as_ref())
        .and_then(|tool| tool.uv.as_ref())
        .map(|uv| Options::simple(uv.globals.clone(), uv.top_level.clone()))
        .map(FilesystemOptions::from)
        .combine(filesystem);

    // Resolve the global settings.
    let globals = GlobalSettings::resolve(&cli.global_args, filesystem.as_ref());

    // Resolve the cache settings.
    let cache_settings = CacheSettings::resolve(*cli.cache_args, filesystem.as_ref());

    // Configure the `tracing` crate, which controls internal logging.
    #[cfg(feature = "tracing-durations-export")]
    let (duration_layer, _duration_guard) = logging::setup_duration()?;
    #[cfg(not(feature = "tracing-durations-export"))]
    let duration_layer = None::<tracing_subscriber::layer::Identity>;
    logging::setup_logging(
        match globals.verbose {
            0 => logging::Level::Default,
            1 => logging::Level::Verbose,
            2.. => logging::Level::ExtraVerbose,
        },
        duration_layer,
    )?;

    // Configure the `Printer`, which controls user-facing output in the CLI.
    let printer = if globals.quiet {
        Printer::Quiet
    } else if globals.verbose > 0 {
        Printer::Verbose
    } else if globals.no_progress {
        Printer::NoProgress
    } else {
        Printer::Default
    };

    // Configure the `warn!` macros, which control user-facing warnings in the CLI.
    if globals.quiet {
        uv_warnings::disable();
    } else {
        uv_warnings::enable();
    }

    anstream::ColorChoice::write_global(globals.color.into());

    miette::set_hook(Box::new(|_| {
        Box::new(
            miette::MietteHandlerOpts::new()
                .break_words(false)
                .word_separator(textwrap::WordSeparator::AsciiSpace)
                .word_splitter(textwrap::WordSplitter::NoHyphenation)
                .wrap_lines(std::env::var("UV_NO_WRAP").map(|_| false).unwrap_or(true))
                .build(),
        )
    }))?;

    rayon::ThreadPoolBuilder::new()
        .num_threads(globals.concurrency.installs)
        .build_global()
        .expect("failed to initialize global rayon pool");

    debug!("uv {}", version::version());

    // Write out any resolved settings.
    macro_rules! show_settings {
        ($arg:expr) => {
            if globals.show_settings {
                writeln!(printer.stdout(), "{:#?}", $arg)?;
                return Ok(ExitStatus::Success);
            }
        };
        ($arg:expr, false) => {
            if globals.show_settings {
                writeln!(printer.stdout(), "{:#?}", $arg)?;
            }
        };
    }
    show_settings!(globals, false);
    show_settings!(cache_settings, false);

    // Configure the cache.
    let cache = Cache::from_settings(cache_settings.no_cache, cache_settings.cache_dir)?;

    match *cli.command {
        Commands::Help(args) => commands::help(
            args.command.unwrap_or_default().as_slice(),
            printer,
            args.no_pager,
        ),
        Commands::Pip(PipNamespace {
            command: PipCommand::Compile(args),
        }) => {
            args.compat_args.validate()?;

            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = PipCompileSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(
                args.refresh
                    .combine(Refresh::from(args.settings.reinstall.clone()))
                    .combine(Refresh::from(args.settings.upgrade.clone())),
            );

            let requirements = args
                .src_file
                .into_iter()
                .map(RequirementsSource::from_requirements_file)
                .collect::<Vec<_>>();
            let constraints = args
                .constraint
                .into_iter()
                .map(RequirementsSource::from_constraints_txt)
                .collect::<Vec<_>>();
            let overrides = args
                .r#override
                .into_iter()
                .map(RequirementsSource::from_overrides_txt)
                .collect::<Vec<_>>();
            let build_constraints = args
                .build_constraint
                .into_iter()
                .map(RequirementsSource::from_constraints_txt)
                .collect::<Vec<_>>();

            commands::pip_compile(
                &requirements,
                &constraints,
                &overrides,
                &build_constraints,
                args.constraints_from_workspace,
                args.overrides_from_workspace,
                args.environments,
                args.settings.extras,
                args.settings.output_file.as_deref(),
                args.settings.resolution,
                args.settings.prerelease,
                args.settings.dependency_mode,
                args.settings.upgrade,
                args.settings.generate_hashes,
                args.settings.no_emit_package,
                args.settings.no_strip_extras,
                args.settings.no_strip_markers,
                !args.settings.no_annotate,
                !args.settings.no_header,
                args.settings.custom_compile_command,
                args.settings.emit_index_url,
                args.settings.emit_find_links,
                args.settings.emit_build_options,
                args.settings.emit_marker_expression,
                args.settings.emit_index_annotation,
                args.settings.index_locations,
                args.settings.index_strategy,
                args.settings.keyring_provider,
                args.settings.allow_insecure_host,
                args.settings.config_setting,
                globals.connectivity,
                args.settings.no_build_isolation,
                args.settings.no_build_isolation_package,
                args.settings.build_options,
                args.settings.python_version,
                args.settings.python_platform,
                args.settings.universal,
                args.settings.exclude_newer,
                args.settings.sources,
                args.settings.annotation_style,
                args.settings.link_mode,
                args.settings.python,
                args.settings.system,
                globals.python_preference,
                globals.concurrency,
                globals.native_tls,
                globals.quiet,
                cache,
                printer,
            )
            .await
        }
        Commands::Pip(PipNamespace {
            command: PipCommand::Sync(args),
        }) => {
            args.compat_args.validate()?;

            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = PipSyncSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(
                args.refresh
                    .combine(Refresh::from(args.settings.reinstall.clone()))
                    .combine(Refresh::from(args.settings.upgrade.clone())),
            );

            let requirements = args
                .src_file
                .into_iter()
                .map(RequirementsSource::from_requirements_file)
                .collect::<Vec<_>>();
            let constraints = args
                .constraint
                .into_iter()
                .map(RequirementsSource::from_constraints_txt)
                .collect::<Vec<_>>();
            let build_constraints = args
                .build_constraint
                .into_iter()
                .map(RequirementsSource::from_constraints_txt)
                .collect::<Vec<_>>();

            commands::pip_sync(
                &requirements,
                &constraints,
                &build_constraints,
                args.settings.reinstall,
                args.settings.link_mode,
                args.settings.compile_bytecode,
                args.settings.hash_checking,
                args.settings.index_locations,
                args.settings.index_strategy,
                args.settings.keyring_provider,
                args.settings.allow_insecure_host,
                args.settings.allow_empty_requirements,
                globals.connectivity,
                &args.settings.config_setting,
                args.settings.no_build_isolation,
                args.settings.no_build_isolation_package,
                args.settings.build_options,
                args.settings.python_version,
                args.settings.python_platform,
                args.settings.strict,
                args.settings.exclude_newer,
                args.settings.python,
                args.settings.system,
                args.settings.break_system_packages,
                args.settings.target,
                args.settings.prefix,
                args.settings.sources,
                globals.concurrency,
                globals.native_tls,
                cache,
                args.dry_run,
                printer,
            )
            .await
        }
        Commands::Pip(PipNamespace {
            command: PipCommand::Install(args),
        }) => {
            args.compat_args.validate()?;

            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = PipInstallSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(
                args.refresh
                    .combine(Refresh::from(args.settings.reinstall.clone()))
                    .combine(Refresh::from(args.settings.upgrade.clone())),
            );

            let requirements = args
                .package
                .into_iter()
                .map(RequirementsSource::from_package)
                .chain(args.editable.into_iter().map(RequirementsSource::Editable))
                .chain(
                    args.requirement
                        .into_iter()
                        .map(RequirementsSource::from_requirements_file),
                )
                .collect::<Vec<_>>();
            let constraints = args
                .constraint
                .into_iter()
                .map(RequirementsSource::from_constraints_txt)
                .collect::<Vec<_>>();
            let overrides = args
                .r#override
                .into_iter()
                .map(RequirementsSource::from_overrides_txt)
                .collect::<Vec<_>>();

            let build_constraints = args
                .build_constraint
                .into_iter()
                .map(RequirementsSource::from_overrides_txt)
                .collect::<Vec<_>>();

            commands::pip_install(
                &requirements,
                &constraints,
                &overrides,
                &build_constraints,
                args.constraints_from_workspace,
                args.overrides_from_workspace,
                &args.settings.extras,
                args.settings.resolution,
                args.settings.prerelease,
                args.settings.dependency_mode,
                args.settings.upgrade,
                args.settings.index_locations,
                args.settings.index_strategy,
                args.settings.keyring_provider,
                args.settings.allow_insecure_host,
                args.settings.reinstall,
                args.settings.link_mode,
                args.settings.compile_bytecode,
                args.settings.hash_checking,
                globals.connectivity,
                &args.settings.config_setting,
                args.settings.no_build_isolation,
                args.settings.no_build_isolation_package,
                args.settings.build_options,
                args.settings.python_version,
                args.settings.python_platform,
                args.settings.strict,
                args.settings.exclude_newer,
                args.settings.sources,
                args.settings.python,
                args.settings.system,
                args.settings.break_system_packages,
                args.settings.target,
                args.settings.prefix,
                globals.concurrency,
                globals.native_tls,
                cache,
                args.dry_run,
                printer,
            )
            .await
        }
        Commands::Pip(PipNamespace {
            command: PipCommand::Uninstall(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = PipUninstallSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?;

            let sources = args
                .package
                .into_iter()
                .map(RequirementsSource::from_package)
                .chain(
                    args.requirement
                        .into_iter()
                        .map(RequirementsSource::from_requirements_txt),
                )
                .collect::<Vec<_>>();
            commands::pip_uninstall(
                &sources,
                args.settings.python,
                args.settings.system,
                args.settings.break_system_packages,
                args.settings.target,
                args.settings.prefix,
                cache,
                globals.connectivity,
                globals.native_tls,
                args.settings.keyring_provider,
                args.settings.allow_insecure_host,
                printer,
            )
            .await
        }
        Commands::Pip(PipNamespace {
            command: PipCommand::Freeze(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = PipFreezeSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::pip_freeze(
                args.exclude_editable,
                args.settings.strict,
                args.settings.python.as_deref(),
                args.settings.system,
                &cache,
                printer,
            )
        }
        Commands::Pip(PipNamespace {
            command: PipCommand::List(args),
        }) => {
            args.compat_args.validate()?;

            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = PipListSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::pip_list(
                args.editable,
                &args.exclude,
                &args.format,
                args.settings.strict,
                args.settings.python.as_deref(),
                args.settings.system,
                &cache,
                printer,
            )
        }
        Commands::Pip(PipNamespace {
            command: PipCommand::Show(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = PipShowSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::pip_show(
                args.package,
                args.settings.strict,
                args.settings.python.as_deref(),
                args.settings.system,
                &cache,
                printer,
            )
        }
        Commands::Pip(PipNamespace {
            command: PipCommand::Tree(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = PipTreeSettings::resolve(args, filesystem);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::pip_tree(
                args.show_version_specifiers,
                args.depth,
                args.prune,
                args.package,
                args.no_dedupe,
                args.invert,
                args.shared.strict,
                args.shared.python.as_deref(),
                args.shared.system,
                &cache,
                printer,
            )
        }
        Commands::Pip(PipNamespace {
            command: PipCommand::Check(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = PipCheckSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::pip_check(
                args.settings.python.as_deref(),
                args.settings.system,
                &cache,
                printer,
            )
        }
        Commands::Cache(CacheNamespace {
            command: CacheCommand::Clean(args),
        })
        | Commands::Clean(args) => {
            show_settings!(args);
            commands::cache_clean(&args.package, &cache, printer)
        }
        Commands::Cache(CacheNamespace {
            command: CacheCommand::Prune(args),
        }) => {
            show_settings!(args);
            commands::cache_prune(args.ci, &cache, printer)
        }
        Commands::Cache(CacheNamespace {
            command: CacheCommand::Dir,
        }) => {
            commands::cache_dir(&cache);
            Ok(ExitStatus::Success)
        }
        Commands::Build(args) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::BuildSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(
                args.refresh
                    .combine(Refresh::from(args.settings.upgrade.clone())),
            );

            // Resolve the build constraints.
            let build_constraints = args
                .build_constraint
                .into_iter()
                .map(RequirementsSource::from_constraints_txt)
                .collect::<Vec<_>>();

            commands::build(
                args.src,
                args.package,
                args.out_dir,
                args.sdist,
                args.wheel,
                build_constraints,
                args.hash_checking,
                args.python,
                args.settings,
                cli.no_config,
                globals.python_preference,
                globals.python_downloads,
                globals.connectivity,
                globals.concurrency,
                globals.native_tls,
                &cache,
                printer,
            )
            .await
        }
        Commands::Venv(args) => {
            args.compat_args.validate()?;

            if args.no_system {
                warn_user_once!("The `--no-system` flag has no effect, a system Python interpreter is always used in `uv venv`");
            }

            if args.system {
                warn_user_once!("The `--system` flag has no effect, a system Python interpreter is always used in `uv venv`");
            }

            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::VenvSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?;

            // Since we use ".venv" as the default name, we use "." as the default prompt.
            let prompt = args.prompt.or_else(|| {
                if args.path.is_none() {
                    Some(".".to_string())
                } else {
                    None
                }
            });

            commands::venv(
                args.path,
                args.settings.python.as_deref(),
                globals.python_preference,
                globals.python_downloads,
                args.settings.link_mode,
                &args.settings.index_locations,
                args.settings.index_strategy,
                args.settings.keyring_provider,
                args.settings.allow_insecure_host,
                uv_virtualenv::Prompt::from_args(prompt),
                args.system_site_packages,
                globals.connectivity,
                args.seed,
                args.allow_existing,
                args.settings.exclude_newer,
                globals.concurrency,
                globals.native_tls,
                cli.no_config,
                args.no_project,
                &cache,
                printer,
                args.relocatable,
            )
            .await
        }
        Commands::Project(project) => {
            Box::pin(run_project(
                project,
                run_command,
                script,
                globals,
                cli.no_config,
                filesystem,
                cache,
                printer,
            ))
            .await
        }
        #[cfg(feature = "self-update")]
        Commands::Self_(SelfNamespace {
            command:
                SelfCommand::Update(SelfUpdateArgs {
                    target_version,
                    token,
                }),
        }) => commands::self_update(target_version, token, printer).await,
        Commands::Version { output_format } => {
            commands::version(output_format, &mut stdout())?;
            Ok(ExitStatus::Success)
        }
        Commands::GenerateShellCompletion(args) => {
            args.shell.generate(&mut Cli::command(), &mut stdout());
            Ok(ExitStatus::Success)
        }
        Commands::Tool(ToolNamespace {
            command: run_variant @ (ToolCommand::Uvx(_) | ToolCommand::Run(_)),
        }) => {
            let (args, invocation_source) = match run_variant {
                ToolCommand::Uvx(args) => (args, ToolRunCommand::Uvx),
                ToolCommand::Run(args) => (args, ToolRunCommand::ToolRun),
                // OK guarded by the outer match statement
                _ => unreachable!(),
            };

            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::ToolRunSettings::resolve(args, filesystem, invocation_source);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(
                args.refresh
                    .combine(Refresh::from(args.settings.reinstall.clone()))
                    .combine(Refresh::from(args.settings.upgrade.clone())),
            );

            let requirements = args
                .with
                .into_iter()
                .map(RequirementsSource::from_package)
                .chain(
                    args.with_requirements
                        .into_iter()
                        .map(RequirementsSource::from_requirements_file),
                )
                .collect::<Vec<_>>();

            commands::tool_run(
                args.command,
                args.from,
                &requirements,
                args.show_resolution || globals.verbose > 0,
                args.python,
                args.settings,
                invocation_source,
                args.isolated,
                globals.python_preference,
                globals.python_downloads,
                globals.connectivity,
                globals.concurrency,
                globals.native_tls,
                cache,
                printer,
            )
            .await
        }
        Commands::Tool(ToolNamespace {
            command: ToolCommand::Install(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::ToolInstallSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(
                args.refresh
                    .combine(Refresh::from(args.settings.reinstall.clone()))
                    .combine(Refresh::from(args.settings.upgrade.clone())),
            );

            let requirements = args
                .with
                .into_iter()
                .map(RequirementsSource::from_package)
                .chain(
                    args.with_requirements
                        .into_iter()
                        .map(RequirementsSource::from_requirements_file),
                )
                .collect::<Vec<_>>();

            commands::tool_install(
                args.package,
                args.editable,
                args.from,
                &requirements,
                args.python,
                args.force,
                args.options,
                args.settings,
                globals.python_preference,
                globals.python_downloads,
                globals.connectivity,
                globals.concurrency,
                globals.native_tls,
                cache,
                printer,
            )
            .await
        }
        Commands::Tool(ToolNamespace {
            command: ToolCommand::List(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::ToolListSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::tool_list(
                args.show_paths,
                args.show_version_specifiers,
                &cache,
                printer,
            )
            .await
        }
        Commands::Tool(ToolNamespace {
            command: ToolCommand::Upgrade(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::ToolUpgradeSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(Refresh::All(Timestamp::now()));

            commands::tool_upgrade(
                args.name,
                globals.connectivity,
                args.args,
                args.filesystem,
                globals.concurrency,
                globals.native_tls,
                &cache,
                printer,
            )
            .await
        }
        Commands::Tool(ToolNamespace {
            command: ToolCommand::Uninstall(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::ToolUninstallSettings::resolve(args, filesystem);
            show_settings!(args);

            commands::tool_uninstall(args.name, printer).await
        }
        Commands::Tool(ToolNamespace {
            command: ToolCommand::UpdateShell,
        }) => {
            commands::tool_update_shell(printer).await?;
            Ok(ExitStatus::Success)
        }
        Commands::Tool(ToolNamespace {
            command: ToolCommand::Dir(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::ToolDirSettings::resolve(args, filesystem);
            show_settings!(args);

            commands::tool_dir(args.bin, globals.preview)?;
            Ok(ExitStatus::Success)
        }
        Commands::Python(PythonNamespace {
            command: PythonCommand::List(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::PythonListSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::python_list(
                args.kinds,
                args.all_versions,
                args.all_platforms,
                globals.python_preference,
                globals.python_downloads,
                &cache,
                printer,
            )
            .await
        }
        Commands::Python(PythonNamespace {
            command: PythonCommand::Install(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::PythonInstallSettings::resolve(args, filesystem);
            show_settings!(args);

            commands::python_install(
                args.targets,
                args.reinstall,
                globals.python_downloads,
                globals.native_tls,
                globals.connectivity,
                cli.no_config,
                printer,
            )
            .await
        }
        Commands::Python(PythonNamespace {
            command: PythonCommand::Uninstall(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::PythonUninstallSettings::resolve(args, filesystem);
            show_settings!(args);

            commands::python_uninstall(args.targets, args.all, printer).await
        }
        Commands::Python(PythonNamespace {
            command: PythonCommand::Find(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::PythonFindSettings::resolve(args, filesystem);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::python_find(
                args.request,
                args.no_project,
                cli.no_config,
                args.system,
                globals.python_preference,
                &cache,
            )
            .await
        }
        Commands::Python(PythonNamespace {
            command: PythonCommand::Pin(args),
        }) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::PythonPinSettings::resolve(args, filesystem);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::python_pin(
                args.request,
                args.resolved,
                globals.python_preference,
                args.no_project,
                &cache,
                printer,
            )
            .await
        }
        Commands::Python(PythonNamespace {
            command: PythonCommand::Dir,
        }) => {
            commands::python_dir()?;
            Ok(ExitStatus::Success)
        }
    }
}

/// Run a [`ProjectCommand`].
async fn run_project(
    project_command: Box<ProjectCommand>,
    command: Option<RunCommand>,
    script: Option<Pep723Script>,
    globals: GlobalSettings,
    // TODO(zanieb): Determine a better story for passing `no_config` in here
    no_config: bool,
    filesystem: Option<FilesystemOptions>,
    cache: Cache,
    printer: Printer,
) -> Result<ExitStatus> {
    // Write out any resolved settings.
    macro_rules! show_settings {
        ($arg:expr) => {
            if globals.show_settings {
                writeln!(printer.stdout(), "{:#?}", $arg)?;
                return Ok(ExitStatus::Success);
            }
        };
        ($arg:expr, false) => {
            if globals.show_settings {#
                writeln!(printer.stdout(), "{:#?}", $arg)?;
            }
        };
    }

    match *project_command {
        ProjectCommand::Init(args) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::InitSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::init(
                args.path,
                args.name,
                args.package,
                args.kind,
                args.no_readme,
                args.no_pin_python,
                args.python,
                args.no_workspace,
                globals.python_preference,
                globals.python_downloads,
                globals.connectivity,
                globals.native_tls,
                &cache,
                printer,
            )
            .await
        }
        ProjectCommand::Run(args) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::RunSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(
                args.refresh
                    .combine(Refresh::from(args.settings.reinstall.clone()))
                    .combine(Refresh::from(args.settings.upgrade.clone())),
            );

            let requirements = args
                .with
                .into_iter()
                .map(RequirementsSource::from_package)
                .chain(
                    args.with_editable
                        .into_iter()
                        .map(RequirementsSource::Editable),
                )
                .chain(
                    args.with_requirements
                        .into_iter()
                        .map(RequirementsSource::from_requirements_file),
                )
                .collect::<Vec<_>>();

            // Given `ProjectCommand::Run`, we always expect a `RunCommand` to be present.
            let command = command.expect("run command is required");

            Box::pin(commands::run(
                script,
                command,
                requirements,
                args.show_resolution || globals.verbose > 0,
                args.locked,
                args.frozen,
                args.no_sync,
                args.isolated,
                args.package,
                args.no_project,
                no_config,
                args.extras,
                args.dev,
                args.python,
                args.settings,
                globals.python_preference,
                globals.python_downloads,
                globals.connectivity,
                globals.concurrency,
                globals.native_tls,
                &cache,
                printer,
            ))
            .await
        }
        ProjectCommand::Sync(args) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::SyncSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(
                args.refresh
                    .combine(Refresh::from(args.settings.reinstall.clone()))
                    .combine(Refresh::from(args.settings.upgrade.clone())),
            );

            commands::sync(
                args.locked,
                args.frozen,
                args.package,
                args.extras,
                args.dev,
                args.install_options,
                args.modifications,
                args.python,
                globals.python_preference,
                globals.python_downloads,
                args.settings,
                globals.connectivity,
                globals.concurrency,
                globals.native_tls,
                &cache,
                printer,
            )
            .await
        }
        ProjectCommand::Lock(args) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::LockSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(
                args.refresh
                    .combine(Refresh::from(args.settings.upgrade.clone())),
            );

            commands::lock(
                args.locked,
                args.frozen,
                args.python,
                args.settings,
                globals.python_preference,
                globals.python_downloads,
                globals.connectivity,
                globals.concurrency,
                globals.native_tls,
                &cache,
                printer,
            )
            .await
        }
        ProjectCommand::Add(args) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::AddSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(
                args.refresh
                    .combine(Refresh::from(args.settings.reinstall.clone()))
                    .combine(Refresh::from(args.settings.upgrade.clone())),
            );

            let requirements = args
                .packages
                .into_iter()
                .map(RequirementsSource::Package)
                .chain(
                    args.requirements
                        .into_iter()
                        .map(RequirementsSource::from_requirements_file),
                )
                .collect::<Vec<_>>();

            Box::pin(commands::add(
                args.locked,
                args.frozen,
                args.no_sync,
                requirements,
                args.editable,
                args.dependency_type,
                args.raw_sources,
                args.rev,
                args.tag,
                args.branch,
                args.extras,
                args.package,
                args.python,
                args.settings,
                args.script,
                globals.python_preference,
                globals.python_downloads,
                globals.connectivity,
                globals.concurrency,
                globals.native_tls,
                &cache,
                printer,
            ))
            .await
        }
        ProjectCommand::Remove(args) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::RemoveSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?.with_refresh(
                args.refresh
                    .combine(Refresh::from(args.settings.reinstall.clone()))
                    .combine(Refresh::from(args.settings.upgrade.clone())),
            );

            commands::remove(
                args.locked,
                args.frozen,
                args.no_sync,
                args.packages,
                args.dependency_type,
                args.package,
                args.python,
                args.settings,
                script,
                globals.python_preference,
                globals.python_downloads,
                globals.connectivity,
                globals.concurrency,
                globals.native_tls,
                &cache,
                printer,
            )
            .await
        }
        ProjectCommand::Tree(args) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::TreeSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::tree(
                args.locked,
                args.frozen,
                args.universal,
                args.depth,
                args.prune,
                args.package,
                args.no_dedupe,
                args.invert,
                args.python_version,
                args.python_platform,
                args.python,
                args.resolver,
                globals.python_preference,
                globals.python_downloads,
                globals.connectivity,
                globals.concurrency,
                globals.native_tls,
                &cache,
                printer,
            )
            .await
        }
        ProjectCommand::Export(args) => {
            // Resolve the settings from the command-line arguments and workspace configuration.
            let args = settings::ExportSettings::resolve(args, filesystem);
            show_settings!(args);

            // Initialize the cache.
            let cache = cache.init()?;

            commands::export(
                args.format,
                args.package,
                args.hashes,
                args.install_options,
                args.output_file,
                args.extras,
                args.dev,
                args.locked,
                args.frozen,
                args.python,
                args.settings,
                globals.python_preference,
                globals.python_downloads,
                globals.connectivity,
                globals.concurrency,
                globals.native_tls,
                globals.quiet,
                &cache,
                printer,
            )
            .await
        }
    }
}

/// The main entry point for a uv invocation.
///
/// WARNING: This entry point is not recommended for external consumption, the
/// uv binary interface is the official public API. When using this entry
/// point, uv assumes it is running in a process it controls and that the
/// entire process lifetime is managed by uv. Unexpected behavior may be
/// encountered if this entry pointis called multiple times in a single process.
pub fn main<I, T>(args: I) -> ExitCode
where
    I: IntoIterator<Item = T>,
    T: Into<OsString> + Clone,
{
    // `std::env::args` is not `Send` so we parse before passing to our runtime
    // https://github.com/rust-lang/rust/pull/48005
    let cli = match Cli::try_parse_from(args) {
        Ok(cli) => cli,
        Err(mut err) => {
            if let Some(ContextValue::String(subcommand)) = err.get(ContextKind::InvalidSubcommand)
            {
                match subcommand.as_str() {
                    "compile" | "lock" => {
                        err.insert(
                            ContextKind::SuggestedSubcommand,
                            ContextValue::String("uv pip compile".to_string()),
                        );
                    }
                    "sync" => {
                        err.insert(
                            ContextKind::SuggestedSubcommand,
                            ContextValue::String("uv pip sync".to_string()),
                        );
                    }
                    "install" | "add" => {
                        err.insert(
                            ContextKind::SuggestedSubcommand,
                            ContextValue::String("uv pip install".to_string()),
                        );
                    }
                    "uninstall" | "remove" => {
                        err.insert(
                            ContextKind::SuggestedSubcommand,
                            ContextValue::String("uv pip uninstall".to_string()),
                        );
                    }
                    "freeze" => {
                        err.insert(
                            ContextKind::SuggestedSubcommand,
                            ContextValue::String("uv pip freeze".to_string()),
                        );
                    }
                    "list" => {
                        err.insert(
                            ContextKind::SuggestedSubcommand,
                            ContextValue::String("uv pip list".to_string()),
                        );
                    }
                    "show" => {
                        err.insert(
                            ContextKind::SuggestedSubcommand,
                            ContextValue::String("uv pip show".to_string()),
                        );
                    }
                    "tree" => {
                        err.insert(
                            ContextKind::SuggestedSubcommand,
                            ContextValue::String("uv pip tree".to_string()),
                        );
                    }
                    _ => {}
                }
            }
            err.exit()
        }
    };

    // Windows has a default stack size of 1MB, which is lower than the linux and mac default.
    // https://learn.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=msvc-170
    // We support increasing the stack size to avoid stack overflows in debug mode on Windows. In
    // addition, we box types and futures in various places. This includes the `Box::pin(run())`
    // here, which prevents the large (non-send) main future alone from overflowing the stack.
    let result = if let Ok(stack_size) = std::env::var("UV_STACK_SIZE") {
        let stack_size = stack_size.parse().expect("Invalid stack size");
        let tokio_main = move || {
            let runtime = tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .thread_stack_size(stack_size)
                .build()
                .expect("Failed building the Runtime");
            // Box the large main future to avoid stack overflows.
            let result = runtime.block_on(Box::pin(run(cli)));
            // Avoid waiting for pending tasks to complete.
            //
            // The resolver may have kicked off HTTP requests during resolution that
            // turned out to be unnecessary. Waiting for those to complete can cause
            // the CLI to hang before exiting.
            runtime.shutdown_background();
            result
        };
        std::thread::Builder::new()
            .stack_size(stack_size)
            .spawn(tokio_main)
            .expect("Tokio executor failed, was there a panic?")
            .join()
            .expect("Tokio executor failed, was there a panic?")
    } else {
        let runtime = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .expect("Failed building the Runtime");
        // Box the large main future to avoid stack overflows.
        let result = runtime.block_on(Box::pin(run(cli)));
        runtime.shutdown_background();
        result
    };

    match result {
        Ok(code) => code.into(),
        Err(err) => {
            let mut causes = err.chain();
            eprintln!("{}: {}", "error".red().bold(), causes.next().unwrap());
            for err in causes {
                eprintln!("  {}: {}", "Caused by".red().bold(), err);
            }
            ExitStatus::Error.into()
        }
    }
}
