/*
 * 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 module contains support for running actions and asynchronous providers
//!
//! An 'Action' is a unit of work with a set of input files known as 'Artifact's that are required
//! for its execution, and a set of output files called 'BuildArtifact's that are created by its
//! execution. Each 'Action' registered by a rule will only be executed when it's 'BuildArtifact's
//! are requested to be available. It will be guaranteed by the action system that all input
//! 'Artifact's are available before the execution of an 'Action'.
//!
//! 'Actions' struct will act as a general registry where users can create new 'Artifact's that
//! represent the outputs of the execution of their 'Action'. These are 'DeclaredArtifact's that
//! are yet bound to any 'Action's. When 'Action's are registered, they will be bound to their
//! appropriate 'DeclaredArtifact' to create a 'BuildArtifact'
//!
//! An 'Action' can be bound to multiple 'BuildArtifact's, but each 'BuildArtifact' can only be
//! bound to a particular 'Action'.

pub mod artifact;
pub mod box_slice_set;
pub mod calculation;
pub mod execute;
pub mod impls;
pub mod key;
pub mod query;
pub mod registry;

use std::any::Demand;
use std::borrow::Cow;
use std::fmt::Debug;
use std::ops::ControlFlow;
use std::sync::Arc;

use allocative::Allocative;
use async_trait::async_trait;
use buck2_artifact::actions::key::ActionKey;
use buck2_artifact::artifact::build_artifact::BuildArtifact;
use buck2_artifact::artifact::provide_outputs::ProvideOutputs;
use buck2_common::executor_config::CommandExecutorConfig;
use buck2_common::http::counting_client::CountingHttpClient;
use buck2_common::io::IoProvider;
use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_core::category::Category;
use buck2_core::fs::artifact_path_resolver::ArtifactFs;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use buck2_events::dispatch::EventDispatcher;
use buck2_execute::artifact::fs::ExecutorFs;
use buck2_execute::digest_config::DigestConfig;
use buck2_execute::execute::action_digest::ActionDigest;
use buck2_execute::execute::blocking::BlockingExecutor;
use buck2_execute::execute::manager::CommandExecutionManager;
use buck2_execute::execute::prepared::PreparedAction;
use buck2_execute::execute::request::CommandExecutionRequest;
use buck2_execute::execute::result::CommandExecutionResult;
use buck2_execute::materialize::materializer::Materializer;
use buck2_execute::re::manager::ManagedRemoteExecutionClient;
use buck2_file_watcher::mergebase::Mergebase;
use derivative::Derivative;
use derive_more::Display;
use indexmap::indexmap;
use indexmap::IndexMap;
use indexmap::IndexSet;
use more_futures::cancellation::CancellationContext;
use starlark::values::OwnedFrozenValue;
use static_assertions::_core::ops::Deref;
use thiserror::Error;

use crate::actions::execute::action_execution_target::ActionExecutionTarget;
use crate::actions::execute::action_executor::ActionExecutionMetadata;
use crate::actions::execute::action_executor::ActionOutputs;
use crate::actions::impls::run_action_knobs::RunActionKnobs;
use crate::artifact_groups::ArtifactGroup;
use crate::artifact_groups::ArtifactGroupValues;
use crate::deferred::types::AnyValue;
use crate::deferred::types::TrivialDeferred;

/// Represents an unregistered 'Action' that will be registered into the 'Actions' module.
/// The 'UnregisteredAction' is not executable until it is registered, upon which it becomes an
/// 'Action' that is executable.
pub trait UnregisteredAction: Allocative {
    /// consumes the self and becomes a registered 'Action'. The 'Action' will be executable
    /// and no longer bindable to any other 'Artifact's.
    fn register(
        self: Box<Self>,
        inputs: IndexSet<ArtifactGroup>,
        outputs: IndexSet<BuildArtifact>,
        starlark_data: Option<OwnedFrozenValue>,
    ) -> anyhow::Result<Box<dyn Action>>;
}

/// A registered, immutable 'Action' that is fully bound. All it's 'Artifact's, both inputs and
/// outputs are verified to exist.
///
/// The 'Action' can be executed to produce the set of 'BuildArtifact's it declares. Before
/// execution, all input 'Artifact's will be made available to access.
#[async_trait]
pub trait Action: Allocative + Debug + Send + Sync + 'static {
    /// A machine readable kind identifying this type of action.
    fn kind(&self) -> buck2_data::ActionKind;

    /// All the input 'Artifact's, both sources and built artifacts, that are required for
    /// executing this artifact. While nothing enforces it, this should be a pure function.
    fn inputs(&self) -> anyhow::Result<Cow<'_, [ArtifactGroup]>>;

    /// All the outputs this 'Artifact' will generate. Just like inputs, this should be a pure
    /// function.
    fn outputs(&self) -> anyhow::Result<Cow<'_, [BuildArtifact]>>;

    /// Obtains an executable for this action.
    fn as_executable(&self) -> ActionExecutable<'_>;

    /// A machine-readable category for this action, intended to be used when analyzing actions outside of buck2 itself.
    ///
    /// A category provides a namespace for identifiers within the rule that produced this action. Examples of
    /// categories would be things such as `cxx_compile`, `cxx_link`, and so on. Categories are user-specified in the
    /// rule implementation; however, buck2 enforces some restrictions on category names.
    fn category(&self) -> &Category;

    /// A machine-readable identifier for this action. Required (but as of now, not yet enforced) to be unique within
    /// a category within a single invocation of a rule. Like categories, identifiers are also user-specified and buck2
    /// ascribes no semantics to them. Examples of category-identifier pairs would be `cxx_compile` + `MyCppFile.cpp`,
    /// reflecting a C++ compiler invocation for a file `MyCppFile.cpp`.
    ///
    /// Not required; if None, only one action will be given in the given category. The user should
    /// be given either control over the identifier or the category.
    fn identifier(&self) -> Option<&str>;

    /// Whether to always print stderr, or only print when a user asks for it.
    fn always_print_stderr(&self) -> bool {
        false
    }

    /// Provides a string name for this action, obtained by combining the provided category and identifier.
    fn name(&self) -> String {
        if let Some(identifier) = self.identifier() {
            format!("{} {}", self.category(), identifier)
        } else {
            self.category().to_string()
        }
    }

    fn aquery_attributes(&self, _fs: &ExecutorFs) -> IndexMap<String, String> {
        indexmap! {}
    }

    // TODO this probably wants more data for execution, like printing a short_name and the target
}

pub enum ActionExecutable<'a> {
    Pristine(&'a dyn PristineActionExecutable),
    Incremental(&'a dyn IncrementalActionExecutable),
}

#[async_trait]
pub trait PristineActionExecutable: Send + Sync + 'static {
    /// Runs the 'Action', where all inputs are available and the output directory has been cleaned
    /// up. Upon success, it is expected that all outputs will be available
    async fn execute(
        &self,
        ctx: &mut dyn ActionExecutionCtx,
    ) -> anyhow::Result<(ActionOutputs, ActionExecutionMetadata)>;
}

#[async_trait]
pub trait IncrementalActionExecutable: Send + Sync + 'static {
    /// Runs the 'Action', where all inputs are available but the output directory may not have
    /// been cleaned up. Upon success, it is expected that all outputs will be available
    async fn execute(
        &self,
        ctx: &mut dyn ActionExecutionCtx,
    ) -> anyhow::Result<(ActionOutputs, ActionExecutionMetadata)>;
}

/// The context for actions to use when executing
#[async_trait]
pub trait ActionExecutionCtx: Send + Sync {
    fn target(&self) -> ActionExecutionTarget<'_>;

    /// An 'ArtifactFs' to be used for managing 'Artifact's
    fn fs(&self) -> &ArtifactFs;

    fn executor_fs(&self) -> ExecutorFs;

    /// A `Materializer` used for expensive materializations
    fn materializer(&self) -> &dyn Materializer;

    fn events(&self) -> &EventDispatcher;

    fn command_execution_manager(&self) -> CommandExecutionManager;

    fn mergebase(&self) -> &Mergebase;

    fn prepare_action(
        &mut self,
        request: &CommandExecutionRequest,
    ) -> anyhow::Result<PreparedAction>;

    async fn action_cache(
        &mut self,
        manager: CommandExecutionManager,
        request: &CommandExecutionRequest,
        prepared_action: &PreparedAction,
    ) -> ControlFlow<CommandExecutionResult, CommandExecutionManager>;

    async fn cache_upload(
        &mut self,
        action_digest: ActionDigest,
        execution_result: &CommandExecutionResult,
    ) -> anyhow::Result<bool>;

    /// Executes a command
    /// TODO(bobyf) this seems like it deserves critical sections?
    async fn exec_cmd(
        &mut self,
        manager: CommandExecutionManager,
        request: &CommandExecutionRequest,
        prepared_action: &PreparedAction,
    ) -> CommandExecutionResult;

    fn unpack_command_execution_result(
        &mut self,
        request: &CommandExecutionRequest,
        result: CommandExecutionResult,
        allows_cache_upload: bool,
    ) -> anyhow::Result<(ActionOutputs, ActionExecutionMetadata)>;

    /// Clean up all the output directories for this action. This requires a mutable reference
    /// because you shouldn't be doing anything else with the ActionExecutionCtx while cleaning the
    /// outputs.
    async fn cleanup_outputs(&mut self) -> anyhow::Result<()>;

    /// Get the value of an Artifact. This Artifact _must_ have been declared
    /// as an input to the associated action or a panic will be raised.
    fn artifact_values(&self, input: &ArtifactGroup) -> &ArtifactGroupValues;

    fn blocking_executor(&self) -> &dyn BlockingExecutor;

    fn re_client(&self) -> ManagedRemoteExecutionClient;

    fn digest_config(&self) -> DigestConfig;

    /// Obtain per-command knobs for RunAction.
    fn run_action_knobs(&self) -> RunActionKnobs;

    fn cancellation_context(&self) -> &CancellationContext;

    /// I/O layer access to add non-source files (e.g. downloaded files) to
    /// offline archive trace. If None, tracing is not enabled.
    fn io_provider(&self) -> Arc<dyn IoProvider>;

    /// Http client used for fetching and downloading remote artifacts.
    fn http_client(&self) -> CountingHttpClient;
}

#[derive(Error, Debug)]
pub enum ActionErrors {
    #[error("Output path for artifact or metadata file cannot be empty.")]
    EmptyOutputPath,
    #[error(
        "Multiple artifacts and/or metadata files are declared at the same output location `{0}` declared at `{1}`."
    )]
    ConflictingOutputPath(ForwardRelativePathBuf, String),
    #[error(
        "Multiple artifacts and/or metadata files are declared at conflicting output locations. Output path `{0}` conflicts with the following output paths: {1:?}."
    )]
    ConflictingOutputPaths(ForwardRelativePathBuf, Vec<String>),
    #[error(
        "Action category `{0}` contains duplicate identifier `{1}`; category-identifier pairs must be unique within a rule"
    )]
    ActionCategoryIdentifierNotUnique(Category, String),
    #[error(
        "Analysis produced multiple actions with category `{0}` and at least one of them had no identifier. Add an identifier to these actions to disambiguate them"
    )]
    ActionCategoryDuplicateSingleton(Category),
}

#[derive(Derivative, Debug, Display, Allocative)]
#[derivative(Eq, Hash, PartialEq)]
#[display(fmt = "Action(key={}, name={})", key, "action.name()")]
pub struct RegisteredAction {
    /// The key uniquely identifies a registered action.
    /// The key to the action is a one to one mapping.
    key: ActionKey,
    #[derivative(Hash = "ignore", PartialEq = "ignore")]
    action: Box<dyn Action>,
    #[derivative(Hash = "ignore", PartialEq = "ignore")]
    executor_config: Arc<CommandExecutorConfig>,
}

impl TrivialDeferred for Arc<RegisteredAction> {
    fn as_any_value(&self) -> &dyn AnyValue {
        self
    }

    fn provide<'a>(&'a self, demand: &mut Demand<'a>) {
        demand.provide_value_with(|| {
            ProvideOutputs(
                self.action
                    .outputs()
                    .map(|outputs| outputs.iter().cloned().collect()),
            )
        });
    }
}

impl RegisteredAction {
    pub fn new(
        key: ActionKey,
        action: Box<dyn Action>,
        executor_config: Arc<CommandExecutorConfig>,
    ) -> Self {
        Self {
            key,
            action,
            executor_config,
        }
    }

    pub fn action(&self) -> &dyn Action {
        self.action.as_ref()
    }

    /// Gets the target label to the rule that created this action.
    pub fn owner(&self) -> &BaseDeferredKey {
        self.key.deferred_key().owner()
    }

    pub fn key(&self) -> &ActionKey {
        &self.key
    }

    pub fn execution_config(&self) -> &CommandExecutorConfig {
        &self.executor_config
    }

    pub fn category(&self) -> &Category {
        self.action.category()
    }

    pub fn identifier(&self) -> Option<&str> {
        self.action.identifier()
    }
}

impl Deref for RegisteredAction {
    type Target = dyn Action;

    fn deref(&self) -> &Self::Target {
        self.action.as_ref()
    }
}

/// An 'UnregisteredAction' that is stored by the 'ActionRegistry' to be registered.
/// The stored inputs have not yet been validated as bound, but will be validated upon registering.
#[derive(Allocative)]
struct ActionToBeRegistered {
    inputs: IndexSet<ArtifactGroup>,
    outputs: IndexSet<BuildArtifact>,
    action: Box<dyn UnregisteredAction>,
}

impl ActionToBeRegistered {
    fn new<A: UnregisteredAction + 'static>(
        inputs: IndexSet<ArtifactGroup>,
        outputs: IndexSet<BuildArtifact>,
        a: A,
    ) -> Self {
        Self {
            inputs,
            outputs,
            action: Box::new(a),
        }
    }

    fn register(self, starlark_data: Option<OwnedFrozenValue>) -> anyhow::Result<Box<dyn Action>> {
        self.action
            .register(self.inputs, self.outputs, starlark_data)
    }
}


/*
 * 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::borrow::Cow;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::future::Future;
use std::io::Write;
use std::pin::Pin;
use std::sync::Arc;

use buck2_artifact::actions::key::ActionKey;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::CellResolver;
use buck2_core::fs::artifact_path_resolver::ArtifactFs;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use buck2_core::fs::project_rel_path::ProjectRelativePath;
use buck2_core::target::label::TargetLabel;
use buck2_execute::artifact::fs::ExecutorFs;
use buck2_query::query::environment::LabeledNode;
use buck2_query::query::environment::QueryTarget;
use buck2_util::late_binding::LateBinding;
use derivative::Derivative;
use dice::DiceComputations;
use dupe::Dupe;
use gazebo::variants::VariantName;
use indexmap::IndexMap;
use internment::ArcIntern;
use ref_cast::RefCast;
use serde::Serialize;
use serde::Serializer;

use crate::actions::RegisteredAction;
use crate::analysis::AnalysisResult;
use crate::artifact_groups::TransitiveSetProjectionKey;

#[derive(Debug, derive_more::Display, RefCast, Serialize)]
#[repr(transparent)]
#[serde(transparent)]
pub struct ActionAttr(pub(crate) str);

impl ActionAttr {
    pub(crate) fn new(x: &str) -> &Self {
        ActionAttr::ref_cast(x)
    }
}

#[derive(Debug, Eq, PartialEq, Hash)]
pub struct SetProjectionInputsData {
    key: TransitiveSetProjectionKey,
    pub direct: Vec<ActionKey>,
    pub(crate) children: Vec<SetProjectionInputs>,
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct SetProjectionInputs {
    pub node: ArcIntern<SetProjectionInputsData>,
}

// ArcIntern doesn't impl Dupe, but is cheap to copy, so we need to explicitly impl it.
impl Dupe for SetProjectionInputs {}

impl SetProjectionInputs {
    pub fn new(
        key: TransitiveSetProjectionKey,
        direct: Vec<ActionKey>,
        children: Vec<SetProjectionInputs>,
    ) -> Self {
        Self {
            node: ArcIntern::new(SetProjectionInputsData {
                key,
                direct,
                children,
            }),
        }
    }
}

#[derive(Debug)]
pub enum ActionInput {
    ActionKey(ActionKey),
    IndirectInputs(SetProjectionInputs),
}

#[derive(Derivative, Clone, Dupe)]
#[derivative(Debug)]
pub struct ActionQueryNode {
    action: Arc<RegisteredAction>,
    deps: Arc<Vec<ActionInput>>,
    #[derivative(Debug = "ignore")]
    fs: Arc<ArtifactFs>,
}

impl ActionQueryNode {
    pub fn new(action: Arc<RegisteredAction>, deps: Vec<ActionInput>, fs: Arc<ArtifactFs>) -> Self {
        Self {
            action,
            deps: Arc::new(deps),
            fs,
        }
    }

    pub fn attrs(&self) -> IndexMap<String, String> {
        let mut attrs = self.action.action().aquery_attributes(&ExecutorFs::new(
            &self.fs,
            self.action.execution_config().options.path_separator,
        ));
        attrs.insert(
            "executor_configuration".to_owned(),
            self.action.execution_config().executor.to_string(),
        );
        attrs
    }

    pub fn action(&self) -> Arc<RegisteredAction> {
        self.action.dupe()
    }
}

impl LabeledNode for ActionQueryNode {
    type NodeRef = ActionKey;

    fn node_ref(&self) -> &Self::NodeRef {
        self.action.key()
    }
}

impl QueryTarget for ActionQueryNode {
    type Attr<'a> = ActionAttr;

    fn rule_type(&self) -> Cow<str> {
        Cow::Owned(self.action.kind().variant_name().to_ascii_lowercase())
    }

    /// Return the path to the buildfile that defines this target, e.g. `fbcode//foo/bar/TARGETS`
    fn buildfile_path(&self) -> &BuildFilePath {
        // TODO(cjhopman): In addition to implementing this, we should be able to return an anyhow::Error here rather than panicking.
        unimplemented!("buildfile not yet implemented in aquery")
    }

    // TODO(cjhopman): Use existential traits to remove the Box<> once they are stabilized.
    fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        struct Iter<'a> {
            visited: HashSet<&'a SetProjectionInputs>,
            queue: VecDeque<&'a SetProjectionInputs>,
        }

        impl<'a> Iter<'a> {
            fn new<From: Iterator<Item = &'a SetProjectionInputs>>(iter: From) -> Self {
                let mut visited = HashSet::new();
                let mut queue = VecDeque::new();
                for it in iter {
                    if visited.insert(it) {
                        queue.push_back(it);
                    }
                }
                Self { visited, queue }
            }
        }

        impl<'a> Iterator for Iter<'a> {
            type Item = &'a SetProjectionInputs;

            fn next(&mut self) -> Option<Self::Item> {
                self.queue.pop_front().map(|node| {
                    for child in &*node.node.children {
                        if self.visited.insert(child) {
                            self.queue.push_back(child);
                        }
                    }

                    node
                })
            }
        }

        let direct = self.deps.iter().filter_map(|input| match input {
            ActionInput::ActionKey(action_key) => Some(action_key),
            ActionInput::IndirectInputs(..) => None,
        });

        let indirect = Iter::new(self.deps.iter().filter_map(|input| match input {
            ActionInput::ActionKey(..) => None,
            ActionInput::IndirectInputs(val) => Some(val),
        }));

        let indirect = Iter::new(indirect);

        Box::new(direct.chain(indirect.flat_map(|v| v.node.direct.iter())))
    }

    fn exec_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(std::iter::empty())
    }

    fn target_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        self.deps()
    }

    fn attr_any_matches(
        attr: &Self::Attr<'_>,
        filter: &dyn Fn(&str) -> anyhow::Result<bool>,
    ) -> anyhow::Result<bool> {
        filter(&attr.0)
    }

    fn special_attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        mut _func: F,
    ) -> Result<(), E> {
        Ok(())
    }

    fn attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        mut func: F,
    ) -> Result<(), E> {
        func("kind", ActionAttr::new(&self.rule_type()))?;
        func("category", ActionAttr::new(self.action.category().as_str()))?;
        func(
            "identifier",
            ActionAttr::new(self.action.identifier().unwrap_or("")),
        )?;
        // TODO(cjhopman): impl inputs/outputs for actions in aquery
        func("inputs", ActionAttr::new(""))?;
        func("outputs", ActionAttr::new(""))?;

        for (k, v) in self.attrs() {
            func(&k, ActionAttr::new(&v))?;
        }
        Ok(())
    }

    fn map_attr<R, F: FnMut(Option<&Self::Attr<'_>>) -> R>(&self, key: &str, mut func: F) -> R {
        let mut res = None;

        self.attrs_for_each(|k, attr| {
            if k == key {
                res = Some(func(Some(attr)));
            }
            Ok::<(), anyhow::Error>(())
        })
        .unwrap();
        match res {
            Some(v) => v,
            None => func(None),
        }
    }

    fn inputs_for_each<E, F: FnMut(CellPath) -> Result<(), E>>(
        &self,
        mut _func: F,
    ) -> Result<(), E> {
        // TODO(cjhopman): In addition to implementing this, we should be able to return an anyhow::Error here rather than panicking.
        unimplemented!("inputs not yet implemented in aquery")
    }

    fn call_stack(&self) -> Option<String> {
        None
    }

    fn attr_to_string_alternate(&self, attr: &Self::Attr<'_>) -> String {
        format!("{:#}", attr)
    }

    fn attr_serialize<S: Serializer>(
        &self,
        attr: &Self::Attr<'_>,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        attr.serialize(serializer)
    }
}

pub static FIND_MATCHING_ACTION: LateBinding<
    for<'c> fn(
        &'c DiceComputations,
        // Working dir.
        &'c ProjectRelativePath,
        // global_target_platform
        Option<TargetLabel>,
        &'c AnalysisResult,
        // path_after_target_name
        ForwardRelativePathBuf,
    ) -> Pin<
        Box<dyn Future<Output = anyhow::Result<Option<ActionQueryNode>>> + Send + 'c>,
    >,
> = LateBinding::new("FIND_MATCHING_ACTION");

/// Hook to link printer in `buck2_server_commands` from `buck2_audit_server`.
pub static PRINT_ACTION_NODE: LateBinding<
    for<'a> fn(
        stdout: &'a mut (dyn Write + Send),
        action: ActionQueryNode,
        json: bool,
        output_attributes: &'a [String],
        cell_resolver: &'a CellResolver,
    ) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send + 'a>>,
> = LateBinding::new("PRINT_ACTION_NODE");


/*
 * 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::collections::HashSet;
use std::sync::Arc;

use allocative::Allocative;
use buck2_artifact::actions::key::ActionKey;
use buck2_artifact::artifact::artifact_type::DeclaredArtifact;
use buck2_artifact::artifact::artifact_type::OutputArtifact;
use buck2_artifact::deferred::id::DeferredId;
use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_core::category::Category;
use buck2_core::directory;
use buck2_core::directory::Directory;
use buck2_core::directory::DirectoryBuilder;
use buck2_core::directory::DirectoryEntry;
use buck2_core::directory::DirectoryInsertError;
use buck2_core::directory::DirectoryIterator;
use buck2_core::directory::NoDigest;
use buck2_core::fs::buck_out_path::BuckOutPath;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use buck2_execute::execute::request::OutputType;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use dupe::Dupe;
use indexmap::IndexSet;
use starlark::codemap::FileSpan;

use crate::actions::key::ActionKeyExt;
use crate::actions::ActionErrors;
use crate::actions::ActionToBeRegistered;
use crate::actions::RegisteredAction;
use crate::actions::UnregisteredAction;
use crate::analysis::registry::AnalysisValueFetcher;
use crate::artifact_groups::ArtifactGroup;
use crate::deferred::types::DeferredRegistry;
use crate::deferred::types::ReservedTrivialDeferredData;

/// The actions registry for a particular analysis of a rule implementation
#[derive(Allocative)]
pub struct ActionsRegistry {
    owner: BaseDeferredKey,
    action_key: Option<Arc<str>>,
    artifacts: IndexSet<DeclaredArtifact>,
    pending: Vec<(
        ReservedTrivialDeferredData<Arc<RegisteredAction>>,
        ActionToBeRegistered,
    )>,
    execution_platform: ExecutionPlatformResolution,
    claimed_output_paths: DirectoryBuilder<Option<FileSpan>, NoDigest>,
}

impl ActionsRegistry {
    pub fn new(owner: BaseDeferredKey, execution_platform: ExecutionPlatformResolution) -> Self {
        Self {
            owner,
            action_key: None,
            artifacts: Default::default(),
            pending: Default::default(),
            execution_platform,
            claimed_output_paths: DirectoryBuilder::empty(),
        }
    }

    pub fn set_action_key(&mut self, action_key: Arc<str>) {
        self.action_key = Some(action_key);
    }

    pub fn declare_dynamic_output(
        &mut self,
        path: BuckOutPath,
        output_type: OutputType,
    ) -> DeclaredArtifact {
        // We don't want to claim path, because the output belongs to different (outer) context. We
        // also don't care to keep track of the hidden components count since this output will
        // never escape the dynamic lambda.
        DeclaredArtifact::new(path, output_type, 0)
    }

    pub fn claim_output_path(
        &mut self,
        path: &ForwardRelativePath,
        declaration_location: Option<FileSpan>,
    ) -> anyhow::Result<()> {
        fn display_location_opt(location: Option<&FileSpan>) -> &dyn std::fmt::Display {
            location.map_or(&"<unknown>" as _, |l| l as _)
        }

        match self
            .claimed_output_paths
            .insert(path, DirectoryEntry::Leaf(declaration_location))
        {
            Ok(None) => Ok(()),
            Ok(Some(conflict)) => match conflict {
                DirectoryEntry::Leaf(location) => {
                    Err(anyhow::anyhow!(ActionErrors::ConflictingOutputPath(
                        path.to_owned(),
                        display_location_opt(location.as_ref()).to_string(),
                    )))
                }
                DirectoryEntry::Dir(conflict_dir) => {
                    let conflicting_paths = conflict_dir
                        .ordered_walk()
                        .with_paths()
                        .filter_map(|(p, entry)| match entry {
                            DirectoryEntry::Leaf(location) => Some(format!(
                                "{} declared at {}",
                                path.join(p),
                                display_location_opt(location.as_ref()),
                            )),
                            _ => None,
                        })
                        .collect::<Vec<_>>();
                    Err(anyhow::anyhow!(ActionErrors::ConflictingOutputPaths(
                        path.to_owned(),
                        conflicting_paths,
                    )))
                }
            },
            Err(DirectoryInsertError::EmptyPath) => {
                Err(anyhow::anyhow!(ActionErrors::EmptyOutputPath))
            }
            Err(DirectoryInsertError::CannotTraverseLeaf { path: conflict }) => {
                let location = match directory::find(&self.claimed_output_paths, &conflict) {
                    Ok(Some(DirectoryEntry::Leaf(l))) => l.as_ref(),
                    _ => None,
                };

                let conflict = format!(
                    "{} declared at {}",
                    conflict,
                    display_location_opt(location),
                );

                Err(anyhow::anyhow!(ActionErrors::ConflictingOutputPaths(
                    path.to_owned(),
                    vec![conflict],
                )))
            }
        }
    }

    /// Declares a new output file that will be generated by some action.
    pub fn declare_artifact(
        &mut self,
        prefix: Option<ForwardRelativePathBuf>,
        path: ForwardRelativePathBuf,
        output_type: OutputType,
        declaration_location: Option<FileSpan>,
    ) -> anyhow::Result<DeclaredArtifact> {
        let (path, hidden) = match prefix {
            None => (path, 0),
            Some(prefix) => (prefix.join(path), prefix.iter().count()),
        };
        self.claim_output_path(&path, declaration_location)?;
        let out_path =
            BuckOutPath::with_action_key(self.owner.dupe(), path, self.action_key.dupe());
        let declared = DeclaredArtifact::new(out_path, output_type, hidden);
        if !self.artifacts.insert(declared.dupe()) {
            panic!("not expected duplicate artifact after output path was successfully claimed");
        }
        Ok(declared)
    }

    /// Registers the supplied action
    pub fn register<A: UnregisteredAction + 'static>(
        &mut self,
        registry: &mut DeferredRegistry,
        inputs: IndexSet<ArtifactGroup>,
        outputs: IndexSet<OutputArtifact>,
        action: A,
    ) -> anyhow::Result<DeferredId> {
        let reserved = registry.reserve_trivial::<Arc<RegisteredAction>>();

        let mut bound_outputs = IndexSet::with_capacity(outputs.len());
        for output in outputs {
            let bound = output
                .bind(ActionKey::new(reserved.data().dupe()))?
                .as_base_artifact()
                .dupe();
            bound_outputs.insert(bound);
        }
        let id = reserved.data().deferred_key().id();
        self.pending.push((
            reserved,
            ActionToBeRegistered::new(inputs, bound_outputs, action),
        ));

        Ok(id)
    }

    /// Consumes the registry so no more 'Action's can be registered. This returns
    /// an 'ActionAnalysisResult' that holds all the registered 'Action's
    pub fn ensure_bound(
        self,
        registry: &mut DeferredRegistry,
        analysis_value_fetcher: &AnalysisValueFetcher,
    ) -> anyhow::Result<()> {
        for artifact in self.artifacts {
            artifact.ensure_bound()?;
        }

        // Buck2 has an invariant that pairs of categories and identifiers are unique throughout a build. That
        // invariant is enforced here, using observed_names to keep track of the categories and identifiers that we've seen.
        let mut observed_names: HashMap<Category, HashSet<String>> = HashMap::new();
        for (key, a) in self.pending.into_iter() {
            let starlark_data = analysis_value_fetcher.get(key.data().deferred_key().id())?;
            let action_key = ActionKey::new(key.data().dupe());
            let action = a.register(starlark_data)?;
            match (action.category(), action.identifier()) {
                (category, Some(identifier)) => {
                    let existing_identifiers = observed_names
                        .entry(category.clone())
                        .or_insert_with(HashSet::<String>::new);
                    // false -> identifier was already present in the set
                    if !existing_identifiers.insert(identifier.to_owned()) {
                        return Err(ActionErrors::ActionCategoryIdentifierNotUnique(
                            category.clone(),
                            identifier.to_owned(),
                        )
                        .into());
                    }
                }
                (category, None) => {
                    if observed_names
                        .insert(category.clone(), HashSet::new())
                        .is_some()
                    {
                        return Err(ActionErrors::ActionCategoryDuplicateSingleton(
                            category.clone(),
                        )
                        .into());
                    };
                }
            }

            registry.bind_trivial(
                key,
                Arc::new(RegisteredAction::new(
                    action_key,
                    action,
                    (*self.execution_platform.executor_config()?).dupe(),
                )),
            );
        }

        Ok(())
    }

    pub fn testing_artifacts(&self) -> &IndexSet<DeclaredArtifact> {
        &self.artifacts
    }

    pub fn testing_pending(
        &self,
    ) -> impl Iterator<Item = &ReservedTrivialDeferredData<Arc<RegisteredAction>>> {
        self.pending.iter().map(|(reserved, _)| reserved)
    }
}


/*
 * 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 async_trait::async_trait;
use buck2_common::dice::cells::HasCellResolver;
use buck2_common::dice::data::HasIoProvider;
use buck2_core::fs::artifact_path_resolver::ArtifactFs;
use dice::DiceComputations;
use dupe::Dupe;

use crate::context::HasBuildContextData;

#[async_trait]
pub trait GetArtifactFs {
    /// Get the configured ArtifactFs.
    async fn get_artifact_fs(&self) -> anyhow::Result<ArtifactFs>;
}

#[async_trait]
impl GetArtifactFs for DiceComputations {
    async fn get_artifact_fs(&self) -> anyhow::Result<ArtifactFs> {
        let buck_out_path_resolver = self.get_buck_out_path().await?;
        let project_filesystem = self.global_data().get_io_provider().project_root().dupe();
        let buck_path_resolver = self.get_cell_resolver().await?;
        Ok(ArtifactFs::new(
            buck_path_resolver,
            buck_out_path_resolver,
            project_filesystem,
        ))
    }
}


/*
 * 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::time::Instant;

use async_trait::async_trait;
use buck2_artifact::artifact::artifact_type::Artifact;
use buck2_artifact::artifact::build_artifact::BuildArtifact;
use buck2_build_signals::NodeDuration;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_data::ToProtoMessage;
use buck2_events::dispatch::current_span;
use buck2_events::dispatch::span_async;
use buck2_execute::artifact::artifact_dyn::ArtifactDyn;
use buck2_execute::materialize::materializer::HasMaterializer;
use dice::DiceComputations;
use dupe::Dupe;

use crate::actions::artifact::get_artifact_fs::GetArtifactFs;
use crate::build_signals::HasBuildSignals;

#[async_trait]
pub trait ArtifactMaterializer {
    async fn materialize(&self, artifact: &Artifact) -> anyhow::Result<ProjectRelativePathBuf>;

    /// called to materialized the final set of requested artifacts for the build of a target.
    /// This method will render events in superconsole
    async fn try_materialize_requested_artifact(
        &self,
        artifact: &BuildArtifact,
        required: bool,
    ) -> anyhow::Result<()>;
}

#[async_trait]
impl ArtifactMaterializer for DiceComputations {
    async fn materialize(&self, artifact: &Artifact) -> anyhow::Result<ProjectRelativePathBuf> {
        let materializer = self.per_transaction_data().get_materializer();
        let artifact_fs = self.get_artifact_fs().await?;
        let path = artifact.resolve_path(&artifact_fs)?;
        materializer.ensure_materialized(vec![path.clone()]).await?;
        Ok(path)
    }

    async fn try_materialize_requested_artifact(
        &self,
        artifact: &BuildArtifact,
        required: bool,
    ) -> anyhow::Result<()> {
        let materializer = self.per_transaction_data().get_materializer();
        let artifact_fs = self.get_artifact_fs().await?;
        let path = artifact_fs.resolve_build(artifact.get_path());

        let start_event = buck2_data::MaterializeRequestedArtifactStart {
            artifact: Some(artifact.as_proto()),
        };

        span_async(start_event, async move {
            let now = Instant::now();

            let result: anyhow::Result<_> = try {
                if required {
                    materializer.ensure_materialized(vec![path]).await?;
                } else {
                    materializer.try_materialize_final_artifact(path).await?;
                }
            };

            if let Some(signals) = self.per_transaction_data().get_build_signals() {
                let duration = now.elapsed();

                signals.final_materialization(
                    artifact.dupe(),
                    NodeDuration {
                        user: duration,
                        total: duration,
                    },
                    current_span(),
                );
            }

            (
                result,
                buck2_data::MaterializeRequestedArtifactEnd {
                    artifact: Some(artifact.as_proto()),
                },
            )
        })
        .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.
 */

