/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fmt;

pub(crate) struct SystemInfo {
    pub(crate) username: Option<String>,
    pub(crate) hostname: Option<String>,
    pub(crate) os: String,
    pub(crate) os_version: Option<String>,
}

impl fmt::Display for SystemInfo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "username: {}
hostname: {}
os: {}
os_version: {}
",
            self.username.as_deref().unwrap_or(""),
            self.hostname.as_deref().unwrap_or(""),
            self.os,
            self.os_version.as_deref().unwrap_or(""),
        )
    }
}

pub(crate) async fn get() -> anyhow::Result<SystemInfo> {
    let info = buck2_events::metadata::system_info();
    let output = SystemInfo {
        username: info.username,
        hostname: info.hostname,
        os: info.os,
        os_version: info.os_version,
    };
    Ok(output)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io::Cursor;

use anyhow::Context;
use buck2_client_ctx::daemon::client::connect::BootstrapBuckdClient;
use buck2_client_ctx::manifold;
use buck2_common::result::SharedResult;
use buck2_util::process::async_background_command;

pub async fn upload_thread_dump(
    buckd: &SharedResult<BootstrapBuckdClient>,
    manifold_id: &String,
) -> anyhow::Result<String> {
    let buckd_pid = buckd.clone()?.pid();
    let command = async_background_command("lldb")
        .arg("-p")
        .arg(buckd_pid.to_string())
        .arg("--batch")
        .arg("-o")
        .arg("thread backtrace all")
        .stdin(std::process::Stdio::null())
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .kill_on_drop(true)
        .spawn()
        .context("Failed to spawn lldb command")?
        .wait_with_output()
        .await?;

    if command.status.success() {
        let manifold_bucket = manifold::Bucket::RAGE_DUMPS;
        let manifold_filename = format!("{}_thread_dump", manifold_id);
        let mut stdout = command.stdout;
        manifold::Upload::new(manifold_bucket, &manifold_filename)
            .with_default_ttl()
            .from_async_read(&mut Cursor::new(&mut stdout))?
            .spawn()
            .await?;
        Ok(format!(
            "{}/flat/{}",
            manifold_bucket.name, manifold_filename
        ))
    } else {
        let stderr = &command.stderr;
        Ok(String::from_utf8_lossy(stderr).to_string())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

#[derive(Clone)]
pub struct Argv {
    pub argv: Vec<String>,
    pub expanded_argv: Vec<String>,
}

#[derive(Clone)]
pub struct SanitizedArgv {
    pub argv: Vec<String>,
    pub expanded_argv: Vec<String>,
}

impl Argv {
    pub fn no_need_to_sanitize(self) -> SanitizedArgv {
        let Argv {
            argv,
            expanded_argv,
        } = self;
        SanitizedArgv {
            argv,
            expanded_argv,
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::collections::HashMap;
use std::io::ErrorKind;
use std::time::Duration;

use anyhow::Context;
use buck2_common::client_utils;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::paths::file_name::FileName;
use fs4::FileExt;
use serde::Deserialize;
use serde::Serialize;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;

#[derive(Serialize, Deserialize)]
struct BuildCount(HashMap<String, u64>);

impl BuildCount {
    pub fn update(&mut self, target_patterns: &[buck2_data::TargetPattern]) {
        for target in target_patterns.iter() {
            match self.0.get_mut(&target.value) {
                Some(counter) => {
                    *counter += 1;
                }
                None => {
                    self.0.insert(target.value.clone(), 1);
                }
            }
        }
    }

    pub fn min_count(&self) -> u64 {
        self.0.values().min().copied().unwrap_or_default()
    }
}

/// BuildCountManager keeps track of how many times each target has been built since rebase.
/// This helps understand how much the performance differs between first and incremental builds.
pub struct BuildCountManager {
    base_dir: AbsNormPathBuf,
}

impl BuildCountManager {
    const LOCK_FILE_NAME: &'static str = "build_count.lock";
    const LOCK_TIMEOUT: Duration = Duration::from_millis(2000);

    pub fn new(base_dir: AbsNormPathBuf) -> Self {
        Self { base_dir }
    }

    async fn ensure_dir(&self) -> anyhow::Result<()> {
        tokio::fs::create_dir_all(&self.base_dir)
            .await
            .with_context(|| {
                format!("Error creating build count directory: `{}`", self.base_dir)
            })?;
        Ok(())
    }

    async fn read(&self, file_name: &FileName) -> anyhow::Result<BuildCount> {
        match tokio::fs::File::open(self.base_dir.join(file_name)).await {
            Ok(mut file) => {
                let mut buffer = String::new();
                file.read_to_string(&mut buffer).await?;
                Ok(serde_json::from_str(&buffer)?)
            }
            Err(e) => match e.kind() {
                ErrorKind::NotFound => {
                    // it is normal after rebase, clean, etc.
                    Ok(BuildCount(HashMap::new()))
                }
                _ => Err(e.into()),
            },
        }
    }

    async fn write(&self, build_count: &BuildCount, file_name: &FileName) -> anyhow::Result<()> {
        self.ensure_dir().await?;
        let mut file = tokio::fs::File::create(self.base_dir.join(file_name)).await?;
        file.write_all(&serde_json::to_vec(build_count)?).await?;
        file.sync_data().await?;
        Ok(())
    }

    async fn lock_with_timeout(&mut self, timeout: Duration) -> anyhow::Result<FileLockGuard> {
        self.ensure_dir().await?;
        let file = std::fs::File::create(self.base_dir.join(FileName::new(Self::LOCK_FILE_NAME)?))?;
        client_utils::retrying(
            Duration::from_millis(5),
            Duration::from_millis(100),
            timeout,
            async || anyhow::Ok(file.try_lock_exclusive()?),
        )
        .await?;
        Ok(FileLockGuard { file })
    }

    pub async fn min_build_count(
        &mut self,
        merge_base: &str,
        target_patterns: &[buck2_data::TargetPattern],
    ) -> anyhow::Result<u64> {
        let file_name = FileName::new(merge_base)?;
        let _guard = self.lock_with_timeout(Self::LOCK_TIMEOUT).await?;
        let mut build_count = self.read(file_name).await?;
        build_count.update(target_patterns);
        self.write(&build_count, file_name).await?;
        Ok(build_count.min_count())
    }
}

#[must_use]
struct FileLockGuard {
    file: std::fs::File,
}

impl Drop for FileLockGuard {
    fn drop(&mut self) {
        self.file
            .unlock()
            .expect("Unexpected failure to release a lock file for build count");
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_update_normal_input() -> anyhow::Result<()> {
        let mut before = HashMap::new();
        before.insert("//some:target".to_owned(), 1);
        before.insert("//some/other:target".to_owned(), 2);
        let mut bc = BuildCount(before);
        let target_patterns = vec![
            buck2_data::TargetPattern {
                value: "//some/other:target".to_owned(),
            },
            buck2_data::TargetPattern {
                value: "//yet/another:target".to_owned(),
            },
        ];
        bc.update(&target_patterns);
        let mut expected = HashMap::new();
        expected.insert("//some:target".to_owned(), 1);
        expected.insert("//some/other:target".to_owned(), 3);
        expected.insert("//yet/another:target".to_owned(), 1);
        assert_eq!(bc.0, expected);

        Ok(())
    }

    #[test]
    fn test_update_empty_input() -> anyhow::Result<()> {
        let mut before = HashMap::new();
        before.insert("//some:target".to_owned(), 1);
        let expected = before.clone();
        let mut bc = BuildCount(before);
        let target_patterns = vec![];
        bc.update(&target_patterns);
        assert_eq!(bc.0, expected);

        Ok(())
    }

    #[test]
    fn test_min_count_some_value() -> anyhow::Result<()> {
        let mut data = HashMap::new();
        data.insert("//some:target1".to_owned(), 3);
        data.insert("//some:target2".to_owned(), 4);
        data.insert("//some:target3".to_owned(), 5);
        let bc = BuildCount(data);
        assert_eq!(bc.min_count(), 3);

        Ok(())
    }

    #[test]
    fn test_min_count_empty_data() -> anyhow::Result<()> {
        let data = HashMap::new();
        let bc = BuildCount(data);
        assert_eq!(bc.min_count(), 0);

        Ok(())
    }

    #[tokio::test]
    async fn test_read_no_such_file() -> anyhow::Result<()> {
        let no_such_dir = if cfg!(windows) {
            "C:\\no\\such\\dir"
        } else {
            "/no/such/dir"
        };
        let bcm = BuildCountManager::new(AbsNormPathBuf::from(no_such_dir.to_owned())?);
        let bc = bcm.read(FileName::new("no_such_file")?).await?;
        assert_eq!(bc.0, HashMap::new());

        Ok(())
    }

    #[tokio::test]
    async fn test_read_normal_file() -> anyhow::Result<()> {
        let temp_dir = tempfile::tempdir()?;
        let file_name = "some_file";
        tokio::fs::write(temp_dir.path().join(file_name), "{\"//some:target\":1}").await?;
        let mut expected = HashMap::new();
        expected.insert("//some:target".to_owned(), 1);
        let bcm = BuildCountManager::new(temp_dir.path().to_path_buf().try_into()?);
        let bc = bcm.read(FileName::new(file_name)?).await?;
        assert_eq!(bc.0, expected);

        Ok(())
    }

    #[tokio::test]
    async fn test_read_illegal_file_contents() -> anyhow::Result<()> {
        let temp_dir = tempfile::tempdir()?;
        let file_name = "some_file";
        tokio::fs::write(temp_dir.path().join(file_name), "aaa").await?;
        let bcm = BuildCountManager::new(temp_dir.path().to_path_buf().try_into()?);
        assert!(bcm.read(FileName::new(file_name)?).await.is_err());

        Ok(())
    }

    #[tokio::test]
    async fn test_write_normal_input() -> anyhow::Result<()> {
        let temp_dir = tempfile::tempdir()?;
        let file_name = "some_file";
        let bcm = BuildCountManager::new(temp_dir.path().to_path_buf().try_into()?);
        let mut data = HashMap::new();
        data.insert("//some:target".to_owned(), 1);
        bcm.write(&BuildCount(data), FileName::new(file_name)?)
            .await?;
        assert_eq!(
            &tokio::fs::read(temp_dir.path().join(file_name)).await?,
            b"{\"//some:target\":1}"
        );

        Ok(())
    }

    #[tokio::test]
    async fn test_write_empty_input() -> anyhow::Result<()> {
        let temp_dir = tempfile::tempdir()?;
        let file_name = "some_file";
        let bcm = BuildCountManager::new(temp_dir.path().to_path_buf().try_into()?);
        let data = HashMap::new();
        bcm.write(&BuildCount(data), FileName::new(file_name)?)
            .await?;
        assert_eq!(
            &tokio::fs::read(temp_dir.path().join(file_name)).await?,
            b"{}"
        );

        Ok(())
    }

    #[tokio::test]
    async fn test_min_build_count_normal_input() -> anyhow::Result<()> {
        let temp_dir = tempfile::tempdir()?;
        let file_name = "some_file";
        tokio::fs::write(temp_dir.path().join(file_name), "{\"//some:target\":1}").await?;
        let target_patterns = vec![
            buck2_data::TargetPattern {
                value: "//some:target".to_owned(),
            },
            buck2_data::TargetPattern {
                value: "//some/other:target".to_owned(),
            },
        ];
        let mut bcm = BuildCountManager::new(temp_dir.path().to_path_buf().try_into()?);
        assert_eq!(bcm.min_build_count(file_name, &target_patterns).await?, 1);

        Ok(())
    }

    #[tokio::test]
    async fn test_min_build_count_empty_input() -> anyhow::Result<()> {
        let temp_dir = tempfile::tempdir()?;
        let file_name = "some_file";
        tokio::fs::write(temp_dir.path().join(file_name), "{}").await?;
        let target_patterns = vec![];
        let mut bcm = BuildCountManager::new(temp_dir.path().to_path_buf().try_into()?);
        assert_eq!(bcm.min_build_count(file_name, &target_patterns).await?, 0);

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use std::time::Instant;

use dupe::Dupe;
use futures::future;
use futures::future::BoxFuture;
use tokio::runtime::Runtime;

/// For cleanup we want to perform, but cant do in `drop` because it's async.
#[derive(Clone, Dupe)]
pub struct AsyncCleanupContext<'a> {
    jobs: Arc<Mutex<Vec<BoxFuture<'static, ()>>>>,
    runtime: &'a Runtime,
}

impl<'a> AsyncCleanupContext<'a> {
    pub fn register(&self, name: &'static str, fut: BoxFuture<'static, ()>) {
        const WARNING_TIMEOUT: Duration = Duration::from_millis(1000);
        self.jobs
            .lock()
            .expect("Poisoned mutex")
            .push(Box::pin(async move {
                let start = Instant::now();
                fut.await;
                let elapsed = start.elapsed();
                if elapsed > WARNING_TIMEOUT {
                    tracing::warn!("Async cleanup step \'{}\' took {:?}", name, elapsed);
                } else {
                    tracing::info!("Async cleanup step \'{}\' took {:?}", name, elapsed);
                };
            }));
    }

    async fn join(&self) {
        let futs = std::mem::take(&mut *self.jobs.lock().expect("Poisoned mutex"));
        future::join_all(futs).await;
    }
}

pub struct AsyncCleanupContextGuard<'a>(AsyncCleanupContext<'a>);

impl<'a> AsyncCleanupContextGuard<'a> {
    pub fn new(runtime: &'a Runtime) -> Self {
        Self(AsyncCleanupContext {
            jobs: Arc::new(Mutex::new(Vec::new())),
            runtime,
        })
    }

    pub fn ctx(&self) -> &AsyncCleanupContext<'a> {
        &self.0
    }
}

impl<'a> Drop for AsyncCleanupContextGuard<'a> {
    fn drop(&mut self) {
        let future = self.0.join();
        self.ctx().runtime.block_on(async move {
            if tokio::time::timeout(Duration::from_secs(30), future)
                .await
                .is_err()
            {
                tracing::warn!("Timeout waiting for async cleanup");
            }
        });
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::mem;
use std::time::Instant;

use buck2_util::process_stats::process_cpu_time_us;

pub(crate) struct ClientCpuTracker {
    last_instant: Instant,
    last_cpu_us: Option<u64>,
}

impl ClientCpuTracker {
    pub(crate) fn new() -> ClientCpuTracker {
        ClientCpuTracker {
            last_instant: Instant::now(),
            last_cpu_us: process_cpu_time_us(),
        }
    }

    fn compute(
        prev_instant: Instant,
        prev_cpu_time_us: Option<u64>,
        new_instant: Instant,
        new_cpu_time_us: Option<u64>,
    ) -> Option<u32> {
        let prev_cpu_time_us = prev_cpu_time_us?;
        let new_cpu_time_us = new_cpu_time_us?;
        let duration = new_instant.checked_duration_since(prev_instant)?;
        let duration_us = u64::try_from(duration.as_micros()).ok()?;
        if duration_us == 0 {
            return None;
        }
        let cpu_time_since_prev_us = new_cpu_time_us.checked_sub(prev_cpu_time_us)?;
        u32::try_from(
            cpu_time_since_prev_us
                .checked_mul(100)?
                .checked_div(duration_us)?,
        )
        .ok()
    }

    // CPU time percents since last tick (can be more than 100% if there are multiple cores).
    pub(crate) fn tick_cpu_time_percents(&mut self) -> Option<u32> {
        let prev_cpu_us = mem::replace(&mut self.last_cpu_us, process_cpu_time_us());
        let prev_instant = mem::replace(&mut self.last_instant, Instant::now());
        Self::compute(
            prev_instant,
            prev_cpu_us,
            self.last_instant,
            self.last_cpu_us,
        )
    }
}

#[cfg(test)]
mod tests {
    use std::time::Duration;
    use std::time::Instant;

    use crate::client_cpu_tracker::ClientCpuTracker;

    #[test]
    fn test_compute() {
        let start = Instant::now();
        let now = start + Duration::from_secs(3);
        assert_eq!(
            Some(20),
            ClientCpuTracker::compute(start, Some(1_000_000_000), now, Some(1_000_600_000))
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::future::Future;

use anyhow::Context as _;
use buck2_cli_proto::client_context::HostArchOverride as GrpcHostArchOverride;
use buck2_cli_proto::client_context::HostPlatformOverride as GrpcHostPlatformOverride;
use buck2_cli_proto::ClientContext;
use buck2_common::invocation_paths::InvocationPaths;
use buck2_common::result::SharedResult;
use buck2_core::error::BUCK2_HARD_ERROR_ENV_VAR;
use buck2_core::fs::working_dir::WorkingDir;
use buck2_event_observer::verbosity::Verbosity;
use buck2_wrapper_common::invocation_id::TraceId;
use dupe::Dupe;
use tokio::runtime::Runtime;

use crate::argv::SanitizedArgv;
use crate::cleanup_ctx::AsyncCleanupContext;
use crate::common::CommonBuildConfigurationOptions;
use crate::common::CommonDaemonCommandOptions;
use crate::common::HostArchOverride;
use crate::common::HostPlatformOverride;
use crate::daemon::client::connect::BuckdConnectOptions;
use crate::daemon::client::BuckdClientConnector;
use crate::exit_result::ExitResult;
use crate::immediate_config::ImmediateConfigContext;
use crate::restarter::Restarter;
use crate::stdin::Stdin;
use crate::subscribers::recorder::try_get_invocation_recorder;

pub struct ClientCommandContext<'a> {
    pub init: fbinit::FacebookInit,
    pub immediate_config: &'a ImmediateConfigContext<'a>,
    pub paths: SharedResult<InvocationPaths>,
    pub working_dir: WorkingDir,
    pub verbosity: Verbosity,
    /// When set, this function is called to launch in process daemon.
    /// The function returns `Ok` when daemon successfully started
    /// and ready to accept connections.
    pub start_in_process_daemon: Option<Box<dyn FnOnce() -> anyhow::Result<()> + Send + Sync>>,
    pub command_name: String,
    pub sanitized_argv: SanitizedArgv,
    pub trace_id: TraceId,
    pub async_cleanup: AsyncCleanupContext<'a>,
    pub stdin: &'a mut Stdin,
    pub restarter: &'a mut Restarter,
    pub restarted_trace_id: Option<TraceId>,
    pub runtime: &'a Runtime,
}

impl<'a> ClientCommandContext<'a> {
    pub fn fbinit(&self) -> fbinit::FacebookInit {
        self.init
    }

    pub fn paths(&self) -> anyhow::Result<&InvocationPaths> {
        match &self.paths {
            Ok(p) => Ok(p),
            Err(e) => Err(e.dupe().into()),
        }
    }

    pub fn with_runtime<Fut, F>(self, func: F) -> <Fut as Future>::Output
    where
        Fut: Future + 'a,
        F: FnOnce(ClientCommandContext<'a>) -> Fut,
    {
        self.runtime.block_on(func(self))
    }

    pub fn instant_command<Fut, F>(self, command_name: &'static str, func: F) -> ExitResult
    where
        Fut: Future<Output = anyhow::Result<()>> + 'a,
        F: FnOnce(ClientCommandContext<'a>) -> Fut,
    {
        let mut recorder = try_get_invocation_recorder(
            &self,
            CommonDaemonCommandOptions::default_ref(),
            command_name,
            std::env::args().collect(),
            None,
        )?;

        let result = self.runtime.block_on(func(self));

        recorder.instant_command_outcome(result.is_ok());
        result.into()
    }

    pub fn stdin(&mut self) -> &mut Stdin {
        self.stdin
    }

    pub async fn connect_buckd(
        &self,
        options: BuckdConnectOptions<'a>,
    ) -> anyhow::Result<BuckdClientConnector<'a>> {
        BuckdConnectOptions { ..options }
            .connect(self.paths()?)
            .await
    }

    pub fn client_context(
        &self,
        config_opts: &CommonBuildConfigurationOptions,
        arg_matches: &clap::ArgMatches,
        sanitized_argv: Vec<String>,
    ) -> anyhow::Result<ClientContext> {
        // TODO(cjhopman): Support non unicode paths?
        Ok(ClientContext {
            config_overrides: config_opts.config_overrides(arg_matches)?,
            target_platform: config_opts.target_platforms.clone().unwrap_or_default(),
            host_platform: match config_opts.host_platform_override() {
                HostPlatformOverride::Default => GrpcHostPlatformOverride::DefaultPlatform,
                HostPlatformOverride::Linux => GrpcHostPlatformOverride::Linux,
                HostPlatformOverride::MacOs => GrpcHostPlatformOverride::MacOs,
                HostPlatformOverride::Windows => GrpcHostPlatformOverride::Windows,
            }
            .into(),
            host_arch: match config_opts.host_arch_override() {
                HostArchOverride::Default => GrpcHostArchOverride::DefaultArch,
                HostArchOverride::X86_64 => GrpcHostArchOverride::X8664,
                HostArchOverride::AArch64 => GrpcHostArchOverride::AArch64,
            }
            .into(),
            host_xcode_version: config_opts.host_xcode_version_override(),
            oncall: config_opts.oncall.as_ref().cloned().unwrap_or_default(),
            disable_starlark_types: config_opts.disable_starlark_types,
            skip_targets_with_duplicate_names: config_opts.skip_targets_with_duplicate_names,
            reuse_current_config: config_opts.reuse_current_config,
            sanitized_argv,
            exit_when_different_state: config_opts.exit_when_different_state,
            argfiles: self
                .immediate_config
                .trace()
                .iter()
                .map(|path| path.to_string())
                .collect(),
            target_call_stacks: config_opts.target_call_stacks,
            ..self.empty_client_context()?
        })
    }

    /// A client context for commands where CommonConfigOptions are not provided.
    pub fn empty_client_context(&self) -> anyhow::Result<ClientContext> {
        #[derive(Debug, thiserror::Error)]
        #[error("Current directory is not UTF-8")]
        struct CurrentDirIsNotUtf8;

        let daemon_uuid = match std::env::var("BUCK2_DAEMON_UUID") {
            Ok(daemon_uuid) => Some(daemon_uuid),
            _ => None,
        };

        Ok(ClientContext {
            working_dir: self
                .working_dir
                .path()
                .to_str()
                .context(CurrentDirIsNotUtf8)?
                .to_owned(),
            config_overrides: Default::default(),
            target_platform: Default::default(),
            host_platform: Default::default(),
            host_arch: Default::default(),
            host_xcode_version: Default::default(),
            oncall: Default::default(),
            disable_starlark_types: false,
            target_call_stacks: false,
            skip_targets_with_duplicate_names: false,
            trace_id: format!("{}", self.trace_id),
            reuse_current_config: false,
            daemon_uuid,
            sanitized_argv: Vec::new(),
            argfiles: Vec::new(),
            buck2_hard_error: BUCK2_HARD_ERROR_ENV_VAR.get()?.cloned().unwrap_or_default(),
            exit_when_different_state: false,
        })
    }

    pub fn async_cleanup_context(&self) -> &AsyncCleanupContext<'a> {
        &self.async_cleanup
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::ops::ControlFlow;
use std::ops::FromResidual;
use std::ops::Try;

use crate::exit_result::ExitResult;

/// The final outcome returned to the client of running a command in the daemon.
///
/// Either "successful", in which case `R`, the response type, is available, or "failure",
/// where a general `CommandError` was returned. Consider this a "failed successfully" indicator.
/// At the point where this is returned, all event processing / logging should be handled.
#[derive(Debug)]
#[must_use]
pub enum CommandOutcome<R> {
    /// The buckd client successfully returned the expected response.
    ///
    /// Additional processing of this response may be necessary to determine overall success or
    /// failure within the client.
    Success(R),
    /// The buckd client successfully returned a response, but that response was a general failure.
    ///
    /// The user has already been presented an error message, and the CLI should exit with
    /// this status code.
    Failure(Option<u8>),
}

/// Small wrapper used in FromResidual
pub struct CommandFailure(Option<u8>);

/// Allow the usage of '?' when going from a CommandOutcome -> ExitResult
impl<R> Try for CommandOutcome<R> {
    type Output = R;
    type Residual = CommandFailure;

    fn from_output(output: Self::Output) -> Self {
        Self::Success(output)
    }

    fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
        match self {
            CommandOutcome::Success(res) => ControlFlow::Continue(res),
            CommandOutcome::Failure(status) => ControlFlow::Break(CommandFailure(status)),
        }
    }
}

impl FromResidual<CommandFailure> for ExitResult {
    fn from_residual(_residual: CommandFailure) -> Self {
        ExitResult::failure()
    }
}

impl<R> FromResidual<CommandFailure> for CommandOutcome<R> {
    fn from_residual(residual: CommandFailure) -> Self {
        Self::Failure(residual.0)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

//! This modules contains common options that are shared between different commands.
//! They are shared by composition together with flattening of the options.
//!
//! For example, to adopt config options, add the following field to the
//! command definition:
//!
//! ```ignore
//! #[derive(Debug, clap::Parser)]
//! struct MyCommand {
//!    #[clap(flatten)]
//!    config_opts: CommonConfigOptions,
//!    ...
//! }
//! ```
use std::path::Path;

use buck2_cli_proto::common_build_options::ExecutionStrategy;
use buck2_cli_proto::config_override::ConfigType;
use buck2_cli_proto::ConfigOverride;
use buck2_core::fs::fs_util;
use dupe::Dupe;
use gazebo::prelude::*;
use termwiz::istty::IsTty;

use crate::final_console::FinalConsole;
use crate::path_arg::PathArg;
use crate::subscribers::superconsole::SuperConsoleConfig;

pub const EVENT_LOG: &str = "--event-log";
pub const NO_EVENT_LOG: &str = "--no-event-log";

#[derive(
    Debug,
    serde::Serialize,
    serde::Deserialize,
    Clone,
    Dupe,
    Copy,
    clap::ArgEnum
)]
#[clap(rename_all = "lower")]
pub enum ConsoleType {
    Simple,
    SimpleNoTty,
    SimpleTty,
    Super,
    Auto,
    None,
}

#[derive(
    Debug,
    serde::Serialize,
    serde::Deserialize,
    Clone,
    Dupe,
    Copy,
    clap::ArgEnum
)]
#[clap(rename_all = "lower")]
pub enum UiOptions {
    Dice,
    DebugEvents,
    /// I/O panel.
    Io,
    /// RE panel.
    Re,
}

#[derive(
    Debug,
    serde::Serialize,
    serde::Deserialize,
    Clone,
    Dupe,
    Copy,
    clap::ArgEnum
)]
#[clap(rename_all = "lower")]
pub enum HostPlatformOverride {
    Default,
    Linux,
    MacOs,
    Windows,
}

#[derive(
    Debug,
    serde::Serialize,
    serde::Deserialize,
    Clone,
    Dupe,
    Copy,
    clap::ArgEnum
)]
#[clap(rename_all = "lower")]
pub enum HostArchOverride {
    Default,
    AArch64,
    X86_64,
}

/// Defines options related to commands that involves a streaming daemon command.
#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize, Default)]
pub struct CommonDaemonCommandOptions {
    /// Write events to this log file
    #[clap(value_name = "PATH", long = EVENT_LOG)]
    pub event_log: Option<PathArg>,

    /// Do not write any event logs. Overrides --event-log. Used from `replay` to avoid recursive logging
    #[clap(long = NO_EVENT_LOG, hidden = true)]
    pub no_event_log: bool,

    /// Write command invocation id into this file.
    #[clap(long, value_name = "PATH")]
    pub(crate) write_build_id: Option<PathArg>,

    /// Write the invocation record (as JSON) to this path. No guarantees whatsoever are made
    /// regarding the stability of the format.
    #[clap(long, value_name = "PATH")]
    pub(crate) unstable_write_invocation_record: Option<PathArg>,
}

impl CommonDaemonCommandOptions {
    pub fn default_ref() -> &'static Self {
        static DEFAULT: CommonDaemonCommandOptions = CommonDaemonCommandOptions {
            event_log: None,
            no_event_log: false,
            write_build_id: None,
            unstable_write_invocation_record: None,
        };
        &DEFAULT
    }
}

/// Defines options for config and configuration related things. Any command that involves the build graph should include these options.
#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize, Default)]
pub struct CommonBuildConfigurationOptions {
    #[clap(
        value_name = "SECTION.OPTION=VALUE",
        long = "config",
        short = 'c',
        help = "List of config options",
        // Needs to be explicitly set, otherwise will treat `-c a b c` -> [a, b, c]
        // rather than [a] and other positional arguments `b c`.
        number_of_values = 1
    )]
    pub config_values: Vec<String>,

    #[clap(
        value_name = "PATH",
        long = "config-file",
        help = "List of config file paths",
        number_of_values = 1
    )]
    pub config_files: Vec<String>,

    #[clap(
        long = "target-platforms",
        help = "Configuration target (one) to use to configure targets",
        number_of_values = 1,
        value_name = "PLATFORM"
    )]
    pub target_platforms: Option<String>,

    #[clap(long, ignore_case = true, value_name = "HOST", arg_enum)]
    fake_host: Option<HostPlatformOverride>,

    #[clap(long, ignore_case = true, value_name = "ARCH", arg_enum)]
    fake_arch: Option<HostArchOverride>,

    /// Value must be formatted as: version-build (e.g., 14.3.0-14C18 or 14.1-14B47b)
    #[clap(long, value_name = "VERSION-BUILD")]
    fake_xcode_version: Option<String>,

    // TODO(cjhopman): Why is this only in CommonConfigOptions options, it has nothing to do with config? Shouldn't all commands support --oncall?
    #[clap(long)]
    pub oncall: Option<String>,

    /// Disable runtime type checking in Starlark interpreter.
    ///
    /// This option is not stable, and can be used only locally
    /// to diagnose evaluation performance problems.
    #[clap(long)]
    pub disable_starlark_types: bool,

    /// Record or show target call stacks.
    ///
    /// Starlark call stacks will be included in duplicate targets error.
    ///
    /// If a command outputs targets (like `targets` command),
    /// starlark call stacks will be printed after the targets.
    #[clap(long = "stack")]
    pub target_call_stacks: bool,

    /// If there are targets with duplicate names in `BUCK` file,
    /// skip all the duplicates but the first one.
    /// This is a hack for TD. Do not use this option.
    #[clap(long)]
    pub(crate) skip_targets_with_duplicate_names: bool,

    #[clap(long)]
    pub reuse_current_config: bool,

    /// Used for exiting a concurrent command when a different state is detected.
    #[clap(long)]
    pub exit_when_different_state: bool,
}

impl CommonBuildConfigurationOptions {
    /// Produces a single, ordered list of config overrides. A `ConfigOverride`
    /// represents either a file, passed via `--config-file`, or a config value,
    /// passed via `-c`/`--config`. The relative order of those are important,
    /// hence they're merged into a single list.
    pub fn config_overrides(
        &self,
        matches: &clap::ArgMatches,
    ) -> anyhow::Result<Vec<ConfigOverride>> {
        fn with_indices<'a, T>(
            collection: &'a [T],
            name: &str,
            matches: &'a clap::ArgMatches,
        ) -> impl Iterator<Item = (usize, &'a T)> + 'a {
            let indices = matches.indices_of(name);
            let indices = indices.unwrap_or_default();
            assert_eq!(
                indices.len(),
                collection.len(),
                "indices len is not equal to collection len for flag `{}`",
                name
            );
            indices.into_iter().zip(collection)
        }

        // Relative paths passed on the command line are relative to the cwd
        // of the client, not the daemon, so perform path canonicalisation here.
        fn resolve_config_file_argument(arg: &str) -> anyhow::Result<String> {
            if arg.contains("//") {
                // Cell-relative path resolution would be performed by the daemon
                return Ok(arg.to_owned());
            }

            let path = Path::new(arg);
            if path.is_absolute() {
                return Ok(arg.to_owned());
            }

            let abs_path = fs_util::canonicalize(path)?;
            Ok(abs_path.to_string_lossy().into_owned())
        }

        let config_values_args = with_indices(&self.config_values, "config-values", matches).map(
            |(index, config_value)| {
                (
                    index,
                    ConfigOverride {
                        config_override: config_value.clone(),
                        config_type: ConfigType::Value as i32,
                    },
                )
            },
        );

        let config_file_args = with_indices(&self.config_files, "config-files", matches)
            .map(|(index, unresolved_file)| {
                let resolved_file = resolve_config_file_argument(unresolved_file)?;
                Ok((
                    index,
                    ConfigOverride {
                        config_override: resolved_file,
                        config_type: ConfigType::File as i32,
                    },
                ))
            })
            .collect::<anyhow::Result<Vec<_>>>()?;

        let mut ordered_merged_configs: Vec<(usize, ConfigOverride)> = config_file_args;
        ordered_merged_configs.extend(config_values_args);
        ordered_merged_configs.sort_by(|(lhs_index, _), (rhs_index, _)| lhs_index.cmp(rhs_index));

        Ok(ordered_merged_configs.into_map(|(_, config_arg)| config_arg))
    }