pub mod get_artifact_fs;
pub mod materializer;


/*
 * 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::Write;

use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_core::category::Category;
use buck2_core::fs::buck_out_path::BuckOutScratchPath;
use buck2_data::ToProtoMessage;
use buck2_execute::execute::target::CommandExecutionTarget;
use derivative::Derivative;
use dupe::Dupe;

use crate::actions::RegisteredAction;

/// Indicates why we are executing a given command.
#[derive(Clone, Dupe, Derivative)]
#[derivative(Debug)]
pub struct ActionExecutionTarget<'a> {
    action: &'a RegisteredAction,
}

impl<'a> ActionExecutionTarget<'a> {
    pub(crate) fn new(action: &'a RegisteredAction) -> Self {
        Self { action }
    }

    pub fn owner(&self) -> &'a BaseDeferredKey {
        self.action.owner()
    }

    pub fn category(&self) -> &'a Category {
        self.action.category()
    }

    pub fn identifier(&self) -> Option<&'a str> {
        self.action.identifier()
    }

    pub fn custom_tmpdir(&self) -> BuckOutScratchPath {
        BuckOutScratchPath::new(
            self.action.owner().dupe(),
            self.action.category(),
            self.action.identifier(),
        )
        .unwrap()
    }
}

impl CommandExecutionTarget for ActionExecutionTarget<'_> {
    fn re_action_key(&self) -> String {
        let mut key = String::new();
        write!(
            &mut key,
            "{} {}",
            self.action.owner(),
            self.action.category()
        )
        .unwrap();
        if let Some(ident) = self.action.identifier().as_ref() {
            write!(&mut key, " {}", ident).unwrap();
        }
        key
    }

    fn re_affinity_key(&self) -> String {
        self.action.owner().to_string()
    }

    fn as_proto_action_key(&self) -> buck2_data::ActionKey {
        self.action.key().as_proto()
    }

    fn as_proto_action_name(&self) -> buck2_data::ActionName {
        buck2_data::ActionName {
            category: self.action.category().as_str().to_owned(),
            identifier: self.action.identifier().unwrap_or("").to_owned(),
        }
    }
}


/*
 * 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::Debug;
use std::ops::ControlFlow;
use std::sync::Arc;

use allocative::Allocative;
use anyhow::Context;
use async_trait::async_trait;
use buck2_artifact::artifact::build_artifact::BuildArtifact;
use buck2_common::dice::data::HasIoProvider;
use buck2_common::events::HasEvents;
use buck2_common::executor_config::CommandExecutorConfig;
use buck2_common::http::counting_client::CountingHttpClient;
use buck2_common::http::HasHttpClient;
use buck2_common::io::IoProvider;
use buck2_common::liveliness_observer::NoopLivelinessObserver;
use buck2_core::fs::artifact_path_resolver::ArtifactFs;
use buck2_core::fs::buck_out_path::BuckOutPath;
use buck2_events::dispatch::EventDispatcher;
use buck2_execute::artifact::fs::ExecutorFs;
use buck2_execute::artifact_value::ArtifactValue;
use buck2_execute::digest_config::DigestConfig;
use buck2_execute::digest_config::HasDigestConfig;
use buck2_execute::execute::action_digest::ActionDigest;
use buck2_execute::execute::blocking::BlockingExecutor;
use buck2_execute::execute::blocking::HasBlockingExecutor;
use buck2_execute::execute::cache_uploader::CacheUploadInfo;
use buck2_execute::execute::claim::MutexClaimManager;
use buck2_execute::execute::clean_output_paths::CleanOutputPaths;
use buck2_execute::execute::command_executor::ActionExecutionTimingData;
use buck2_execute::execute::command_executor::CommandExecutor;
use buck2_execute::execute::dice_data::CommandExecutorResponse;
use buck2_execute::execute::dice_data::GetReClient;
use buck2_execute::execute::dice_data::HasCommandExecutor;
use buck2_execute::execute::kind::CommandExecutionKind;
use buck2_execute::execute::manager::CommandExecutionManager;
use buck2_execute::execute::prepared::PreparedAction;
use buck2_execute::execute::prepared::PreparedCommand;
use buck2_execute::execute::request::CommandExecutionRequest;
use buck2_execute::execute::request::OutputType;
use buck2_execute::execute::result::CommandExecutionReport;
use buck2_execute::execute::result::CommandExecutionResult;
use buck2_execute::execute::result::CommandExecutionStatus;
use buck2_execute::materialize::materializer::HasMaterializer;
use buck2_execute::materialize::materializer::Materializer;
use buck2_execute::output_size::OutputCountAndBytes;
use buck2_execute::output_size::OutputSize;
use buck2_execute::re::manager::ManagedRemoteExecutionClient;
use buck2_file_watcher::mergebase::GetMergebase;
use buck2_file_watcher::mergebase::Mergebase;
use derivative::Derivative;
use derive_more::Display;
use dice::DiceComputations;
use dupe::Dupe;
use indexmap::indexmap;
use indexmap::IndexMap;
use itertools::Itertools;
use more_futures::cancellation::CancellationContext;

use crate::actions::artifact::get_artifact_fs::GetArtifactFs;
use crate::actions::execute::action_execution_target::ActionExecutionTarget;
use crate::actions::execute::error::CommandExecutionErrorMarker;
use crate::actions::execute::error::ExecuteError;
use crate::actions::impls::run_action_knobs::HasRunActionKnobs;
use crate::actions::impls::run_action_knobs::RunActionKnobs;
use crate::actions::ActionExecutable;
use crate::actions::ActionExecutionCtx;
use crate::actions::RegisteredAction;
use crate::artifact_groups::ArtifactGroup;
use crate::artifact_groups::ArtifactGroupValues;

/// This is the result of the action as exposed to other things in the dice computation.
#[derive(Clone, Dupe, Debug, PartialEq, Eq, Allocative)]
pub struct ActionOutputs(Arc<ActionOutputsData>);

impl OutputSize for ActionOutputs {
    fn calc_output_count_and_bytes(&self) -> OutputCountAndBytes {
        let mut total_count = 0;
        let mut total_bytes = 0;
        for v in self.values() {
            let count_and_bytes = v.calc_output_count_and_bytes();
            total_count += count_and_bytes.count;
            total_bytes += count_and_bytes.bytes;
        }
        OutputCountAndBytes {
            count: total_count,
            bytes: total_bytes,
        }
    }
}

#[derive(Derivative, Debug, Allocative)]
#[derivative(PartialEq, Eq)]
struct ActionOutputsData {
    outputs: IndexMap<BuckOutPath, ArtifactValue>,
}

/// Metadata associated with the execution of this action.
#[derive(Debug)]
pub struct ActionExecutionMetadata {
    pub execution_kind: ActionExecutionKind,
    pub timing: ActionExecutionTimingData,
}

/// The *way* that a particular action was executed.
#[derive(Debug, Display, Clone)]
pub enum ActionExecutionKind {
    #[display(fmt = "command({})", kind)]
    Command {
        kind: CommandExecutionKind,
        prefers_local: bool,
        requires_local: bool,
        allows_cache_upload: bool,
        did_cache_upload: bool,
        eligible_for_full_hybrid: bool,
    },
    /// This action is simple and executed inline within buck2 (e.g. write, symlink_dir)
    #[display(fmt = "simple")]
    Simple,
    /// This action was not executed at all.
    #[display(fmt = "skipped")]
    Skipped,
    /// This action logically executed, but didn't do all the work.
    #[display(fmt = "deferred")]
    Deferred,
    /// This action was served by the local dep file cache and not executed.
    #[display(fmt = "local_dep_files")]
    LocalDepFile,
}

pub struct CommandExecutionRef<'a> {
    pub kind: &'a CommandExecutionKind,
    pub prefers_local: bool,
    pub requires_local: bool,
    pub allows_cache_upload: bool,
    pub did_cache_upload: bool,
    pub eligible_for_full_hybrid: bool,
}

impl ActionExecutionKind {
    pub fn as_enum(&self) -> buck2_data::ActionExecutionKind {
        match self {
            ActionExecutionKind::Command { kind, .. } => kind.as_enum(),
            ActionExecutionKind::Simple => buck2_data::ActionExecutionKind::Simple,
            ActionExecutionKind::Skipped => buck2_data::ActionExecutionKind::Skipped,
            ActionExecutionKind::Deferred => buck2_data::ActionExecutionKind::Deferred,
            ActionExecutionKind::LocalDepFile => buck2_data::ActionExecutionKind::LocalDepFile,
        }
    }

    pub fn command(&self) -> Option<CommandExecutionRef<'_>> {
        match self {
            Self::Command {
                kind,
                prefers_local,
                requires_local,
                allows_cache_upload,
                did_cache_upload,
                eligible_for_full_hybrid,
            } => Some(CommandExecutionRef {
                kind,
                prefers_local: *prefers_local,
                requires_local: *requires_local,
                allows_cache_upload: *allows_cache_upload,
                did_cache_upload: *did_cache_upload,
                eligible_for_full_hybrid: *eligible_for_full_hybrid,
            }),
            Self::Simple | Self::Skipped | Self::Deferred | Self::LocalDepFile => None,
        }
    }
}

impl ActionOutputs {
    pub fn new(outputs: IndexMap<BuckOutPath, ArtifactValue>) -> Self {
        Self(Arc::new(ActionOutputsData { outputs }))
    }

    pub fn from_single(artifact: BuckOutPath, value: ArtifactValue) -> Self {
        Self::new(indexmap! {artifact => value})
    }

    pub fn get(&self, artifact: &BuckOutPath) -> Option<&ArtifactValue> {
        self.0.outputs.get(artifact)
    }

    pub fn iter(&self) -> impl Iterator<Item = (&BuckOutPath, &ArtifactValue)> {
        self.0.outputs.iter()
    }

    pub fn values(&self) -> impl Iterator<Item = &ArtifactValue> {
        self.0.outputs.values()
    }
}

/// Executes 'Actions'
#[async_trait]
pub trait ActionExecutor: Send + Sync {
    async fn execute(
        &self,
        inputs: IndexMap<ArtifactGroup, ArtifactGroupValues>,
        action: &RegisteredAction,
        cancellation: &CancellationContext,
    ) -> (
        Result<(ActionOutputs, ActionExecutionMetadata), ExecuteError>,
        Vec<CommandExecutionReport>,
    );
}

#[async_trait]
pub trait HasActionExecutor {
    async fn get_action_executor(
        &self,
        config: &CommandExecutorConfig,
    ) -> anyhow::Result<Arc<dyn ActionExecutor>>;
}

#[async_trait]
impl HasActionExecutor for DiceComputations {
    async fn get_action_executor(
        &self,
        executor_config: &CommandExecutorConfig,
    ) -> anyhow::Result<Arc<dyn ActionExecutor>> {
        let artifact_fs = self.get_artifact_fs().await?;
        let digest_config = self.global_data().get_digest_config();

        let CommandExecutorResponse {
            executor,
            platform,
            cache_checker,
            cache_uploader,
        } = self.get_command_executor(&artifact_fs, executor_config)?;
        let blocking_executor = self.get_blocking_executor();
        let materializer = self.per_transaction_data().get_materializer();
        let events = self.per_transaction_data().get_dispatcher().dupe();
        let re_client = self.per_transaction_data().get_re_client();
        let run_action_knobs = self.per_transaction_data().get_run_action_knobs();
        let io_provider = self.global_data().get_io_provider();
        let http_client = self.per_transaction_data().get_http_client();
        let mergebase = self.per_transaction_data().get_mergebase();

        Ok(Arc::new(BuckActionExecutor::new(
            CommandExecutor::new(
                executor,
                cache_checker,
                cache_uploader,
                artifact_fs,
                executor_config.options,
                platform,
                run_action_knobs.enforce_re_timeouts,
            ),
            blocking_executor,
            materializer,
            events,
            re_client,
            digest_config,
            run_action_knobs,
            io_provider,
            http_client,
            mergebase,
        )))
    }
}

pub struct BuckActionExecutor {
    command_executor: CommandExecutor,
    blocking_executor: Arc<dyn BlockingExecutor>,
    materializer: Arc<dyn Materializer>,
    events: EventDispatcher,
    re_client: ManagedRemoteExecutionClient,
    digest_config: DigestConfig,
    run_action_knobs: RunActionKnobs,
    io_provider: Arc<dyn IoProvider>,
    http_client: CountingHttpClient,
    mergebase: Mergebase,
}

impl BuckActionExecutor {
    pub fn new(
        command_executor: CommandExecutor,
        blocking_executor: Arc<dyn BlockingExecutor>,
        materializer: Arc<dyn Materializer>,
        events: EventDispatcher,
        re_client: ManagedRemoteExecutionClient,
        digest_config: DigestConfig,
        run_action_knobs: RunActionKnobs,
        io_provider: Arc<dyn IoProvider>,
        http_client: CountingHttpClient,
        mergebase: Mergebase,
    ) -> Self {
        Self {
            command_executor,
            blocking_executor,
            materializer,
            events,
            re_client,
            digest_config,
            run_action_knobs,
            io_provider,
            http_client,
            mergebase,
        }
    }
}

struct BuckActionExecutionContext<'a> {
    executor: &'a BuckActionExecutor,
    action: &'a RegisteredAction,
    inputs: IndexMap<ArtifactGroup, ArtifactGroupValues>,
    outputs: &'a [BuildArtifact],
    command_reports: &'a mut Vec<CommandExecutionReport>,
    cancellations: &'a CancellationContext<'a>,
}

#[async_trait]
impl ActionExecutionCtx for BuckActionExecutionContext<'_> {
    fn target(&self) -> ActionExecutionTarget<'_> {
        ActionExecutionTarget::new(self.action)
    }

    fn fs(&self) -> &ArtifactFs {
        self.executor.command_executor.fs()
    }

    fn executor_fs(&self) -> ExecutorFs {
        self.executor.command_executor.executor_fs()
    }

    fn materializer(&self) -> &dyn Materializer {
        self.executor.materializer.as_ref()
    }

    fn events(&self) -> &EventDispatcher {
        &self.executor.events
    }

    fn command_execution_manager(&self) -> CommandExecutionManager {
        CommandExecutionManager::new(
            Box::new(MutexClaimManager::new()),
            self.executor.events.dupe(),
            NoopLivelinessObserver::create(),
        )
    }

    fn artifact_values(&self, artifact: &ArtifactGroup) -> &ArtifactGroupValues {
        self.inputs.get(artifact).unwrap_or_else(|| panic!("Internal error: action {:?} tried to grab the artifact {:?} even though it was not an input.", self.action.owner(), artifact))
    }

    fn blocking_executor(&self) -> &dyn BlockingExecutor {
        self.executor.blocking_executor.as_ref()
    }

    fn re_client(&self) -> ManagedRemoteExecutionClient {
        self.executor.re_client.dupe()
    }

    fn digest_config(&self) -> DigestConfig {
        self.executor.digest_config
    }

    fn run_action_knobs(&self) -> RunActionKnobs {
        self.executor.run_action_knobs
    }

    fn cancellation_context(&self) -> &CancellationContext {
        self.cancellations
    }

    fn mergebase(&self) -> &Mergebase {
        &self.executor.mergebase
    }

    fn prepare_action(
        &mut self,
        request: &CommandExecutionRequest,
    ) -> anyhow::Result<PreparedAction> {
        self.executor
            .command_executor
            .prepare_action(request, self.digest_config())
    }

    async fn action_cache(
        &mut self,
        manager: CommandExecutionManager,
        request: &CommandExecutionRequest,
        prepared_action: &PreparedAction,
    ) -> ControlFlow<CommandExecutionResult, CommandExecutionManager> {
        let action = self.target();
        self.executor
            .command_executor
            .action_cache(
                manager,
                &PreparedCommand {
                    target: &action as _,
                    request,
                    prepared_action,
                    digest_config: self.digest_config(),
                },
                self.cancellations,
            )
            .await
    }

    fn unpack_command_execution_result(
        &mut self,
        request: &CommandExecutionRequest,
        result: CommandExecutionResult,
        allows_cache_upload: bool,
    ) -> anyhow::Result<(ActionOutputs, ActionExecutionMetadata)> {
        let CommandExecutionResult {
            outputs,
            report,
            rejected_execution,
            did_cache_upload,
            eligible_for_full_hybrid,
        } = result;
        // TODO (@torozco): The execution kind should be made to come via the command reports too.
        let res = match &report.status {
            CommandExecutionStatus::Success { execution_kind } => {
                let result = (
                    // TODO(T156483516): We should also validate that the outputs match the expected outputs
                    ActionOutputs::new(
                        outputs
                            .into_iter()
                            .filter_map(|(output, value)| {
                                Some((output.into_build_artifact()?.0, value))
                            })
                            .collect(),
                    ),
                    ActionExecutionMetadata {
                        execution_kind: ActionExecutionKind::Command {
                            kind: execution_kind.clone(),
                            prefers_local: request.executor_preference().prefers_local(),
                            requires_local: request.executor_preference().requires_local(),
                            allows_cache_upload,
                            did_cache_upload,
                            eligible_for_full_hybrid,
                        },
                        timing: report.timing.into(),
                    },
                );
                Ok(result)
            }
            _ => Err(CommandExecutionErrorMarker.into()),
        };
        self.command_reports.extend(rejected_execution.into_iter());
        self.command_reports.push(report);
        res
    }

    async fn exec_cmd(
        &mut self,
        manager: CommandExecutionManager,
        request: &CommandExecutionRequest,
        prepared_action: &PreparedAction,
    ) -> CommandExecutionResult {
        let action = self.target();
        self.executor
            .command_executor
            .exec_cmd(
                manager,
                &PreparedCommand {
                    target: &action as _,
                    request,
                    prepared_action,
                    digest_config: self.digest_config(),
                },
                self.cancellations,
            )
            .await
    }

    async fn cache_upload(
        &mut self,
        action_digest: ActionDigest,
        execution_result: &CommandExecutionResult,
    ) -> anyhow::Result<bool> {
        let action = self.target();
        self.executor
            .command_executor
            .cache_upload(
                &CacheUploadInfo {
                    target: &action as _,
                    action_digest,
                    digest_config: self.digest_config(),
                },
                execution_result,
            )
            .await
    }

    async fn cleanup_outputs(&mut self) -> anyhow::Result<()> {
        // Delete all outputs before we start, so things will be clean.
        let output_paths = self
            .outputs
            .iter()
            .map(|o| self.fs().resolve_build(o.get_path()))
            .collect::<Vec<_>>();

        // Invalidate all the output paths this action might provide. Note that this is a bit
        // approximative: we might have previous instances of this action that declared
        // different outputs with a different materialization method that will become invalid
        // now. However, nothing should reference those stale outputs, so while this does not
        // do a good job of cleaning up garbage, it prevents using invalid artifacts.
        self.executor
            .materializer
            .invalidate_many(output_paths.clone())
            .await
            .context("Failed to invalidate output directory")?;

        // Use Eden's clean up API if possible, it is significantly faster on Eden compared with
        // the native method as the API does not load and materialize files or folders
        if let Some(eden_buck_out) = self.executor.materializer.eden_buck_out() {
            eden_buck_out
                .remove_paths_recursive(self.fs().fs(), output_paths, self.cancellations)
                .await?;
        } else {
            self.executor
                .blocking_executor
                .execute_io(
                    Box::new(CleanOutputPaths {
                        paths: output_paths,
                    }),
                    self.cancellations,
                )
                .await
                .context("Failed to cleanup output directory")?;
        }

        Ok(())
    }

    fn io_provider(&self) -> Arc<dyn IoProvider> {
        self.executor.io_provider.dupe()
    }

    fn http_client(&self) -> CountingHttpClient {
        self.executor.http_client.dupe()
    }
}

#[async_trait]
impl ActionExecutor for BuckActionExecutor {
    async fn execute(
        &self,
        inputs: IndexMap<ArtifactGroup, ArtifactGroupValues>,
        action: &RegisteredAction,
        cancellations: &CancellationContext,
    ) -> (
        Result<(ActionOutputs, ActionExecutionMetadata), ExecuteError>,
        Vec<CommandExecutionReport>,
    ) {
        let mut command_reports = Vec::new();

        let res = async {
            let outputs = action.outputs()?;

            let mut ctx = BuckActionExecutionContext {
                executor: self,
                action,
                inputs,
                outputs: outputs.as_ref(),
                command_reports: &mut command_reports,
                cancellations,
            };

            let (result, metadata) = match action.as_executable() {
                ActionExecutable::Pristine(exe) => {
                    ctx.cleanup_outputs().await?;
                    exe.execute(&mut ctx).await?
                }
                ActionExecutable::Incremental(exe) => {
                    // Let the action perform clean up in this case.
                    exe.execute(&mut ctx).await?
                }
            };

            // Check that all the outputs are the right output_type
            for x in outputs.iter() {
                let declared = x.output_type();
                // FIXME: One day we should treat FileOrDirectory as a File, and soft_error if it is a directory
                if declared != OutputType::FileOrDirectory {
                    if let Some(t) = result.0.outputs.get(x.get_path()) {
                        let real = if t.is_dir() {
                            OutputType::Directory
                        } else {
                            OutputType::File
                        };
                        if real != declared {
                            return Err(ExecuteError::WrongOutputType {
                                path: self.command_executor.fs().resolve_build(x.get_path()),
                                declared,
                                real,
                            });
                        }
                    }
                }
            }

            // Check all the outputs were returned, and no additional outputs
            // TODO (T122966509): Check projections here as well
            if !outputs
                .iter()
                .map(|b| b.get_path())
                .eq(result.0.outputs.keys())
            {
                let declared = outputs
                    .iter()
                    .filter(|x| !result.0.outputs.contains_key(x.get_path()))
                    .map(|x| self.command_executor.fs().resolve_build(x.get_path()))
                    .collect();
                let real = result
                    .0
                    .outputs
                    .keys()
                    .filter(|x| {
                        // This is error message, linear search is fine.
                        !outputs.iter().map(|b| b.get_path()).contains(x)
                    })
                    .map(|x| self.command_executor.fs().resolve_build(x))
                    .collect::<Vec<_>>();
                if real.is_empty() {
                    Err(ExecuteError::MissingOutputs { declared })
                } else {
                    Err(ExecuteError::MismatchedOutputs { declared, real })
                }
            } else {
                Ok((result, metadata))
            }
        }
        .await;

        (res, command_reports)
    }
}

#[cfg(test)]
mod tests {
    use std::borrow::Cow;
    use std::sync::atomic::AtomicBool;
    use std::sync::atomic::Ordering;
    use std::sync::Arc;
    use std::sync::Mutex;

    use allocative::Allocative;
    use async_trait::async_trait;
    use buck2_artifact::actions::key::ActionKey;
    use buck2_artifact::artifact::artifact_type::testing::BuildArtifactTestingExt;
    use buck2_artifact::artifact::artifact_type::Artifact;
    use buck2_artifact::artifact::build_artifact::BuildArtifact;
    use buck2_artifact::artifact::source_artifact::SourceArtifact;
    use buck2_artifact::deferred::data::DeferredData;
    use buck2_artifact::deferred::id::DeferredId;
    use buck2_artifact::deferred::key::DeferredKey;
    use buck2_common::cas_digest::CasDigestConfig;
    use buck2_common::executor_config::CommandExecutorConfig;
    use buck2_common::executor_config::CommandGenerationOptions;
    use buck2_common::executor_config::PathSeparatorKind;
    use buck2_common::http::counting_client::CountingHttpClient;
    use buck2_common::http::ClientForTest;
    use buck2_common::io::fs::FsIoProvider;
    use buck2_core::base_deferred_key::BaseDeferredKey;
    use buck2_core::buck_path::path::BuckPath;
    use buck2_core::category::Category;
    use buck2_core::cells::cell_root_path::CellRootPathBuf;
    use buck2_core::cells::name::CellName;
    use buck2_core::cells::paths::CellRelativePath;
    use buck2_core::cells::CellResolver;
    use buck2_core::configuration::data::ConfigurationData;
    use buck2_core::fs::artifact_path_resolver::ArtifactFs;
    use buck2_core::fs::buck_out_path::BuckOutPathResolver;
    use buck2_core::fs::fs_util;
    use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
    use buck2_core::fs::project::ProjectRootTemp;
    use buck2_core::fs::project_rel_path::ProjectRelativePath;
    use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
    use buck2_core::package::package_relative_path::PackageRelativePathBuf;
    use buck2_core::package::PackageLabel;
    use buck2_core::target::label::TargetLabel;
    use buck2_core::target::name::TargetNameRef;
    use buck2_events::dispatch::with_dispatcher_async;
    use buck2_events::dispatch::EventDispatcher;
    use buck2_execute::artifact_value::ArtifactValue;
    use buck2_execute::digest_config::DigestConfig;
    use buck2_execute::execute::blocking::testing::DummyBlockingExecutor;
    use buck2_execute::execute::cache_uploader::NoOpCacheUploader;
    use buck2_execute::execute::clean_output_paths::cleanup_path;
    use buck2_execute::execute::command_executor::ActionExecutionTimingData;
    use buck2_execute::execute::command_executor::CommandExecutor;
    use buck2_execute::execute::prepared::NoOpCommandExecutor;
    use buck2_execute::execute::request::CommandExecutionInput;
    use buck2_execute::execute::request::CommandExecutionOutput;
    use buck2_execute::execute::request::CommandExecutionPaths;
    use buck2_execute::execute::request::CommandExecutionRequest;
    use buck2_execute::execute::request::OutputType;
    use buck2_execute::execute::testing_dry_run::DryRunExecutor;
    use buck2_execute::materialize::nodisk::NoDiskMaterializer;
    use buck2_execute::re::manager::ManagedRemoteExecutionClient;
    use dupe::Dupe;
    use indexmap::indexset;
    use more_futures::cancellation::CancellationContext;
    use once_cell::sync::Lazy;
    use sorted_vector_map::SortedVectorMap;

    use crate::actions::box_slice_set::BoxSliceSet;
    use crate::actions::execute::action_executor::ActionExecutionKind;
    use crate::actions::execute::action_executor::ActionExecutionMetadata;
    use crate::actions::execute::action_executor::ActionExecutor;
    use crate::actions::execute::action_executor::ActionOutputs;
    use crate::actions::execute::action_executor::BuckActionExecutor;
    use crate::actions::key::ActionKeyExt;
    use crate::actions::Action;
    use crate::actions::ActionExecutable;
    use crate::actions::ActionExecutionCtx;
    use crate::actions::PristineActionExecutable;
    use crate::actions::RegisteredAction;
    use crate::artifact_groups::ArtifactGroup;
    use crate::artifact_groups::ArtifactGroupValues;
    #[tokio::test]
    async fn can_execute_some_action() {
        let cells = CellResolver::testing_with_name_and_path(
            CellName::testing_new("cell"),
            CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new("cell_path".into())),
        );

        let temp_fs = ProjectRootTemp::new().unwrap();

        let project_fs = temp_fs.path().dupe();
        let artifact_fs = ArtifactFs::new(
            cells,
            BuckOutPathResolver::new(ProjectRelativePathBuf::unchecked_new(
                "cell/buck-out/v2".into(),
            )),
            project_fs.dupe(),
        );

        let tracker = Arc::new(Mutex::new(Vec::new()));

        let executor = BuckActionExecutor::new(
            CommandExecutor::new(
                Arc::new(DryRunExecutor::new(tracker, artifact_fs.clone())),
                Arc::new(NoOpCommandExecutor {}),
                Arc::new(NoOpCacheUploader {}),
                artifact_fs,
                CommandGenerationOptions {
                    path_separator: PathSeparatorKind::Unix,
                    output_paths_behavior: Default::default(),
                },
                Default::default(),
                false,
            ),
            Arc::new(DummyBlockingExecutor {
                fs: project_fs.dupe(),
            }),
            Arc::new(NoDiskMaterializer),
            EventDispatcher::null(),
            ManagedRemoteExecutionClient::testing_new_dummy(),
            DigestConfig::testing_default(),
            Default::default(),
            Arc::new(FsIoProvider::new(
                project_fs,
                CasDigestConfig::testing_default(),
            )),
            CountingHttpClient::new(Arc::new(ClientForTest {})),
            Default::default(),
        );

        #[derive(Debug, Allocative)]
        struct TestingAction {
            inputs: BoxSliceSet<ArtifactGroup>,
            outputs: BoxSliceSet<BuildArtifact>,
            ran: AtomicBool,
        }

        #[async_trait]
        impl Action for TestingAction {
            fn kind(&self) -> buck2_data::ActionKind {
                buck2_data::ActionKind::NotSet
            }

            fn inputs(&self) -> anyhow::Result<Cow<'_, [ArtifactGroup]>> {
                Ok(Cow::Borrowed(self.inputs.as_slice()))
            }

            fn outputs(&self) -> anyhow::Result<Cow<'_, [BuildArtifact]>> {
                Ok(Cow::Borrowed(self.outputs.as_slice()))
            }

            fn as_executable(&self) -> ActionExecutable<'_> {
                ActionExecutable::Pristine(self)
            }

            fn category(&self) -> &Category {
                static TEST_CATEGORY: Lazy<Category> =
                    Lazy::new(|| Category::try_from("testing").unwrap());

                &TEST_CATEGORY
            }

            fn identifier(&self) -> Option<&str> {
                None
            }
        }

        #[async_trait]
        impl PristineActionExecutable for TestingAction {
            async fn execute(
                &self,
                ctx: &mut dyn ActionExecutionCtx,
            ) -> anyhow::Result<(ActionOutputs, ActionExecutionMetadata)> {
                self.ran.store(true, Ordering::SeqCst);

                let req = CommandExecutionRequest::new(
                    vec![],
                    vec!["foo".to_owned(), "bar".to_owned(), "cmd".to_owned()],
                    CommandExecutionPaths::new(
                        self.inputs
                            .iter()
                            .map(|x| {
                                CommandExecutionInput::Artifact(Box::new(
                                    ArtifactGroupValues::from_artifact(
                                        x.unpack_artifact().unwrap().dupe(),
                                        ArtifactValue::file(ctx.digest_config().empty_file()),
                                    ),
                                ))
                            })
                            .collect(),
                        self.outputs
                            .iter()
                            .map(|b| CommandExecutionOutput::BuildArtifact {
                                path: b.get_path().dupe(),
                                output_type: OutputType::FileOrDirectory,
                            })
                            .collect(),
                        ctx.fs(),
                        ctx.digest_config(),
                    )?,
                    SortedVectorMap::new(),
                );

                // on fake executor, this does nothing
                let prepared_action = ctx.prepare_action(&req)?;
                let manager = ctx.command_execution_manager();
                let res = ctx.exec_cmd(manager, &req, &prepared_action).await;

                // Must write out the things we promised to do
                for x in &self.outputs {
                    let dest = x.get_path();
                    let dest_path = ctx.fs().resolve_build(dest);
                    ctx.fs().fs().write_file(&dest_path, "", false)?
                }

                ctx.unpack_command_execution_result(&req, res, false)?;
                let outputs = self
                    .outputs
                    .iter()
                    .map(|o| {
                        (
                            o.get_path().dupe(),
                            ArtifactValue::file(ctx.digest_config().empty_file()),
                        )
                    })
                    .collect();
                Ok((
                    ActionOutputs::new(outputs),
                    ActionExecutionMetadata {
                        execution_kind: ActionExecutionKind::Simple,
                        timing: ActionExecutionTimingData::default(),
                    },
                ))
            }
        }

        let pkg = PackageLabel::new(
            CellName::testing_new("cell"),
            CellRelativePath::unchecked_new("pkg"),
        );

        let inputs = indexset![ArtifactGroup::Artifact(Artifact::from(
            SourceArtifact::new(BuckPath::testing_new(
                pkg.dupe(),
                PackageRelativePathBuf::unchecked_new("source".into()),
            ))
        ))];
        let label = TargetLabel::new(pkg, TargetNameRef::unchecked_new("foo"))
            .configure(ConfigurationData::testing_new());
        let outputs = indexset![BuildArtifact::testing_new(
            label.dupe(),
            ForwardRelativePathBuf::unchecked_new("output".into()),
            DeferredId::testing_new(0),
        )];

        let action = RegisteredAction::new(
            ActionKey::new(DeferredData::unchecked_new(DeferredKey::Base(
                BaseDeferredKey::TargetLabel(label.dupe()),
                DeferredId::testing_new(0),
            ))),
            Box::new(TestingAction {
                inputs: BoxSliceSet::from(inputs),
                outputs: BoxSliceSet::from(outputs.clone()),
                ran: Default::default(),
            }),
            CommandExecutorConfig::testing_local(),
        );
        let res = with_dispatcher_async(
            EventDispatcher::null(),
            executor.execute(Default::default(), &action, CancellationContext::testing()),
        )
        .await
        .0
        .unwrap();
        let outputs = outputs
            .iter()
            .map(|o| {
                (
                    o.get_path().dupe(),
                    ArtifactValue::file(executor.digest_config.empty_file()),
                )
            })
            .collect();
        assert_eq!(res.0, ActionOutputs::new(outputs));
    }

    #[test]
    fn test_cleanup_path_missing() -> anyhow::Result<()> {
        let fs = ProjectRootTemp::new()?;
        let fs = fs.path();
        fs_util::create_dir_all(fs.resolve(ProjectRelativePath::unchecked_new("foo/bar/qux")))?;
        cleanup_path(fs, ProjectRelativePath::unchecked_new("foo/bar/qux/xx"))?;
        assert!(
            fs.resolve(ProjectRelativePath::unchecked_new("foo/bar/qux"))
                .exists()
        );
        Ok(())
    }

    #[test]
    fn test_cleanup_path_present() -> anyhow::Result<()> {
        let fs = ProjectRootTemp::new()?;
        let fs = fs.path();
        fs_util::create_dir_all(fs.resolve(ProjectRelativePath::unchecked_new("foo/bar/qux")))?;
        cleanup_path(fs, ProjectRelativePath::unchecked_new("foo/bar/qux"))?;
        assert!(
            !fs.resolve(ProjectRelativePath::unchecked_new("foo/bar/qux"))
                .exists()
        );
        assert!(
            fs.resolve(ProjectRelativePath::unchecked_new("foo/bar"))
                .exists()
        );
        Ok(())
    }

    #[test]
    fn test_cleanup_path_overlap() -> anyhow::Result<()> {
        let fs = ProjectRootTemp::new()?;
        let fs = fs.path();
        fs.write_file(ProjectRelativePath::unchecked_new("foo/bar"), "xx", false)?;
        cleanup_path(fs, ProjectRelativePath::unchecked_new("foo/bar/qux"))?;
        assert!(
            !fs.resolve(ProjectRelativePath::unchecked_new("foo/bar"))
                .exists()
        );
        assert!(
            fs.resolve(ProjectRelativePath::unchecked_new("foo"))
                .exists()
        );
        Ok(())
    }

    #[test]
    fn test_cleanup_path_overlap_deep() -> anyhow::Result<()> {
        let fs = ProjectRootTemp::new()?;
        let fs = fs.path();
        fs.write_file(ProjectRelativePath::unchecked_new("foo/bar"), "xx", false)?;
        cleanup_path(
            fs,
            ProjectRelativePath::unchecked_new("foo/bar/qux/1/2/3/4"),
        )?;
        assert!(
            !fs.resolve(ProjectRelativePath::unchecked_new("foo/bar"))
                .exists()
        );
        assert!(
            fs.resolve(ProjectRelativePath::unchecked_new("foo"))
                .exists()
        );
        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::fmt::Display;
use std::fmt::Write;

use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_execute::execute::request::OutputType;
use thiserror::Error;

#[derive(Debug)]
pub enum ExecuteError {
    MissingOutputs {
        declared: Vec<ProjectRelativePathBuf>,
    },
    MismatchedOutputs {
        declared: Vec<ProjectRelativePathBuf>,
        real: Vec<ProjectRelativePathBuf>,
    },
    WrongOutputType {
        path: ProjectRelativePathBuf,
        declared: OutputType,
        real: OutputType,
    },
    Error {
        error: anyhow::Error,
    },
    CommandExecutionError,
}

impl ExecuteError {
    pub(crate) fn as_proto(&self) -> buck2_data::action_execution_end::Error {
        match self {
            ExecuteError::MissingOutputs { declared } => buck2_data::CommandOutputsMissing {
                message: format!("Action failed to produce outputs: {}", error_items(declared)),
            }
            .into(),
            ExecuteError::MismatchedOutputs { declared, real } => buck2_data::CommandOutputsMissing {
                message: format!(
                    "Action didn't produce the right set of outputs.\nExpected {}`\nreal {}",
                    error_items(declared),
                    error_items(real)
                ),
            }
            .into(),
            ExecuteError::WrongOutputType {path, declared, real} => buck2_data::CommandOutputsMissing {
                message: format!(
                    "Action didn't produce output of the right type.\nExpected {path} to be {declared:?}\nreal {real:?}",
                ),
            }
            .into(),
            ExecuteError::Error { error } => format!("{:#}", error).into(),
            ExecuteError::CommandExecutionError => buck2_data::CommandExecutionError {}.into(),
        }
    }
}

fn error_items<T: Display>(xs: &[T]) -> String {
    if xs.is_empty() {
        return "none".to_owned();
    }
    let mut res = String::new();
    for (i, x) in xs.iter().enumerate() {
        if i != 0 {
            res.push_str(", ");
        }
        write!(res, "`{}`", x).unwrap();
    }
    res
}

impl From<anyhow::Error> for ExecuteError {
    fn from(error: anyhow::Error) -> Self {
        if error.is::<CommandExecutionErrorMarker>() {
            return Self::CommandExecutionError;
        }
        Self::Error { error }
    }
}

#[derive(Error, Debug)]
#[error("Command execution failed. Details are in the command report.")]
pub struct CommandExecutionErrorMarker;


/*
 * 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.
 */