    pub fn host_platform_override(&self) -> HostPlatformOverride {
        match &self.fake_host {
            Some(v) => *v,
            None => HostPlatformOverride::Default,
        }
    }
    pub fn host_arch_override(&self) -> HostArchOverride {
        match &self.fake_arch {
            Some(v) => *v,
            None => HostArchOverride::Default,
        }
    }
    pub fn host_xcode_version_override(&self) -> Option<String> {
        self.fake_xcode_version.to_owned()
    }

    pub fn default_ref() -> &'static Self {
        static DEFAULT: CommonBuildConfigurationOptions = CommonBuildConfigurationOptions {
            config_values: vec![],
            config_files: vec![],
            target_platforms: None,
            fake_host: None,
            fake_arch: None,
            fake_xcode_version: None,
            oncall: None,
            disable_starlark_types: false,
            target_call_stacks: false,
            skip_targets_with_duplicate_names: false,
            reuse_current_config: false,
            exit_when_different_state: false,
        };
        &DEFAULT
    }
}

/// Defines common options for build-like commands (build, test, install).
#[allow(rustdoc::invalid_html_tags)]
#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
pub struct CommonBuildOptions {
    /// Print a build report
    ///
    /// --build-report=- will print the build report to stdout
    /// --build-report=<filepath> will write the build report to the file
    #[clap(long = "build-report", value_name = "PATH")]
    build_report: Option<String>,

    /// Deprecated. Use --build-report=-
    // TODO(cjhopman): this is probably only used by the e2e framework. remove it from there
    #[clap(long = "print-build-report", hidden = true)]
    print_build_report: bool,

    /// Number of threads to use during execution (default is # cores)
    // TODO(cjhopman): This only limits the threads used for action execution and it doesn't work correctly with concurrent commands.
    #[clap(short = 'j', long = "num-threads", value_name = "THREADS")]
    pub num_threads: Option<u32>,

    /// Enable only local execution. Will reject actions that cannot execute locally.
    #[clap(long, group = "build_strategy")]
    local_only: bool,

    /// Enable only remote execution. Will reject actions that cannot execute remotely.
    #[clap(long, group = "build_strategy")]
    remote_only: bool,

    /// Enable hybrid execution. Will prefer executing actions that can execute locally on the
    /// local host.
    #[clap(long, group = "build_strategy")]
    prefer_local: bool,

    /// Enable hybrid execution. Will prefer executing actions that can execute remotely on RE and will avoid racing local and remote execution.
    #[clap(long, group = "build_strategy")]
    prefer_remote: bool,

    /// Experimental: Disable all execution.
    #[clap(long, group = "build_strategy")]
    unstable_no_execution: bool,

    /// Do not perform remote cache queries or cache writes. If remote execution is enabled, the RE
    /// service might still deduplicate actions, so for e.g. benchmarking, using a random isolation
    /// dir is preferred.
    #[clap(long)]
    no_remote_cache: bool,

    /// Could be used to enable the action cache writes on the RE worker when no_remote_cache is specified
    #[clap(long, requires("no-remote-cache"))]
    write_to_cache_anyway: bool,

    /// Process dep files when they are generated (i.e. after running a command that produces dep
    /// files), rather than when they are used (i.e. before re-running a command that previously
    /// produced dep files). Use this when debugging commands that produce dep files. Note that
    /// commands that previously produced dep files will not re-run: only dep files produced during
    /// this command will be eagerly loaded.
    #[clap(long)]
    eager_dep_files: bool,

    #[clap(long)]
    upload_all_actions: bool,

    /// If Buck hits an error, do as little work as possible before exiting.
    #[clap(long, group = "fail-when")]
    fail_fast: bool,

    /// If Buck hits an error, continue doing as much work as possible before exiting.
    #[clap(long, group = "fail-when")]
    keep_going: bool,

    /// If target is missing, then skip building instead of throwing error.
    #[clap(long)]
    skip_missing_targets: bool,

    /// If target is incompatible with the specified configuration, skip building instead of throwing error.
    /// This does not apply to targets specified with glob patterns `/...` or `:`
    /// which are skipped unconditionally.
    #[clap(long)]
    skip_incompatible_targets: bool,
}

impl CommonBuildOptions {
    fn build_report(&self) -> (bool, String) {
        match (self.print_build_report, &self.build_report) {
            (false, None) => (false, "".to_owned()),
            (_, Some(path)) if path != "-" => (true, path.to_owned()),
            _ => (true, "".to_owned()),
        }
    }

    pub fn to_proto(&self) -> buck2_cli_proto::CommonBuildOptions {
        let (unstable_print_build_report, unstable_build_report_filename) = self.build_report();
        let concurrency = self
            .num_threads
            .map(|num| buck2_cli_proto::Concurrency { concurrency: num });

        buck2_cli_proto::CommonBuildOptions {
            concurrency,
            execution_strategy: if self.local_only {
                ExecutionStrategy::LocalOnly as i32
            } else if self.remote_only {
                ExecutionStrategy::RemoteOnly as i32
            } else if self.prefer_local {
                ExecutionStrategy::HybridPreferLocal as i32
            } else if self.prefer_remote {
                ExecutionStrategy::HybridPreferRemote as i32
            } else if self.unstable_no_execution {
                ExecutionStrategy::NoExecution as i32
            } else {
                ExecutionStrategy::Default as i32
            },
            unstable_print_build_report,
            unstable_build_report_filename,
            eager_dep_files: self.eager_dep_files,
            upload_all_actions: self.upload_all_actions,
            skip_cache_read: self.no_remote_cache,
            skip_cache_write: self.no_remote_cache && !self.write_to_cache_anyway,
            fail_fast: self.fail_fast,
            keep_going: self.keep_going,
            skip_missing_targets: self.skip_missing_targets,
            skip_incompatible_targets: self.skip_incompatible_targets,
        }
    }
}

/// Defines common console options for commands.
#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
pub struct CommonConsoleOptions {
    #[clap(
        long = "console",
        help = "Which console to use for this command",
        default_value = "auto",
        ignore_case = true,
        env = "BUCK_CONSOLE",
        value_name = "super|simple|...",
        arg_enum
    )]
    pub console_type: ConsoleType,

    /// Configure additional superconsole ui components.
    ///
    /// Accepts a comma-separated list of superconsole components to add. Possible values are:
    ///
    ///   dice - shows information about evaluated dice nodes
    ///   debugevents - shows information about the flow of events from buckd
    ///
    /// These components can be turned on/off interactively.
    /// Press 'h' for help when superconsole is active.
    #[clap(
        long = "ui",
        ignore_case = true,
        multiple = true,
        number_of_values = 1,
        arg_enum
    )]
    pub ui: Vec<UiOptions>,

    #[clap(
        long,
        help = "Disable console interactions",
        env = "BUCK_NO_INTERACTIVE_CONSOLE"
    )]
    pub no_interactive_console: bool,
}

impl Default for CommonConsoleOptions {
    fn default() -> Self {
        Self {
            console_type: ConsoleType::Auto,
            ui: Vec::new(),
            no_interactive_console: false,
        }
    }
}

impl CommonConsoleOptions {
    pub fn default_ref() -> &'static Self {
        static OPTS: CommonConsoleOptions = CommonConsoleOptions {
            console_type: ConsoleType::Auto,
            ui: vec![],
            no_interactive_console: false,
        };
        &OPTS
    }

    pub fn simple_ref() -> &'static Self {
        static OPTS: CommonConsoleOptions = CommonConsoleOptions {
            console_type: ConsoleType::Simple,
            ui: vec![],
            no_interactive_console: false,
        };
        &OPTS
    }

    pub fn none_ref() -> &'static Self {
        static OPTS: CommonConsoleOptions = CommonConsoleOptions {
            console_type: ConsoleType::None,
            ui: vec![],
            no_interactive_console: false,
        };
        &OPTS
    }

    pub fn final_console(&self) -> FinalConsole {
        let is_tty = match self.console_type {
            ConsoleType::Auto | ConsoleType::Simple => std::io::stderr().is_tty(),
            ConsoleType::Super => true,
            ConsoleType::SimpleNoTty => false,
            ConsoleType::SimpleTty => true,
            ConsoleType::None => false,
        };
        if is_tty {
            FinalConsole::new_with_tty()
        } else {
            FinalConsole::new_without_tty()
        }
    }

    pub fn superconsole_config(&self) -> SuperConsoleConfig {
        let mut config = SuperConsoleConfig::default();
        for option in &self.ui {
            match option {
                UiOptions::Dice => config.enable_dice = true,
                UiOptions::DebugEvents => config.enable_debug_events = true,
                UiOptions::Io => config.enable_io = true,
                UiOptions::Re => config.enable_detailed_re = true,
            }
        }
        config
    }
}

/// Common options for commands like `build` or `query`.
/// Not all the commands have all the options.
#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize, Default)]
pub struct CommonCommandOptions {
    /// Buckconfig and similar options.
    #[clap(flatten)]
    pub config_opts: CommonBuildConfigurationOptions,

    /// UI options.
    #[clap(flatten)]
    pub console_opts: CommonConsoleOptions,

    /// Event-log options.
    #[clap(flatten)]
    pub event_log_opts: CommonDaemonCommandOptions,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use tokio::io::AsyncReadExt;

use crate::stdin::Stdin;

pub struct ConsoleInteractionStream<'a> {
    stdin: &'a mut Stdin,
    term: InteractiveTerminal,
}

impl<'a> ConsoleInteractionStream<'a> {
    pub fn new(stdin: &'a mut Stdin) -> Option<Self> {
        let term = match InteractiveTerminal::enable() {
            Ok(Some(term)) => term,
            Ok(None) => {
                tracing::debug!("Not enabling interactive terminal");
                return None;
            }
            Err(e) => {
                tracing::warn!("Failed to enable interactive terminal: {:#}", e);
                return None;
            }
        };

        Some(Self { stdin, term })
    }
}

impl<'a> Drop for ConsoleInteractionStream<'a> {
    fn drop(&mut self) {
        if let Err(e) = self.term.disable() {
            tracing::warn!("Failed to disable interactive terminal: {:#}", e);
        }
    }
}

#[cfg(unix)]
mod interactive_terminal {
    use std::io::IsTerminal;
    use std::os::unix::io::AsRawFd;

    use anyhow::Context as _;
    use termios::*;

    pub struct InteractiveTerminal {
        orig: Termios,
    }