pub mod action_execution_target;
pub mod action_executor;
pub(crate) mod 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 allocative::Allocative;
use sorted_vector_map::SortedVectorMap;

/// A command line's expansion, suitable to actually run it.
pub struct ExpandedCommandLine {
    pub exe: Vec<String>,
    pub args: Vec<String>,
    pub env: SortedVectorMap<String, String>,
}

/// The digest of an ExpandedCommandLine.
#[derive(Eq, PartialEq, Debug, Allocative)]
pub struct ExpandedCommandLineDigest(
    // This is OK to skip because hash is stored inline.
    #[allocative(skip)] blake3::Hash,
);

impl ExpandedCommandLine {
    /// Obtain a hash of this command line. Conceptually this is as if we serialized the command
    /// line to a length-prefixed list then hashed it, except we never actually produce the
    /// serialized representation.
    pub fn fingerprint(&self) -> ExpandedCommandLineDigest {
        let mut digest = blake3::Hasher::new();

        digest.update(self.exe.len().to_le_bytes().as_slice());
        for e in self.exe.iter() {
            let bytes = e.as_bytes();
            digest.update(bytes.len().to_le_bytes().as_slice());
            digest.update(bytes);
        }

        digest.update(self.args.len().to_le_bytes().as_slice());
        for e in self.args.iter() {
            let bytes = e.as_bytes();
            digest.update(bytes.len().to_le_bytes().as_slice());
            digest.update(bytes);
        }

        digest.update(self.env.len().to_le_bytes().as_slice());
        for (k, v) in self.env.iter() {
            let k_bytes = k.as_bytes();
            digest.update(k_bytes.len().to_le_bytes().as_slice());
            digest.update(k_bytes);

            let v_bytes = v.as_bytes();
            digest.update(v_bytes.len().to_le_bytes().as_slice());
            digest.update(v_bytes);
        }

        ExpandedCommandLineDigest(digest.finalize())
    }
}