    impl InteractiveTerminal {
        pub fn enable() -> anyhow::Result<Option<Self>> {
            let fd = std::io::stdin().as_raw_fd();

            if !std::io::stdin().is_terminal() {
                return Ok(None);
            }

            // If stdout is redirected, let's also no turn this on since it might be redirected to
            // something that wants stdout. We can always debug those with `debug replay` now.
            if !std::io::stdout().is_terminal() {
                return Ok(None);
            }

            // We also check for stderr, since if a user is starting a bunch of bucks in the
            // background those may end up clobbering the termios state and the following can
            // happen:
            //
            // - Process starts 1 buck, which sets noecho.
            // - Process starts another buck, which reads the tty state, reads noecho.
            // - The first buck exits and resets echo.
            // - The second buck exits and resets noecho (since that's what it read)
            //
            // We check stderr because if stderr is a TTY the user will see a bunch of consoles
            // interleaving and that would probably tell them something's wrong.
            if !std::io::stderr().is_terminal() {
                return Ok(None);
            }

            let orig = Termios::from_fd(fd).context("Failed to access current termios")?;

            let mut termios = orig;

            // Switch to non-canonical mode to get input immediately, and disable echo.
            termios.c_lflag &= !(ICANON | ECHO);

            // Keep blocking reads.
            termios.c_cc[VMIN] = 1;
            termios.c_cc[VTIME] = 0;

            tcsetattr(fd, TCSANOW, &termios).context("Failed to set termios")?;

            Ok(Some(Self { orig }))
        }

        pub fn disable(&mut self) -> anyhow::Result<()> {
            let fd = std::io::stdin().as_raw_fd();
            tcsetattr(fd, TCSANOW, &self.orig).context("Failed to reset termios")?;
            Ok(())
        }
    }
}

#[cfg(not(unix))]
mod interactive_terminal {
    pub struct InteractiveTerminal;

    impl InteractiveTerminal {
        pub fn enable() -> anyhow::Result<Option<Self>> {
            Ok(None)
        }

        pub fn disable(&mut self) -> anyhow::Result<()> {
            Ok(())
        }
    }
}

use interactive_terminal::InteractiveTerminal;

#[async_trait::async_trait]
pub trait ConsoleInteraction: Send + Sync {
    async fn char(&mut self) -> anyhow::Result<char>;
}

#[async_trait::async_trait]
impl<'a> ConsoleInteraction for ConsoleInteractionStream<'a> {
    async fn char(&mut self) -> anyhow::Result<char> {
        match self.stdin.read_u8().await {
            Ok(c) => Ok(c.into()),
            // NOTE: An EOF here would be reported as "unexpected" because we asked for a u8.
            Err(e)
                if e.kind() == std::io::ErrorKind::UnexpectedEof
                    || e.kind() == std::io::ErrorKind::WouldBlock =>
            {
                futures::future::pending().await
            }
            Err(e) => Err(anyhow::Error::from(e).context("Error reading char from console")),
        }
    }
}

pub struct NoopConsoleInteraction;

#[async_trait::async_trait]
impl ConsoleInteraction for NoopConsoleInteraction {
    async fn char(&mut self) -> anyhow::Result<char> {
        futures::future::pending().await
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_common::legacy_configs::cells::DaemonStartupConfig;
use buck2_core::env_helper::EnvHelper;

use crate::version::BuckVersion;

pub fn gen_daemon_constraints(
    daemon_startup_config: &DaemonStartupConfig,
) -> anyhow::Result<buck2_cli_proto::DaemonConstraints> {
    Ok(buck2_cli_proto::DaemonConstraints {
        version: version(),
        user_version: user_version()?,
        daemon_id: buck2_events::daemon_id::DAEMON_UUID.to_string(),
        daemon_startup_config: Some(daemon_startup_config.serialize()),
        extra: None,
    })
}

pub fn version() -> String {
    BuckVersion::get_unique_id().to_owned()
}

pub fn user_version() -> anyhow::Result<Option<String>> {
    static SANDCASTLE_ID: EnvHelper<String> = EnvHelper::new("SANDCASTLE_ID");
    Ok(SANDCASTLE_ID.get()?.cloned())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::ops::ControlFlow;
use std::sync::Arc;
use std::time::SystemTime;

use anyhow::Context;
use async_trait::async_trait;
use buck2_cli_proto::command_result;
use buck2_cli_proto::CommandResult;
use buck2_common::daemon_dir::DaemonDir;
use buck2_events::BuckEvent;
use futures::stream::FuturesUnordered;
use futures::Future;
use futures::Stream;
use futures::StreamExt;
use gazebo::prelude::VecExt;
use thiserror::Error;
use tokio::sync::mpsc;
use tokio::sync::mpsc::UnboundedReceiver;

use crate::client_cpu_tracker::ClientCpuTracker;
use crate::command_outcome::CommandOutcome;
use crate::console_interaction_stream::ConsoleInteraction;
use crate::console_interaction_stream::ConsoleInteractionStream;
use crate::console_interaction_stream::NoopConsoleInteraction;
use crate::file_tailer::FileTailer;
use crate::file_tailer::StdoutOrStderr;
use crate::stream_value::StreamValue;
use crate::subscribers::subscriber::EventSubscriber;
use crate::subscribers::subscriber::Tick;
use crate::ticker::Ticker;

/// Target number of self.tick() calls per second. These can be used by implementations for regular updates, for example
/// superconsole uses it to re-render the frame and this is what allows it to have constantly updating timers.
/// Other than tick() calls, implementations will only be notified when new events arrive.
const TICKS_PER_SECOND: u32 = 10;

#[derive(Debug, Error)]
#[allow(clippy::large_enum_variant)]
enum BuckdCommunicationError {
    #[error("call to daemon returned an unexpected result type. got `{0:?}`")]
    UnexpectedResultType(command_result::Result),
    #[error("buck daemon returned an empty CommandResult")]
    EmptyCommandResult,
    #[error("buck daemon request finished without returning a CommandResult")]
    MissingCommandResult,
    #[error("buckd communication encountered an unexpected error `{0:?}`")]
    TonicError(tonic::Status),
}

impl From<tonic::Status> for BuckdCommunicationError {
    fn from(status: tonic::Status) -> Self {
        match status.code() {
            tonic::Code::Ok => {
                unreachable!("::Ok should be unreachable as it should produce an Ok result")
            }
            // all errors should be encoded into the CommandResult, we must've hit something strange to be here.
            _ => BuckdCommunicationError::TonicError(status),
        }
    }
}

#[async_trait]
pub trait PartialResultHandler {
    type PartialResult: TryFrom<
        buck2_cli_proto::partial_result::PartialResult,
        Error = buck2_cli_proto::partial_result::PartialResult,
    >;

    async fn handle_partial_result(
        &mut self,
        ctx: PartialResultCtx<'_, '_>,
        partial_res: Self::PartialResult,
    ) -> anyhow::Result<()>;
}

/// Exposes restricted access to EventsCtx from PartialResultHandler instances.
pub struct PartialResultCtx<'a, 'b> {
    inner: &'a mut EventsCtx<'b>,
}

impl<'a, 'b> PartialResultCtx<'a, 'b> {
    pub async fn stdout(&mut self, bytes: &[u8]) -> anyhow::Result<()> {
        self.inner
            .handle_subscribers(|subscriber| subscriber.handle_output(bytes))
            .await
    }
}

/// Manages incoming event streams from the daemon for the buck2 client and
/// forwards them to the appropriate subscribers registered on this struct
pub struct EventsCtx<'a> {
    pub(crate) subscribers: Vec<Box<dyn EventSubscriber + 'a>>,
    ticker: Ticker,
    client_cpu_tracker: ClientCpuTracker,
}

#[derive(PartialEq, Eq, Debug)]
pub enum FileTailerEvent {
    Stdout(Vec<u8>),
    Stderr(Vec<u8>),
}

pub struct FileTailers {
    _stdout_tailer: Option<FileTailer>,
    _stderr_tailer: Option<FileTailer>,
    stream: UnboundedReceiver<FileTailerEvent>,
}

impl<'a> EventsCtx<'a> {
    pub fn new(subscribers: Vec<Box<dyn EventSubscriber + 'a>>) -> Self {
        Self {
            subscribers,
            ticker: Ticker::new(TICKS_PER_SECOND),
            client_cpu_tracker: ClientCpuTracker::new(),
        }
    }

    async fn handle_stream_next<Handler>(
        &mut self,
        partial_result_handler: &mut Handler,
        next: Option<Vec<anyhow::Result<StreamValue>>>,
        shutdown: &mut Option<buck2_data::DaemonShutdown>,
    ) -> anyhow::Result<ControlFlow<Box<CommandResult>, ()>>
    where
        Handler: PartialResultHandler,
    {
        let next = next.context(BuckdCommunicationError::MissingCommandResult)?;
        let mut events = Vec::with_capacity(next.len());
        for next in next {
            let next = match next {
                Ok(next) => next,
                Err(e) => {
                    self.handle_events(events, shutdown).await?;
                    return Err(e).context("Buck daemon event bus encountered an error, the root cause (if available) is displayed above this message.");
                }
            };
            match next {
                StreamValue::Event(event) => {
                    let event = event.try_into()?;
                    events.push(event);
                }
                StreamValue::PartialResult(partial_res) => {
                    let partial_res = partial_res
                        .partial_result
                        .context("Empty partial result")?
                        .try_into()
                        .map_err(|e| anyhow::anyhow!("Invalid PartialResult: {:?}", e))?;
                    partial_result_handler
                        .handle_partial_result(PartialResultCtx { inner: self }, partial_res)
                        .await?;
                }
                StreamValue::Result(res) => {
                    self.handle_events(events, shutdown).await?;
                    self.handle_command_result(&res).await?;
                    return Ok(ControlFlow::Break(res));
                }
            }
        }
        self.handle_events(events, shutdown).await?;
        Ok(ControlFlow::Continue(()))
    }

    async fn dispatch_tailer_event(&mut self, event: FileTailerEvent) -> anyhow::Result<()> {
        match event {
            FileTailerEvent::Stdout(stdout) => self.handle_tailer_stdout(&stdout).await,
            FileTailerEvent::Stderr(stderr) => self.handle_tailer_stderr(&stderr).await,
        }
    }

    async fn unpack_stream_inner<S, Handler>(
        &mut self,
        partial_result_handler: &mut Handler,
        stream: S,
        tailers: Option<FileTailers>,
        mut console_interaction: Option<ConsoleInteractionStream<'_>>,
    ) -> anyhow::Result<CommandResult>
    where
        S: Stream<Item = anyhow::Result<StreamValue>> + Unpin,
        Handler: PartialResultHandler,
    {
        let mut noop_console_interaction = NoopConsoleInteraction;
        let console_interaction: &mut dyn ConsoleInteraction = match &mut console_interaction {
            Some(i) => i as _,
            None => &mut noop_console_interaction as _,
        };

        // TODO(cjhopman): This is fragile. We are handling stdout/stderr here but we also want to stop
        // the streaming of stdout/stderr on some things we see here but importantly we need to finish
        // draining stdout/stderr even if we encounter errors. Also, it looks like we probably drop a lot
        // of events/stdout/stderr if a handle_subscribers() call returns an error.

        // We don't want to return early here without draining stdout/stderr.
        // TODO(brasselsprouts): simpler logic

        let mut tailers = tailers.unwrap_or_else(FileTailers::empty);

        let mut stream = stream.ready_chunks(1000);

        // NOTE: When unpacking the stream we capture any shutdown event we encounter. If we fail
        // to unpack the stream to completion, we'll use that later.
        let mut shutdown = None;

        let command_result: anyhow::Result<CommandResult> = try {
            loop {
                tokio::select! {
                    next = stream.next() => {
                        // Make sure we still flush if next produces an error is accurate
                        match self.handle_stream_next(
                            partial_result_handler,
                            next,
                            &mut shutdown
                        ).await? {
                            ControlFlow::Continue(()) => {}
                            ControlFlow::Break(res) => break *res,
                        }
                    }
                    Some(event) = tailers.stream.recv() => {
                        self.dispatch_tailer_event(event).await?;
                    }
                    c = console_interaction.char() => {
                        self.handle_console_interaction(c?).await?;
                    }
                    tick = self.ticker.tick() => {
                        self.tick(&tick).await?;
                    }
                    else => {
                        unreachable!("The tick branch will always take precedence over an else case.");
                    }
                }
            }
        };

        let flush_result = self.flush(&mut Some(tailers)).await;
        let exit_result = self.handle_exit().await;

        let command_result = match (command_result, shutdown) {
            (Ok(r), _) => r,
            (Err(e), Some(shutdown)) => {
                // NOTE: When we get an error unpacking the stream, that means we didn't get a
                // CommandResult, or we had an error on the stream. Both of those things are *most
                // often* caused by a daemon shutdown, so if we did have a daemon shutdown reported
                // to us over the event stream, we show that instead. We log the real error for
                // debugging in case that's useful, but it's sufficiently likely (as in: almost
                // certain) the daemon shutdown is the cause for us to simply claim it is.
                tracing::debug!("Original unpack_stream error was: {:#}", e);

                return Err(anyhow::anyhow!(
                    "The Buck2 daemon was shut down while executing your command. \
                    This happened because: {}",
                    shutdown,
                ));
            }
            (Err(e), None) => return Err(e),
        };

        flush_result?;
        exit_result?;
        Ok(command_result)
    }

    /// Given a stream of StreamValues originating from the daemon, "unpacks" it by extracting the command result from the
    /// event stream and returning it.
    /// Also merges all other streams into a single, larger stream
    pub async fn unpack_stream<S, Res, Handler>(
        &mut self,
        partial_result_handler: &mut Handler,
        stream: S,
        tailers: Option<FileTailers>,
        console_interaction: Option<ConsoleInteractionStream<'_>>,
    ) -> anyhow::Result<CommandOutcome<Res>>
    where
        S: Stream<Item = anyhow::Result<StreamValue>> + Unpin,
        Res: TryFrom<command_result::Result, Error = command_result::Result>,
        Handler: PartialResultHandler,
    {
        let command_result = self
            .unpack_stream_inner(partial_result_handler, stream, tailers, console_interaction)
            .await;

        match command_result {
            Ok(result) => convert_result(result),
            Err(err) => Err(self.handle_error_owned(err).await),
        }
    }

    pub async fn flushing_tailers<R, Fut: Future<Output = R>>(
        &mut self,
        tailers: &mut Option<FileTailers>,
        f: impl FnOnce() -> Fut,
    ) -> anyhow::Result<R> {
        let res = f().await;
        self.flush(tailers).await?;
        Ok(res)
    }

    /// Unpack a single `CommandResult`, log any failures if necessary, and convert it to a
    /// `CommandOutcome`
    pub async fn unpack_oneshot<
        Res: TryFrom<command_result::Result, Error = command_result::Result>,
        Fut: Future<Output = Result<tonic::Response<CommandResult>, tonic::Status>>,
    >(
        &mut self,
        tailers: &mut Option<FileTailers>,
        f: impl FnOnce() -> Fut,
    ) -> anyhow::Result<CommandOutcome<Res>> {
        let res = self.flushing_tailers(tailers, f).await?;
        // important - do not early return before flushing the buffers!
        let inner = res?.into_inner();
        self.handle_command_result(&inner).await?;

        convert_result(inner)
    }

    /// Helper method to abstract the process of applying an `EventSubscriber` method to all of the subscribers.
    /// Quits on the first error encountered.
    async fn handle_subscribers<'b, Fut>(
        &'b mut self,
        f: impl FnMut(&'b mut Box<dyn EventSubscriber + 'a>) -> Fut,
    ) -> anyhow::Result<()>
    where
        Fut: Future<Output = anyhow::Result<()>> + 'b,
    {
        let mut futures: FuturesUnordered<_> = self.subscribers.iter_mut().map(f).collect();
        while let Some(res) = futures.next().await {
            res?;
        }
        Ok(())
    }

    async fn handle_error_owned(&mut self, error: anyhow::Error) -> anyhow::Error {
        let result = self
            .handle_subscribers(|subscriber| subscriber.handle_error(&error))
            .await;
        match result {
            Ok(()) => error,
            Err(e) => EventsCtxError::WrappedStreamError {
                source: error,
                other: e,
            }
            .into(),
        }
    }

    pub async fn flush(&mut self, tailers: &mut Option<FileTailers>) -> anyhow::Result<()> {
        let tailers = match tailers.take() {
            Some(tailers) => tailers,
            None => return Ok(()),
        };
        let mut streams = tailers.stop_reading();

        // We need to loop again to drain stdout/stderr
        let mut complete = false;
        while !complete {
            tokio::select! {
                event = streams.recv() => {
                    match event {
                        Some(event) => {self.dispatch_tailer_event(event).await?;}
                        None => {complete = true;}
                    }
                }
                tick = self.ticker.tick() => {
                    self.tick(&tick).await?;
                }
            }
        }

        let tick = self.ticker.tick_now();
        self.tick(&tick).await?;

        Ok(())
    }
}

/// Convert a CommandResult into a CommandOutcome after the CommandResult has been printed by `handle_command_result`.
fn convert_result<R: TryFrom<command_result::Result, Error = command_result::Result>>(
    value: CommandResult,
) -> anyhow::Result<CommandOutcome<R>> {
    match value.result {
        Some(command_result::Result::Error(_)) => Ok(CommandOutcome::Failure(None)),
        Some(value) => match value.try_into() {
            Ok(v) => Ok(CommandOutcome::Success(v)),
            Err(res) => Err(BuckdCommunicationError::UnexpectedResultType(res).into()),
        },
        None => Err(BuckdCommunicationError::EmptyCommandResult.into()),
    }
}

impl<'a> EventsCtx<'a> {
    async fn handle_tailer_stdout(&mut self, raw_output: &[u8]) -> anyhow::Result<()> {
        self.handle_subscribers(|subscriber| subscriber.handle_output(raw_output))
            .await
    }

    async fn handle_tailer_stderr(&mut self, stderr: &[u8]) -> anyhow::Result<()> {
        let stderr = String::from_utf8_lossy(stderr);
        let stderr = stderr.trim_end();
        self.handle_subscribers(|subscriber| subscriber.handle_tailer_stderr(stderr))
            .await
    }

    async fn handle_console_interaction(&mut self, c: char) -> anyhow::Result<()> {
        self.handle_subscribers(|subscriber| subscriber.handle_console_interaction(c))
            .await
    }

    async fn handle_events(
        &mut self,
        events: Vec<BuckEvent>,
        shutdown: &mut Option<buck2_data::DaemonShutdown>,
    ) -> anyhow::Result<()> {
        let events = events.into_map(|mut event| {
            let timestamp = event.timestamp();
            if let buck2_data::buck_event::Data::Instant(instant_event) = event.data_mut() {
                match &mut instant_event.data {
                    Some(buck2_data::instant_event::Data::Snapshot(snapshot)) => {
                        let now = SystemTime::now();
                        // `None` on overflow.
                        let this_event_client_delay_ms = match now.duration_since(timestamp) {
                            Ok(duration) => i64::try_from(duration.as_millis()).ok(),
                            Err(e) => i64::try_from(e.duration().as_millis())
                                .ok()
                                .and_then(|x| x.checked_neg()),
                        };
                        snapshot.this_event_client_delay_ms = this_event_client_delay_ms;

                        snapshot.client_cpu_percents =
                            self.client_cpu_tracker.tick_cpu_time_percents();
                    }
                    Some(buck2_data::instant_event::Data::DaemonShutdown(msg)) => {
                        *shutdown = Some(msg.clone());
                    }
                    _ => {}
                };
            }
            Arc::new(event)
        });
        self.handle_subscribers(|subscriber| subscriber.handle_events(&events))
            .await
    }

    async fn handle_command_result(
        &mut self,
        result: &buck2_cli_proto::CommandResult,
    ) -> anyhow::Result<()> {
        self.handle_subscribers(|subscriber| subscriber.handle_command_result(result))
            .await
    }

    /// This function is called once per `TICK_SPEED`.
    /// A subscriber will have the opportunity to do an arbitrary process at a reliable interval.
    /// In particular, this is crucial for superconsole so that it can draw itself consistently.
    async fn tick(&mut self, tick: &Tick) -> anyhow::Result<()> {
        self.handle_subscribers(|subscriber| subscriber.tick(tick))
            .await
    }

    async fn handle_exit(&mut self) -> anyhow::Result<()> {
        let mut r = Ok(());
        for subscriber in &mut self.subscribers {
            // Exit all subscribers, do not stop on first one.
            let subscriber_err = subscriber.exit().await;
            if r.is_ok() {
                // Keep first error.
                r = subscriber_err;
            }
        }
        r
    }
}

impl FileTailers {
    pub fn new(daemon_dir: &DaemonDir) -> anyhow::Result<Self> {
        let (tx, rx) = mpsc::unbounded_channel();
        let stdout_tailer = FileTailer::tail_file(
            daemon_dir.buckd_stdout(),
            tx.clone(),
            StdoutOrStderr::Stdout,
        )?;
        let stderr_tailer =
            FileTailer::tail_file(daemon_dir.buckd_stderr(), tx, StdoutOrStderr::Stderr)?;
        let this = Self {
            _stdout_tailer: Some(stdout_tailer),
            _stderr_tailer: Some(stderr_tailer),
            stream: rx,
        };
        Ok(this)
    }

    pub fn empty() -> FileTailers {
        FileTailers {
            _stdout_tailer: None,
            _stderr_tailer: None,
            // Empty stream.
            stream: mpsc::unbounded_channel().1,
        }
    }

    pub fn stop_reading(self) -> UnboundedReceiver<FileTailerEvent> {
        // by dropping the tailers, they shut themselves down.
        self.stream
    }
}

#[derive(Error, Debug)]
pub enum EventsCtxError {
    #[error("While propagating error:\n{source:#?}, another error was detected:\n{other:#?}")]
    WrappedStreamError {
        #[source]
        source: anyhow::Error,
        other: anyhow::Error,
    },
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::convert::Infallible;
use std::ffi::CString;
use std::fmt::Display;
use std::io;
use std::io::Write;
use std::ops::FromResidual;
use std::process::Command;

use anyhow::Context;
use buck2_cli_proto::command_result;
use dupe::Dupe;
use gazebo::prelude::*;

use crate::subscribers::observer::ErrorCause;

pub struct ExecArgs {
    prog: String,
    argv: Vec<String>,
    chdir: Option<String>,
    env: Vec<(String, String)>,
}

/// ExitResult represents the outcome of a process execution where we care to return a specific
/// exit code. This is designed to be used as the return value from `main()`.
///
/// The exit code is u8 integer and has the following meanings
/// - Success             : 0
/// - Uncategorized Error : 1
/// - Infra Error         : 2
/// - User Error          : 3
/// - Signal Interruption : 129-192 (128 + signal number)
///
/// We can easily turn a anyhow::Result (or anyhow::Error, or even a message) into a ExitResult,
/// but the reverse is not possible: once created, the only useful thing we can with a
/// ExitResult is propagate it.
#[must_use]
pub struct ExitResult {
    variant: ExitResultVariant,