#[cfg(test)]
mod test {
    use sorted_vector_map::sorted_vector_map;
    use sorted_vector_map::SortedVectorMap;

    use super::*;

    #[test]
    fn test_cli() {
        let cmd1 = ExpandedCommandLine {
            exe: Default::default(),
            args: vec!["foo".to_owned(), "bar".to_owned()],
            env: Default::default(),
        };

        let cmd2 = ExpandedCommandLine {
            exe: Default::default(),
            args: vec!["foob".to_owned(), "ar".to_owned()],
            env: Default::default(),
        };

        let cmd3 = ExpandedCommandLine {
            exe: Default::default(),
            args: vec!["foobar".to_owned()],
            env: Default::default(),
        };

        let cmd4 = ExpandedCommandLine {
            exe: vec!["foo".to_owned()],
            args: vec!["bar".to_owned()],
            env: Default::default(),
        };

        assert_ne!(cmd1.fingerprint(), cmd2.fingerprint());
        assert_ne!(cmd1.fingerprint(), cmd3.fingerprint());
        assert_ne!(cmd2.fingerprint(), cmd3.fingerprint());
        assert_ne!(cmd3.fingerprint(), cmd4.fingerprint());
    }

    #[test]
    fn test_env() {
        let cmd1 = ExpandedCommandLine {
            exe: Default::default(),
            args: Default::default(),
            env: sorted_vector_map! { "FOO".to_owned() => "BAR".to_owned() },
        };

        let cmd2 = ExpandedCommandLine {
            exe: Default::default(),
            args: Default::default(),
            env: sorted_vector_map! { "FOO2".to_owned()=> "BAR".to_owned() },
        };

        let cmd3 = ExpandedCommandLine {
            exe: Default::default(),
            args: Default::default(),
            env: sorted_vector_map! { "FOO".to_owned()=> "BAR2".to_owned() },
        };

        assert_ne!(cmd1.fingerprint(), cmd2.fingerprint());
        assert_ne!(cmd1.fingerprint(), cmd3.fingerprint());
        assert_ne!(cmd2.fingerprint(), cmd3.fingerprint());
    }