    /// Some stdout output that should be emitted prior to exiting. This allows commands to buffer
    /// their final output and choose not to send it if we opt to restart the command.
    stdout: Vec<u8>,
}

enum ExitResultVariant {
    /// We finished successfully, return the specific exit code.
    Status(u8),
    /// The command failed and it doesn't have a specific exit code yet. This may be updated by
    /// `ErrorObserver::error_cause` if more accurate categorization is available after the
    /// command ends. If no categorization succeeded, it will return exit code 1.
    UncategorizedError,
    /// Instead of terminating normally, `exec` a new process with the given name and argv.
    Exec(ExecArgs),
    /// We failed (i.e. due to a Buck internal error).
    /// At this time, when execution does fail, we print out the error message to stderr.
    Err(anyhow::Error),
}

impl ExitResult {
    pub fn success() -> Self {
        Self::status(0)
    }

    pub fn failure() -> Self {
        Self {
            variant: ExitResultVariant::UncategorizedError,
            stdout: Vec::new(),
        }
    }

    pub fn status(status: u8) -> Self {
        Self {
            variant: ExitResultVariant::Status(status),
            stdout: Vec::new(),
        }
    }

    /// Values out of the range of u8 will have their status information ignored
    pub fn status_extended(status: i32) -> Self {
        if let Ok(code) = status.try_into() {
            Self::status(code)
        } else {
            // The exit code isn't an allowable value, so just switch to generic failure
            Self::failure()
        }
    }

    pub fn exec(
        prog: String,
        argv: Vec<String>,
        chdir: Option<String>,
        env: Vec<(String, String)>,
    ) -> Self {
        Self {
            variant: ExitResultVariant::Exec(ExecArgs {
                prog,
                argv,
                chdir,
                env,
            }),
            stdout: Vec::new(),
        }
    }

    pub fn bail(msg: impl Display) -> Self {
        Self::err(anyhow::anyhow!("Command failed: {}", msg))
    }

    pub fn err(err: anyhow::Error) -> Self {
        Self {
            variant: ExitResultVariant::Err(err),
            stdout: Vec::new(),
        }
    }

    pub fn infer(result: &command_result::Result) -> Self {
        let exit_code = match result {
            command_result::Result::BuildResponse(response) => {
                if response.error_messages.is_empty() {
                    0
                } else {
                    1
                }
            }
            _ => 0,
        };
        Self::status(exit_code)
    }

    /// Return this ExitStatus or call a function to produce a new one.
    pub fn or_else(self, f: impl FnOnce(Self) -> Self) -> Self {
        if matches!(self.variant, ExitResultVariant::Status(0)) {
            return self;
        }

        f(self)
    }

    /// Return this ExitStatus if it's not Uncategorized, or produce a new exit code.
    pub fn categorized_or_else(mut self, f: impl FnOnce() -> u8) -> Self {
        if matches!(self.variant, ExitResultVariant::UncategorizedError) {
            self.variant = ExitResultVariant::Status(f());
        }

        self
    }

    pub fn with_stdout(mut self, stdout: Vec<u8>) -> Self {
        self.stdout.extend(stdout);
        self
    }

    pub fn report(self) -> ! {
        match crate::stdio::print_bytes(&self.stdout) {
            Ok(()) => self.variant.report(),
            Err(e) => ExitResultVariant::Err(e).report(),
        }
    }
}

/// We can produce a ExitResult from a `anyhow::Result` for convenience.
impl From<anyhow::Result<()>> for ExitResult {
    fn from(e: anyhow::Result<()>) -> Self {
        match e {
            Ok(()) => Self::success(),
            Err(e) => Self::err(e),
        }
    }
}

impl From<anyhow::Result<u8>> for ExitResult {
    fn from(e: anyhow::Result<u8>) -> Self {
        match e {
            Ok(code) => Self::status(code),
            Err(e) => Self::err(e),
        }
    }
}

impl From<FailureExitCode> for ExitResult {
    fn from(e: FailureExitCode) -> Self {
        Self::err(e.into())
    }
}

impl FromResidual<anyhow::Error> for ExitResult {
    #[track_caller]
    fn from_residual(residual: anyhow::Error) -> ExitResult {
        Self::err(residual)
    }
}

impl<E: Into<::anyhow::Error>> FromResidual<Result<Infallible, E>> for ExitResult {
    #[track_caller]
    fn from_residual(residual: Result<Infallible, E>) -> ExitResult {
        match residual {
            Ok(infallible) => match infallible {},
            // E -> anyhow::Error -> ExitResult
            Err(e) => Self::err(e.into()),
        }
    }
}

/// Implementing Termination lets us set the exit code for the process.
impl ExitResultVariant {
    pub fn report(self) -> ! {
        // NOTE: We use writeln instead of println so we don't panic if stderr is closed. This
        // ensures we get the desired exit code printed instead of potentially a panic.
        let mut exit_code = match self {
            Self::Status(v) => v,
            Self::UncategorizedError => 1,
            Self::Exec(args) => {
                // Terminate by exec-ing a new process - usually because of `buck2 run`.
                //
                // execv does not return on successful operation, so it always returns an error.
                match execv(args) {
                    Ok(status) => status.report(),
                    Err(e) => Self::Err(e).report(),
                };
            }
            Self::Err(e) => {
                match e.downcast_ref::<FailureExitCode>() {
                    None => {
                        let _ignored = writeln!(io::stderr().lock(), "Command failed: {:?}", e);
                        1
                    }
                    Some(FailureExitCode::SignalInterrupt) => {
                        tracing::debug!("Interrupted");
                        130
                    }
                    Some(FailureExitCode::StdoutBrokenPipe) => {
                        // Report a broken pipe, but don't print anything to stderr by default. If
                        // the user wants to find out why we exited non-zero, they'll have to look
                        // at the output or raise the log level.
                        tracing::debug!("stdout pipe was broken");
                        141
                    }
                    Some(FailureExitCode::StderrBrokenPipe) => {
                        // Not much point in printing anything here, since we know stderr is
                        // closed.
                        141
                    }
                    Some(FailureExitCode::OutputFileBrokenPipe) => {
                        tracing::debug!("--out pipe was broken");
                        141
                    }
                }
            }
        };

        // Global destructors in C++ dependencies destroy global state,
        // while running background threads rely on this state.
        // So the result is non-reproducible crash of the buck2 client.
        // https://fburl.com/7u7kizm7
        // So let's disable global destructors.
        // Global destructors are hard (if even possible) to do safely anyway.

        if io::stdout().flush().is_err() {
            exit_code = 141;
        }

        // Stderr should be autoflushed, but just in case...
        if io::stderr().flush().is_err() {
            exit_code = 141;
        }

        unsafe { libc::_exit(exit_code as libc::c_int) }
    }
}

pub fn gen_error_exit_code(cause: ErrorCause) -> u8 {
    match cause {
        ErrorCause::Unknown => 2, // We treat unknown as infra error.
        ErrorCause::Infra => 2,
        ErrorCause::User => 3,
        ErrorCause::DaemonIsBusy => 4, // For exiting concurrent commands of a different state early
    }
}

/// Common exit codes for buck with stronger semantic meanings
#[derive(thiserror::Error, Debug, Copy, Clone, Dupe)]
pub enum FailureExitCode {
    // TODO: Fill in more exit codes from ExitCode.java here. Need to determine
    // how many make sense in v2 versus v1. Some are assuredly unnecessary in v2.
    #[error("Ctrl-c was pressed")]
    SignalInterrupt,

    #[error("Broken pipe writing on stdout")]
    StdoutBrokenPipe,

    #[error("Broken pipe writing on stdout")]
    StderrBrokenPipe,

    #[error("Broken pipe writing build artifact to --out")]
    OutputFileBrokenPipe,
}

/// Invokes the given program with the given argv and replaces the program image with the new program. Does not return
/// in the case of successful execution.
fn execv(args: ExecArgs) -> anyhow::Result<ExitResult> {
    if let Some(dir) = args.chdir {
        // Note here we break `cwd::cwd_will_not_change` promise.
        // This is OK because we immediately call execv after this
        // (otherwise this would be a really bad idea, even without the promise).
        std::env::set_current_dir(dir)?;
    }

    for (k, v) in args.env {
        // Same as above
        std::env::set_var(k, v);
    }

    if cfg!(windows) {
        let status = Command::new(&args.prog)
            .args(&args.argv[1..])
            .status()
            .with_context(|| {
                format!(
                    "Failed to execute target process, running {:?} {:?}",
                    args.prog, args.argv
                )
            })?;
        let code = status.code().unwrap_or(1);
        return Ok(ExitResult::status(code.try_into().unwrap_or(1)));
    } else {
        let argv_cstrs: Vec<CString> = args.argv.try_map(|s| CString::new(s.clone()))?;
        let mut argv_ptrs: Vec<_> = argv_cstrs.map(|cstr| cstr.as_ptr());
        // By convention, execv's second argument is terminated by a null pointer.
        argv_ptrs.push(std::ptr::null());
        let prog_cstr = CString::new(args.prog).context("program name contained a null byte")?;
        unsafe {
            libc::execvp(prog_cstr.as_ptr(), argv_ptrs.as_ptr());
        }
    }

    // `execv` never returns on success; on failure, it sets errno.
    Err(std::io::Error::last_os_error().into())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Seek;
use std::io::SeekFrom;
use std::time::Duration;

use anyhow::Context;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use futures::FutureExt;
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::oneshot;

use crate::events_ctx::FileTailerEvent;
use crate::tokio_runtime_setup::client_tokio_runtime;

pub(crate) enum StdoutOrStderr {
    Stdout,
    Stderr,
}

/// When `tail_file()` is invoked, the FileTailer will open the file and seek
/// to the end. It'll then watch for changes to the file and copy any newly written
/// data to the writer.
///
/// When the tailer is dropped, it will do a final sync of the data to ensure that
/// the tail is up-to-date at that point.
pub(crate) struct FileTailer {
    // This thread is periodically checking the file for new data. When a message is
    // sent on the end_signaller, the thread will do one final sync of data and then exit.
    thread: Option<std::thread::JoinHandle<()>>,
    end_signaller: Option<oneshot::Sender<()>>,
}

impl Drop for FileTailer {
    fn drop(&mut self) {
        // If the thread has exited then don't error here.
        let _ignored = self.end_signaller.take().unwrap().send(());
        match self.thread.take().unwrap().join() {
            Ok(()) => {}
            Err(e) => std::panic::resume_unwind(e),
        }
    }
}

impl FileTailer {
    pub(crate) fn tail_file(
        file: AbsNormPathBuf,
        sender: UnboundedSender<FileTailerEvent>,
        stdout_or_stderr: StdoutOrStderr,
    ) -> anyhow::Result<FileTailer> {
        let mut reader = BufReader::new(
            File::open(&file)
                .with_context(|| format!("Error setting up tailer for {}", file.display()))?,
        );

        reader.seek(SeekFrom::End(0))?;
        let (tx, rx) = tokio::sync::oneshot::channel();
        // Startup a thread that will repeatedly (with a 200ms interval between) copy from
        // the current position to the end of the file.
        // TODO(cjhopman): It would probably be nicer to implement this via inotify/fsevents/etc
        // rather than just repeatedly reading the file, but I tried to use each of
        // https://crates.io/crates/hotwatch and https://crates.io/crates/notify and neither worked.
        let thread = std::thread::spawn(move || {
            let runtime = client_tokio_runtime().unwrap();
            runtime.block_on(async move {
                let mut interval = tokio::time::interval(Duration::from_millis(200));
                let mut rx = rx.fuse();

                let mut completing = false;
                while !completing {
                    tokio::select! {
                        _ = interval.tick() => {},
                        _ = &mut rx => {
                            // This indicates that the FileTailer is being dropped.
                            // drain any remaining output and return.
                            completing = true;
                        }
                    }

                    let mut line = Vec::new();
                    while reader.read_until(b'\n', &mut line).unwrap() != 0 {
                        let event = match stdout_or_stderr {
                            StdoutOrStderr::Stdout => FileTailerEvent::Stdout(line),
                            StdoutOrStderr::Stderr => FileTailerEvent::Stderr(line),
                        };
                        if sender.send(event).is_err() {
                            break;
                        }
                        line = Vec::new();
                    }
                }
            })
        });

        Ok(Self {
            end_signaller: Some(tx),
            thread: Some(thread),
        })
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use superconsole::style::Color;
use superconsole::style::ContentStyle;
use superconsole::style::StyledContent;

/// A way to uniformly print to the console after a command has finished. This should
/// only be used at the end of a command, after the event context from the buckd client
/// is not available.
pub struct FinalConsole {
    is_tty: bool,
}

impl FinalConsole {
    pub fn new_with_tty() -> Self {
        Self { is_tty: true }
    }

    pub fn new_without_tty() -> Self {
        Self { is_tty: false }
    }

    fn stderr_colored(&self, message: &str, color: Color) -> anyhow::Result<()> {
        if self.is_tty {
            let sc = StyledContent::new(
                ContentStyle {
                    foreground_color: Some(color),
                    background_color: None,
                    attributes: Default::default(),
                },
                message,
            );
            crate::eprintln!("{}", sc)?;
        } else {
            crate::eprintln!("{}", message)?;
        }
        Ok(())
    }

    /// Print the given message to stderr, in red if possible
    pub fn print_error(&self, message: &str) -> anyhow::Result<()> {
        self.stderr_colored(message, Color::DarkRed)
    }

    /// Print the given message to stderr, in yellow if possible
    pub fn print_warning(&self, message: &str) -> anyhow::Result<()> {
        self.stderr_colored(message, Color::Yellow)
    }

    /// Print the given message to stderr, in green if possible
    pub fn print_success(&self, message: &str) -> anyhow::Result<()> {
        self.stderr_colored(message, Color::Green)
    }

    /// Print a string directly to stderr with no extra formatting
    pub fn print_stderr(&self, message: &str) -> anyhow::Result<()> {
        crate::eprintln!("{}", message)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use anyhow::Context;
use bytes::BytesMut;
use futures::Stream;
use futures::StreamExt;
use serde::Deserialize;
use serde::Serialize;
use tokio::io::AsyncRead;
use tokio_util::codec::Decoder;
use tokio_util::codec::FramedRead;

/// Reads from input a stream of lsp-like messages and returns them as serde_json serialized strings.
pub fn ide_message_stream<T: AsyncRead, Message: for<'a> Deserialize<'a> + Serialize>(
    input: T,
) -> impl Stream<Item = anyhow::Result<String>> {
    FramedRead::new(
        input,
        LspMessageLikeDecoder::<Message> {
            _marker: std::marker::PhantomData,
        },
    )
    .map(|m| m.and_then(|m| Ok(serde_json::to_string(&m)?)))
}

pub struct LspMessageLikeDecoder<T: for<'a> Deserialize<'a>> {
    _marker: std::marker::PhantomData<T>,
}

impl<T: for<'a> Deserialize<'a>> Decoder for LspMessageLikeDecoder<T> {
    type Item = T;
    type Error = anyhow::Error;

    fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
        // The LSP (and DAP) protocol allows at most 2 headers (Content-Length and Content-Type), but since a
        // header is 2 pointers we allow ourselves quite a few more.
        let mut headers_buff = [httparse::EMPTY_HEADER; 16];

        let (headers_length, headers) =
            match httparse::parse_headers(src, &mut headers_buff).context("Invalid headers")? {
                httparse::Status::Complete(r) => r,
                httparse::Status::Partial => return Ok(None),
            };

        let mut content_length: Option<usize> = None;

        for h in headers {
            if h.name.eq_ignore_ascii_case("Content-Length") {
                content_length = Some(
                    std::str::from_utf8(h.value)
                        .context("Content-Length is not utf-8")?
                        .parse()
                        .context("Content-Length is not a number")?,
                );
                break;
            }
        }

        let content_length = content_length.context("Content-Length is missing")?;

        if src.len() < headers_length + content_length {
            return Ok(None);
        }

        let _headers = src.split_to(headers_length);
        let text = src.split_to(content_length);
        Some(serde_json::from_slice(&text).context("Invalid request")).transpose()
    }
}

#[cfg(test)]
mod test {
    use assert_matches::assert_matches;
    use lsp_server::Message;
    use lsp_server::Request;
    use lsp_server::RequestId;

    use super::*;

    #[test]
    fn test_decoder() -> anyhow::Result<()> {
        let r1 = Request {
            id: RequestId::from(1i32),
            method: "m1".into(),
            params: "foobar".into(),
        };

        let r2 = Request {
            id: RequestId::from(2i32),
            method: "m2".into(),
            params: "barbaz".into(),
        };

        let mut bytes = BytesMut::new();

        {
            // This wants a `Write` and BytesMut doesn't do that so just use a Vec.
            let mut tmp = Vec::new();
            Message::Request(r1.clone()).write(&mut tmp)?;
            Message::Request(r2.clone()).write(&mut tmp)?;
            bytes.extend_from_slice(&tmp);
        };

        let mut decoder = LspMessageLikeDecoder {
            _marker: std::marker::PhantomData,
        };

        // Decoding a subset should return None and not consume anything.
        {
            let mut tmp = BytesMut::new();
            tmp.extend_from_slice(&bytes[0..8]);
            assert_matches!(decoder.decode(&mut tmp)?, None);
            assert_eq!(tmp.len(), 8);
        }

        assert_matches!(decoder.decode(&mut bytes)?, Some(Message::Request(Request {
            id, method, params
        })) => {
            assert_eq!(id, r1.id);
            assert_eq!(method, r1.method);
            assert_eq!(params, r1.params);
        });

        assert_matches!(decoder.decode(&mut bytes)?, Some(Message::Request(Request {
            id, method, params
        })) => {
            assert_eq!(id, r2.id);
            assert_eq!(method, r2.method);
            assert_eq!(params, r2.params);
        });

        assert_matches!(decoder.decode(&mut bytes)?, None);
        assert_eq!(bytes.len(), 0);

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use anyhow::Context as _;
use buck2_common::invocation_roots::find_invocation_roots;
use buck2_common::legacy_configs::cells::BuckConfigBasedCells;
use buck2_common::legacy_configs::cells::DaemonStartupConfig;
use buck2_core::cells::CellResolver;
use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::project::ProjectRoot;
use buck2_core::fs::working_dir::WorkingDir;
use once_cell::sync::OnceCell;

/// Lazy-computed immediate config data. This is produced by reading the root buckconfig (but not
/// processing any includes).
struct ImmediateConfigContextData {
    cell_resolver: CellResolver,
    daemon_startup_config: DaemonStartupConfig,
    project_filesystem: ProjectRoot,
}

pub struct ImmediateConfigContext<'a> {
    // Deliberately use `OnceCell` rather than `Lazy` because `Lazy` forces
    // us to have a shared reference to the underlying `anyhow::Error` which
    // we cannot use to correct chain the errors. Using `OnceCell` means
    // we don't get the result by a shared reference but instead as local
    // value which can be returned.
    data: OnceCell<ImmediateConfigContextData>,
    cwd: &'a WorkingDir,
    trace: Vec<AbsNormPathBuf>,
}

impl<'a> ImmediateConfigContext<'a> {
    pub fn new(cwd: &'a WorkingDir) -> Self {
        Self {
            data: OnceCell::new(),
            cwd,
            trace: Vec::new(),
        }
    }

    pub fn push_trace(&mut self, path: &AbsNormPath) {
        self.trace.push(path.to_buf());
    }

    pub fn trace(&self) -> &[AbsNormPathBuf] {
        &self.trace
    }

    pub fn daemon_startup_config(&self) -> anyhow::Result<&DaemonStartupConfig> {
        Ok(&self.data()?.daemon_startup_config)
    }

    /// Resolves an argument which can possibly be a cell-relative path.
    /// If the argument is not a cell-relative path, it returns `None`.
    /// Otherwise, it tries to resolve the cell and returns a `Result`.
    pub fn resolve_cell_path_arg(&self, path: &str) -> Option<anyhow::Result<AbsNormPathBuf>> {
        path.split_once("//")
            .map(|(cell_alias, cell_relative_path)| {
                self.resolve_cell_path(cell_alias, cell_relative_path)
            })
    }

    /// Resolves a cell path (i.e., contains `//`) into an absolute path. The cell path must have
    /// been split into two components: `cell_alias` and `cell_path`. For example, if the cell path
    /// is `cell//path/to/file`, then:
    ///   - `cell_alias` would be `cell`
    ///   - `cell_relative_path` would be `path/to/file`
    pub fn resolve_cell_path(
        &self,
        cell_alias: &str,
        cell_relative_path: &str,
    ) -> anyhow::Result<AbsNormPathBuf> {
        let data = self.data()?;

        data.cell_resolver.resolve_cell_relative_path(
            cell_alias,
            cell_relative_path,
            &data.project_filesystem,
            self.cwd.path(),
        )
    }

    fn data(&self) -> anyhow::Result<&ImmediateConfigContextData> {
        self.data
            .get_or_try_init(|| {
                let roots = find_invocation_roots(self.cwd.path())?;

                // See comment in `ImmediateConfig` about why we use `OnceCell` rather than `Lazy`
                let project_filesystem = roots.project_root;
                let cfg = BuckConfigBasedCells::parse_immediate_config(&project_filesystem)?;

                anyhow::Ok(ImmediateConfigContextData {
                    cell_resolver: cfg.cell_resolver,
                    daemon_startup_config: cfg.daemon_startup_config,
                    project_filesystem,
                })
            })
            .context("Error creating cell resolver")
    }
}