    #[test]
    fn test_hash_stability() {
        fn env() -> SortedVectorMap<String, String> {
            sorted_vector_map! { "FOO1".to_owned() => "BAR1".to_owned(), "FOO2".to_owned() => "BAR2".to_owned() }
        }

        let mut cmd = ExpandedCommandLine {
            exe: Default::default(),
            args: vec!["cmd".to_owned()],
            env: env(),
        };

        let digest = cmd.fingerprint();

        for _ in 0..10 {
            cmd.env = env();
            assert_eq!(cmd.fingerprint(), digest);
        }
    }
}


/*
 * 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::sink;
use std::io::Write;

use anyhow::Context;
use buck2_artifact::artifact::artifact_type::Artifact;
use buck2_execute::artifact::artifact_dyn::ArtifactDyn;
use buck2_execute::artifact::fs::ExecutorFs;
use buck2_interpreter::types::label::Label;
use buck2_interpreter::types::target_label::StarlarkTargetLabel;
use dupe::Dupe;
use serde::Serialize;
use serde::Serializer;
use starlark::values::dict::DictRef;
use starlark::values::enumeration::EnumValue;
use starlark::values::list::ListRef;
use starlark::values::record::Record;
use starlark::values::structs::StructRef;
use starlark::values::tuple::TupleRef;
use starlark::values::UnpackValue;
use starlark::values::Value;
use starlark::values::ValueLike;

use crate::artifact_groups::ArtifactGroup;
use crate::interpreter::rule_defs::artifact::FrozenStarlarkOutputArtifact;
use crate::interpreter::rule_defs::artifact::StarlarkArtifactLike;
use crate::interpreter::rule_defs::artifact::StarlarkOutputArtifact;
use crate::interpreter::rule_defs::artifact::ValueAsArtifactLike;
use crate::interpreter::rule_defs::artifact_tagging::TaggedValue;
use crate::interpreter::rule_defs::cmd_args::value_as::ValueAsCommandLineLike;
use crate::interpreter::rule_defs::cmd_args::CommandLineArgLike;
use crate::interpreter::rule_defs::cmd_args::CommandLineArtifactVisitor;
use crate::interpreter::rule_defs::cmd_args::DefaultCommandLineContext;
use crate::interpreter::rule_defs::cmd_args::FrozenStarlarkCommandLine;
use crate::interpreter::rule_defs::cmd_args::StarlarkCommandLine;
use crate::interpreter::rule_defs::provider::ProviderLike;
use crate::interpreter::rule_defs::provider::ValueAsProviderLike;
use crate::interpreter::rule_defs::transitive_set::TransitiveSetJsonProjection;

/// A wrapper with a Serialize instance so we can pass down the necessary context.
pub struct SerializeValue<'a, 'v> {
    pub value: Value<'v>,
    pub fs: Option<&'a ExecutorFs<'a>>,
}

impl<'a, 'v> SerializeValue<'a, 'v> {
    fn with_value(&self, x: Value<'v>) -> Self {
        Self {
            value: x,
            fs: self.fs,
        }
    }
}

fn err<R, E: serde::ser::Error>(res: anyhow::Result<R>) -> Result<R, E> {
    match res {
        Ok(v) => Ok(v),
        Err(e) => Err(serde::ser::Error::custom(format!("{:#}", e))),
    }
}

/// Grab the value as an artifact, if you can.
/// We want to deal with both normal artifacts, and .as_output() artifacts,
/// since otherwise the .as_output ones will fall through as a cmd_args
/// and end up getting wrapped in a list below.
fn get_artifact<'v>(x: Value<'v>) -> Option<Box<dyn FnOnce() -> anyhow::Result<Artifact> + 'v>> {
    if let Some(x) = ValueAsArtifactLike::unpack_value(x) {
        Some(Box::new(|| Ok(x.0.get_bound_artifact()?.dupe())))
    } else if let Some(x) = x.downcast_ref::<StarlarkOutputArtifact>() {
        Some(Box::new(|| Ok(((*x.inner()).get_bound_artifact())?.dupe())))
    } else if let Some(x) = x.downcast_ref::<FrozenStarlarkOutputArtifact>() {
        Some(Box::new(|| Ok(x.inner().artifact())))
    } else {
        None
    }
}

enum JsonUnpack<'v> {
    None,
    String(&'v str),
    Number(i32),
    Bool(bool),
    List(&'v ListRef<'v>),
    Tuple(&'v TupleRef<'v>),
    Dict(DictRef<'v>),
    Struct(StructRef<'v>),
    Record(&'v Record<'v>),
    Enum(&'v EnumValue<'v>),
    TransitiveSetJsonProjection(&'v TransitiveSetJsonProjection<'v>),
    TargetLabel(&'v StarlarkTargetLabel),
    Label(&'v Label),
    Artifact(Box<dyn FnOnce() -> anyhow::Result<Artifact> + 'v>),
    CommandLine(&'v dyn CommandLineArgLike),
    Provider(&'v dyn ProviderLike<'v>),
    TaggedValue(&'v TaggedValue<'v>),
    Unsupported,
}

fn unpack<'v>(value: Value<'v>) -> JsonUnpack<'v> {
    if value.is_none() {
        JsonUnpack::None
    } else if let Some(x) = value.unpack_str() {
        JsonUnpack::String(x)
    } else if let Some(x) = value.unpack_i32() {
        JsonUnpack::Number(x)
    } else if let Some(x) = value.unpack_bool() {
        JsonUnpack::Bool(x)
    } else if let Some(x) = ListRef::from_value(value) {
        JsonUnpack::List(x)
    } else if let Some(x) = TupleRef::from_value(value) {
        JsonUnpack::Tuple(x)
    } else if let Some(x) = DictRef::from_value(value) {
        JsonUnpack::Dict(x)
    } else if let Some(x) = StructRef::from_value(value) {
        JsonUnpack::Struct(x)
    } else if let Some(x) = Record::from_value(value) {
        JsonUnpack::Record(x)
    } else if let Some(x) = EnumValue::from_value(value) {
        JsonUnpack::Enum(x)
    } else if let Some(x) = TransitiveSetJsonProjection::from_value(value) {
        JsonUnpack::TransitiveSetJsonProjection(x)
    } else if let Some(x) = StarlarkTargetLabel::from_value(value) {
        JsonUnpack::TargetLabel(x)
    } else if let Some(x) = Label::from_value(value) {
        JsonUnpack::Label(x)
    } else if let Some(x) = get_artifact(value) {
        JsonUnpack::Artifact(x)
    } else if let Some(x) = value.as_command_line() {
        JsonUnpack::CommandLine(x)
    } else if let Some(x) = value.as_provider() {
        JsonUnpack::Provider(x)
    } else if let Some(x) = TaggedValue::from_value(value) {
        JsonUnpack::TaggedValue(x)
    } else {
        JsonUnpack::Unsupported
    }
}

impl<'a, 'v> Serialize for SerializeValue<'a, 'v> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match unpack(self.value) {
            JsonUnpack::None => serializer.serialize_none(),
            JsonUnpack::String(x) => serializer.serialize_str(x),
            JsonUnpack::Number(x) => serializer.serialize_i32(x),
            JsonUnpack::Bool(x) => serializer.serialize_bool(x),
            JsonUnpack::List(x) => serializer.collect_seq(x.iter().map(|v| self.with_value(v))),
            JsonUnpack::Tuple(x) => serializer.collect_seq(x.iter().map(|v| self.with_value(v))),
            JsonUnpack::Dict(x) => serializer.collect_map(
                x.iter()
                    .map(|(k, v)| (self.with_value(k), self.with_value(v))),
            ),
            JsonUnpack::Struct(x) => {
                serializer.collect_map(x.iter().map(|(k, v)| (k, self.with_value(v))))
            }
            JsonUnpack::Record(x) => {
                serializer.collect_map(x.iter().map(|(k, v)| (k, self.with_value(v))))
            }
            JsonUnpack::Enum(x) => x.serialize(serializer),
            JsonUnpack::TransitiveSetJsonProjection(x) => {
                serializer.collect_seq(err(x.iter_values())?.map(|v| self.with_value(v)))
            }
            JsonUnpack::TargetLabel(x) => {
                // Users could do this with `str(ctx.label.raw_target())`, but in some benchmarks that causes
                // a lot of additional memory to be retained for all those strings
                x.serialize(serializer)
            }
            JsonUnpack::Label(x) => {
                // Users could do this with `str(ctx.label)`, but a bit wasteful
                x.serialize(serializer)
            }
            JsonUnpack::Artifact(x) => {
                match self.fs {
                    None => {
                        // Invariant: If fs == None, then the writer = sink().
                        // Therefore, in the None branch, we only care about getting Serde errors,
                        // so pass something of the right type, but don't worry about the value.
                        serializer.serialize_str("")
                    }
                    Some(fs) => {
                        serializer.serialize_str(err(err(x())?.resolve_path(fs.fs()))?.as_str())
                    }
                }
            }
            JsonUnpack::CommandLine(x) => {
                let singleton = is_singleton_cmdargs(self.value);
                match self.fs {
                    None => {
                        // See a few lines up for fs == None details.
                        if singleton {
                            serializer.serialize_str("")
                        } else {
                            serializer.collect_seq(&[""])
                        }
                    }
                    Some(fs) => {
                        // WriteJsonCommandLineArgGen assumes that any args/write-to-file macros are
                        // rejected here and needs to be updated if that changes.
                        let mut items = Vec::<String>::new();
                        let mut ctx = DefaultCommandLineContext::new(fs);
                        err(x.add_to_command_line(&mut items, &mut ctx))?;
                        // We change the type, based on the value - singleton = String, otherwise list.
                        // That's a little annoying (type based on value), but otherwise there would be
                        // no way to produce a cmd_args as a single string.
                        if singleton {
                            serializer.serialize_str(&items.concat())
                        } else {
                            serializer.collect_seq(items)
                        }
                    }
                }
            }
            JsonUnpack::Provider(x) => {
                serializer.collect_map(x.items().iter().map(|(k, v)| (k, self.with_value(*v))))
            }
            JsonUnpack::TaggedValue(x) => self.with_value(*x.value()).serialize(serializer),
            JsonUnpack::Unsupported => Err(serde::ser::Error::custom(format!(
                "Type `{}` is not supported by `write_json`",
                self.value.get_type()
            ))),
        }
    }
}

fn is_singleton_cmdargs(x: Value) -> bool {
    if let Some(x) = x.downcast_ref::<StarlarkCommandLine>() {
        x.is_concat()
    } else if let Some(x) = x.downcast_ref::<FrozenStarlarkCommandLine>() {
        x.is_concat()
    } else {
        false
    }
}

pub fn validate_json(x: Value) -> anyhow::Result<()> {
    write_json(x, None, &mut sink())
}

pub fn write_json(x: Value, fs: Option<&ExecutorFs>, writer: impl Write) -> anyhow::Result<()> {
    serde_json::ser::to_writer(writer, &SerializeValue { value: x, fs })
        .context("Error converting to JSON for `write_json`")
}

pub fn visit_json_artifacts(
    v: Value,
    visitor: &mut dyn CommandLineArtifactVisitor,
) -> anyhow::Result<()> {
    match unpack(v) {
        JsonUnpack::None
        | JsonUnpack::String(_)
        | JsonUnpack::Number(_)
        | JsonUnpack::Bool(_)
        | JsonUnpack::TargetLabel(_)
        | JsonUnpack::Enum(_)
        | JsonUnpack::Label(_) => {}

        JsonUnpack::List(x) => {
            for x in x.iter() {
                visit_json_artifacts(x, visitor)?;
            }
        }
        JsonUnpack::Tuple(x) => {
            for x in x.iter() {
                visit_json_artifacts(x, visitor)?;
            }
        }
        JsonUnpack::Dict(x) => {
            for (k, v) in x.iter() {
                visit_json_artifacts(k, visitor)?;
                visit_json_artifacts(v, visitor)?;
            }
        }
        JsonUnpack::Struct(x) => {
            for (_k, v) in x.iter() {
                visit_json_artifacts(v, visitor)?;
            }
        }
        JsonUnpack::Record(x) => {
            for (_k, v) in x.iter() {
                visit_json_artifacts(v, visitor)?;
            }
        }
        JsonUnpack::TransitiveSetJsonProjection(x) => visitor.visit_input(
            ArtifactGroup::TransitiveSetProjection(x.to_projection_key()?),
            None,
        ),
        JsonUnpack::Artifact(_x) => {
            // The _x function requires that the artifact is already bound, but we may need to visit artifacts
            // before that happens. Treating it like an opaque command_line works as we want for any artifact
            // type.
            v.as_command_line_err()?.visit_artifacts(visitor)?;
        }
        JsonUnpack::CommandLine(x) => x.visit_artifacts(visitor)?,
        JsonUnpack::Unsupported => {
            return Err(anyhow::anyhow!(
                "Type `{}` is not supported by `write_json` (this should be unreachable)",
                v.get_type()
            ));
        }
        JsonUnpack::Provider(x) => {
            for (_, v) in x.items() {
                visit_json_artifacts(v, visitor)?;
            }
        }
        JsonUnpack::TaggedValue(v) => {
            let mut visitor = v.wrap_visitor(visitor);
            visit_json_artifacts(*v.value(), &mut visitor)?;
        }
    }
    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.
 */

pub mod expanded_command_line;
pub mod json;
pub mod run_action_knobs;