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

use allocative::Allocative;
use buck2_artifact::artifact::artifact_type::Artifact;
use buck2_build_api::interpreter::rule_defs::artifact::StarlarkPromiseArtifact;
use buck2_core::package::PackageLabel;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_node::attrs::attr_type::arg::ConfiguredStringWithMacros;
use buck2_node::attrs::attr_type::bool::BoolLiteral;
use buck2_node::attrs::attr_type::dep::DepAttr;
use buck2_node::attrs::attr_type::dict::DictLiteral;
use buck2_node::attrs::attr_type::list::ListLiteral;
use buck2_node::attrs::attr_type::string::StringLiteral;
use buck2_node::attrs::attr_type::tuple::TupleLiteral;
use buck2_node::attrs::attr_type::AttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coerced_attr_with_type::CoercedAttrWithType;
use buck2_node::attrs::configuration_context::AttrConfigurationContext;
use buck2_node::attrs::configured_traversal::ConfiguredAttrTraversal;
use buck2_node::attrs::display::AttrDisplayWithContext;
use buck2_node::attrs::fmt_context::AttrFmtContext;
use buck2_node::attrs::json::ToJsonWithContext;
use buck2_node::attrs::serialize::AttrSerializeWithContext;
use dupe::Dupe;
use gazebo::prelude::SliceExt;
use serde::Serialize;
use serde::Serializer;
use serde_json::to_value;

#[derive(Debug, Clone, PartialEq, Eq, Hash, Allocative)]
pub enum AnonTargetAttr {
    Bool(BoolLiteral),
    Int(i32),
    // Note we store `String`, not `Arc<str>` here, because we store full attributes
    // in unconfigured target node, but configured target node is basically a pair
    // (reference to unconfigured target node, configuration).
    //
    // Configured attributes are created on demand and destroyed immediately after use.
    //
    // So when working with configured attributes with pay with CPU for string copies,
    // but don't increase total memory usage, because these string copies are short living.
    String(StringLiteral),
    EnumVariant(StringLiteral),
    List(ListLiteral<AnonTargetAttr>),
    Tuple(TupleLiteral<AnonTargetAttr>),
    Dict(DictLiteral<AnonTargetAttr>),
    None,
    OneOf(
        Box<Self>,
        // Index of matched oneof attr type variant.
        u32,
    ),
    Dep(Box<DepAttr<ConfiguredProvidersLabel>>),
    // Accepts any bound artifacts. Maps to `attr.source()`.
    Artifact(Artifact),
    // Accepts unresolved promise artifacts. Maps to `attr.source()`.
    PromiseArtifact(StarlarkPromiseArtifact),
    Arg(ConfiguredStringWithMacros),
}

impl AttrSerializeWithContext for AnonTargetAttr {
    fn serialize_with_ctx<S>(&self, ctx: &AttrFmtContext, s: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // TODO this is inefficient. We should impl Serialize and derive value from this instead.
        self.to_json(ctx)
            .map_err(|e| serde::ser::Error::custom(format!("{}", e)))?
            .serialize(s)
    }
}

impl AttrDisplayWithContext for AnonTargetAttr {
    fn fmt(&self, ctx: &AttrFmtContext, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            AnonTargetAttr::Bool(v) => {
                write!(f, "{}", v)
            }
            AnonTargetAttr::Int(v) => {
                write!(f, "{}", v)
            }
            AnonTargetAttr::String(v) | AnonTargetAttr::EnumVariant(v) => Display::fmt(v, f),
            AnonTargetAttr::List(list) => AttrDisplayWithContext::fmt(list, ctx, f),
            AnonTargetAttr::Tuple(v) => AttrDisplayWithContext::fmt(v, ctx, f),
            AnonTargetAttr::Dict(v) => AttrDisplayWithContext::fmt(v, ctx, f),
            AnonTargetAttr::None => write!(f, "None"),
            AnonTargetAttr::OneOf(box l, _) => AttrDisplayWithContext::fmt(l, ctx, f),
            AnonTargetAttr::Dep(e) => write!(f, "\"{}\"", e),
            AnonTargetAttr::Artifact(e) => write!(f, "\"{}\"", e),
            AnonTargetAttr::Arg(e) => write!(f, "\"{}\"", e),
            AnonTargetAttr::PromiseArtifact(e) => write!(f, "\"{}\"", e),
        }
    }
}

#[derive(Debug, thiserror::Error)]
enum AnonTargetAttrError {
    #[error("Inconsistent number of elements in tuple")]
    InconsistentTupleLength,
}

impl ToJsonWithContext for AnonTargetAttr {
    fn to_json(&self, ctx: &AttrFmtContext) -> anyhow::Result<serde_json::Value> {
        match self {
            AnonTargetAttr::Bool(v) => Ok(to_value(v)?),
            AnonTargetAttr::Int(v) => Ok(to_value(v)?),
            AnonTargetAttr::String(v) | AnonTargetAttr::EnumVariant(v) => Ok(to_value(v)?),
            AnonTargetAttr::List(list) => list.to_json(ctx),
            AnonTargetAttr::Tuple(list) => list.to_json(ctx),
            AnonTargetAttr::Dict(dict) => dict.to_json(ctx),
            AnonTargetAttr::None => Ok(serde_json::Value::Null),
            AnonTargetAttr::OneOf(box l, _) => l.to_json(ctx),
            AnonTargetAttr::Dep(e) => Ok(to_value(e.to_string())?),
            AnonTargetAttr::Artifact(e) => Ok(to_value(e.to_string())?),
            AnonTargetAttr::Arg(e) => Ok(to_value(e.to_string())?),
            AnonTargetAttr::PromiseArtifact(e) => Ok(to_value(e.to_string())?),
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub(crate) enum AnonTargetFromCoercedAttrError {
    #[error("Anon targets do not support default values for `{0}`")]
    DefaultAttrTypeNotSupported(String),
}

impl AnonTargetAttr {
    /// Traverses the anon target attribute and calls the traverse for every encountered target label (in deps, sources, or other places).
    pub fn traverse<'a>(
        &'a self,
        pkg: PackageLabel,
        traversal: &mut dyn ConfiguredAttrTraversal,
    ) -> anyhow::Result<()> {
        match self {
            AnonTargetAttr::Bool(_) => Ok(()),
            AnonTargetAttr::Int(_) => Ok(()),
            AnonTargetAttr::String(_) => Ok(()),
            AnonTargetAttr::EnumVariant(_) => Ok(()),
            AnonTargetAttr::List(list) => {
                for v in list.iter() {
                    v.traverse(pkg.dupe(), traversal)?;
                }
                Ok(())
            }
            AnonTargetAttr::Tuple(list) => {
                for v in list.iter() {
                    v.traverse(pkg.dupe(), traversal)?;
                }
                Ok(())
            }
            AnonTargetAttr::Dict(dict) => {
                for (k, v) in dict.iter() {
                    k.traverse(pkg.dupe(), traversal)?;
                    v.traverse(pkg.dupe(), traversal)?;
                }
                Ok(())
            }
            AnonTargetAttr::None => Ok(()),
            AnonTargetAttr::OneOf(l, _) => l.traverse(pkg, traversal),
            AnonTargetAttr::Dep(dep) => traversal.dep(&dep.label),
            AnonTargetAttr::Artifact(_) => Ok(()),
            AnonTargetAttr::Arg(e) => e.string_with_macros.traverse(traversal),
            AnonTargetAttr::PromiseArtifact(..) => Ok(()),
        }
    }

    pub fn _unpack_list(&self) -> Option<&[AnonTargetAttr]> {
        match self {
            AnonTargetAttr::List(list) => Some(list),
            _ => None,
        }
    }

    // We need this method for attr defaults in anon targets, which are automatically coerced as CoercedAttrs.
    // Note that non-primitives, such as dep/source, will result in an error.

    // TODO(@wendyy) - find a way to coerce attr defaults used in anon targets directly to AnonTargetAttr
    pub fn from_coerced_attr(
        attr: &CoercedAttr,
        ty: &AttrType,
        ctx: &dyn AttrConfigurationContext,
    ) -> anyhow::Result<AnonTargetAttr> {
        Ok(match CoercedAttrWithType::pack(attr, ty)? {
            CoercedAttrWithType::AnyList(list) => AnonTargetAttr::List(ListLiteral(
                list.try_map(|v| AnonTargetAttr::from_coerced_attr(v, ty, ctx))?
                    .into(),
            )),
            CoercedAttrWithType::AnyTuple(tuple) => AnonTargetAttr::Tuple(TupleLiteral(
                tuple
                    .try_map(|v| AnonTargetAttr::from_coerced_attr(v, ty, ctx))?
                    .into(),
            )),
            CoercedAttrWithType::AnyDict(dict) => AnonTargetAttr::Dict(DictLiteral(
                dict.try_map(|(k, v)| {
                    let k2 = AnonTargetAttr::from_coerced_attr(k, ty, ctx)?;
                    let v2 = AnonTargetAttr::from_coerced_attr(v, ty, ctx)?;
                    anyhow::Ok((k2, v2))
                })?
                .into(),
            )),

            CoercedAttrWithType::Bool(v, _t) => AnonTargetAttr::Bool(v),
            CoercedAttrWithType::Int(v, _t) => AnonTargetAttr::Int(v),
            CoercedAttrWithType::String(v, _t) => AnonTargetAttr::String(v.dupe()),
            CoercedAttrWithType::EnumVariant(v, _t) => AnonTargetAttr::EnumVariant(v.dupe()),
            CoercedAttrWithType::List(list, t) => AnonTargetAttr::List(ListLiteral(
                list.try_map(|v| AnonTargetAttr::from_coerced_attr(v, &t.inner, ctx))?
                    .into(),
            )),
            CoercedAttrWithType::Tuple(list, t) => {
                if list.len() != t.xs.len() {
                    return Err(AnonTargetAttrError::InconsistentTupleLength.into());
                }
                AnonTargetAttr::Tuple(TupleLiteral(
                    list.iter()
                        .zip(&t.xs)
                        .map(|(v, vt)| AnonTargetAttr::from_coerced_attr(v, vt, ctx))
                        .collect::<anyhow::Result<_>>()?,
                ))
            }
            CoercedAttrWithType::Dict(dict, t) => AnonTargetAttr::Dict(DictLiteral(
                dict.try_map(|(k, v)| {
                    let k2 = AnonTargetAttr::from_coerced_attr(k, &t.key, ctx)?;
                    let v2 = AnonTargetAttr::from_coerced_attr(v, &t.value, ctx)?;
                    anyhow::Ok((k2, v2))
                })?
                .into(),
            )),
            CoercedAttrWithType::None => AnonTargetAttr::None,
            CoercedAttrWithType::Some(attr, t) => {
                AnonTargetAttr::from_coerced_attr(attr, &t.inner, ctx)?
            }
            CoercedAttrWithType::OneOf(l, i, t) => {
                let item_ty = &t.xs[i as usize];
                let configured = AnonTargetAttr::from_coerced_attr(l, item_ty, ctx)?;
                AnonTargetAttr::OneOf(Box::new(configured), i)
            }
            _ => {
                return Err(AnonTargetFromCoercedAttrError::DefaultAttrTypeNotSupported(
                    ty.to_string(),
                )
                .into());
            }
        })
    }
}


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

use std::cmp::Ordering;
use std::fmt::Debug;
use std::iter;

use buck2_build_api::interpreter::rule_defs::artifact::starlark_artifact_like::ValueAsArtifactLike;
use buck2_build_api::interpreter::rule_defs::artifact::StarlarkPromiseArtifact;
use buck2_build_api::interpreter::rule_defs::provider::dependency::Dependency;
use buck2_build_api::interpreter::rule_defs::resolved_macro::ResolvedStringWithMacros;
use buck2_core::soft_error;
use buck2_node::attrs::attr_type::bool::BoolLiteral;
use buck2_node::attrs::attr_type::dep::DepAttr;
use buck2_node::attrs::attr_type::dep::DepAttrTransition;
use buck2_node::attrs::attr_type::dict::DictAttrType;
use buck2_node::attrs::attr_type::dict::DictLiteral;
use buck2_node::attrs::attr_type::list::ListAttrType;
use buck2_node::attrs::attr_type::list::ListLiteral;
use buck2_node::attrs::attr_type::one_of::OneOfAttrType;
use buck2_node::attrs::attr_type::string::StringLiteral;
use buck2_node::attrs::attr_type::tuple::TupleAttrType;
use buck2_node::attrs::attr_type::tuple::TupleLiteral;
use buck2_node::attrs::attr_type::AttrType;
use buck2_node::attrs::attr_type::AttrTypeInner;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use dupe::Dupe;
use dupe::IterDupedExt;
use gazebo::prelude::SliceExt;
use starlark::values::dict::Dict;
use starlark::values::dict::DictRef;
use starlark::values::list::ListRef;
use starlark::values::string::STRING_TYPE;
use starlark::values::tuple::TupleRef;
use starlark::values::UnpackValue;
use starlark::values::Value;

use crate::anon_target_attr::AnonTargetAttr;
use crate::anon_targets::AnonAttrCtx;

pub trait AnonTargetAttrTypeCoerce {
    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &AnonAttrCtx,
        value: Value,
    ) -> anyhow::Result<AnonTargetAttr>;
}

impl AnonTargetAttrTypeCoerce for AttrType {
    fn coerce_item(
        &self,
        configurable: AttrIsConfigurable,
        ctx: &AnonAttrCtx,
        value: Value,
    ) -> anyhow::Result<AnonTargetAttr> {
        match self.0.as_ref() {
            AttrTypeInner::Any(_) => to_anon_target_any(value, ctx),
            AttrTypeInner::Bool(_) => match value.unpack_bool() {
                Some(s) => Ok(AnonTargetAttr::Bool(BoolLiteral(s))),
                None => Err(anyhow::anyhow!(AnonTargetCoercionError::type_error(
                    "bool", value
                ))),
            },
            AttrTypeInner::Int(_) => match value.unpack_i32() {
                Some(x) => Ok(AnonTargetAttr::Int(x)),
                None => Err(anyhow::anyhow!(AnonTargetCoercionError::type_error(
                    "int", value
                ))),
            },
            AttrTypeInner::Dict(x) => to_anon_target_dict(x, configurable, ctx, value),
            AttrTypeInner::List(x) => to_anon_target_list(x, configurable, ctx, value),
            AttrTypeInner::Tuple(x) => to_anon_target_tuple(x, configurable, ctx, value),
            AttrTypeInner::OneOf(x) => to_anon_target_one_of(x, configurable, ctx, value),
            AttrTypeInner::Option(x) => {
                if value.is_none() {
                    Ok(AnonTargetAttr::None)
                } else {
                    Ok(x.inner.coerce_item(configurable, ctx, value)?)
                }
            }
            AttrTypeInner::String(_) => match value.unpack_str() {
                Some(s) => Ok(AnonTargetAttr::String(StringLiteral(ctx.intern_str(s)))),
                None => Err(anyhow::anyhow!(AnonTargetCoercionError::type_error(
                    STRING_TYPE,
                    value
                ))),
            },
            AttrTypeInner::Enum(x) => match value.unpack_str() {
                Some(s) => {
                    // Enum names in Buck can be specified upper or lower case,
                    // so we normalise them to lowercase to make rule implementations easier
                    let s = s.to_lowercase();
                    if let Some(s) = x.variants.get(s.as_str()) {
                        Ok(AnonTargetAttr::EnumVariant(StringLiteral(s.dupe())))
                    } else {
                        let wanted = x.variants.iter().map(|x| x.as_str().to_owned()).collect();
                        Err(AnonTargetCoercionError::InvalidEnumVariant(s, wanted).into())
                    }
                }
                None => Err(AnonTargetCoercionError::type_error(STRING_TYPE, value).into()),
            },
            AttrTypeInner::Dep(x) => {
                match Dependency::from_value(value) {
                    Some(dep) => {
                        let label = dep.label().inner().clone();

                        let attr_type = match x.transition {
                            DepAttrTransition::Identity => x.clone(),
                            DepAttrTransition::Exec => {
                                match dep.execution_platform() {
                                Some(exec_dep_resolution) => {
                                    if !exec_dep_resolution.eq(&ctx.execution_platform_resolution) {
                                        return Err(AnonTargetCoercionError::ExecDepPlatformMismatch(exec_dep_resolution.platform()?.id(), ctx.execution_platform_resolution.platform()?.id()).into());
                                    }
                                },
                                None => return Err(AnonTargetCoercionError::ExecDepMissingExecPlatformResolution.into()),
                            }

                                x.clone()
                            }
                            _ => {
                                return Err(
                                    AnonTargetCoercionError::OnlyIdentityDepSupported.into()
                                );
                            }
                        };

                        Ok(AnonTargetAttr::Dep(Box::new(DepAttr { attr_type, label })))
                    }
                    _ => Err(AnonTargetCoercionError::type_error("dependency", value).into()),
                }
            }
            AttrTypeInner::Source(_) => {
                // Check if this is a StarlarkPromiseArtifact first before checking other artifact types to
                // allow anon targets to accept unresolved promise artifacts.
                if let Some(promise_artifact) = StarlarkPromiseArtifact::from_value(value) {
                    Ok(AnonTargetAttr::PromiseArtifact(promise_artifact.clone()))
                } else if let Some(artifact_like) = ValueAsArtifactLike::unpack_value(value) {
                    let artifact = artifact_like.0.get_bound_artifact()?;
                    Ok(AnonTargetAttr::Artifact(artifact))
                } else {
                    Err(AnonTargetCoercionError::type_error("artifact", value).into())
                }
            }
            AttrTypeInner::Arg(_) => match ResolvedStringWithMacros::from_value(value) {
                Some(resolved_macro) => match resolved_macro.configured_macros() {
                    Some(configured_macros) => Ok(AnonTargetAttr::Arg(configured_macros.clone())),
                    None => Err(AnonTargetCoercionError::ArgNotAnonTargetCompatible.into()),
                },
                None => Err(AnonTargetCoercionError::type_error("resolved_macro", value).into()),
            },
            _ => {
                return Err(AnonTargetCoercionError::AttrTypeNotSupported(
                    value.get_type().to_owned(),
                )
                .into());
            }
        }
    }
}

#[derive(Debug, thiserror::Error)]
pub(crate) enum AnonTargetCoercionError {
    #[error("Expected value of type `{0}`, got value with type `{1}` (value was `{2}`)")]
    TypeError(String, String, String),
    #[error("Used one_of with an empty list.")]
    OneOfEmpty,
    #[error("one_of fails, the errors against each alternative in turn were:\n{}", .0.map(|x| format!("{:#}", x)).join("\n"))]
    OneOfMany(Vec<anyhow::Error>),
    #[error("enum called with `{0}`, only allowed: {}", .1.map(|x| format!("`{}`", x)).join(", "))]
    InvalidEnumVariant(String, Vec<String>),
    #[error("Cannot coerce value of type `{0}` to any: `{1}`")]
    CannotCoerceToAny(&'static str, String),
    #[error("Attr value of type `{0}` not supported")]
    AttrTypeNotSupported(String),
    #[error("Arg attribute must have `anon_target_compatible` set to `True`")]
    ArgNotAnonTargetCompatible,
    #[error("Internal error: exec dep is missing the execution platform resolution")]
    ExecDepMissingExecPlatformResolution,
    #[error(
        "Exec deps and the current anon target must have the same execution platform resolution. Exec dep's execution platform: ({0}), anon target's execution platform: ({1})"
    )]
    ExecDepPlatformMismatch(String, String),
    #[error(
        "`transition_dep`, and `toolchain_dep` are not supported. By design, anon targets do not support configurations/transitions."
    )]
    OnlyIdentityDepSupported,
}

impl AnonTargetCoercionError {
    pub(crate) fn type_error(expected_type: &str, value: Value) -> AnonTargetCoercionError {
        AnonTargetCoercionError::TypeError(
            expected_type.to_owned(),
            value.get_type().to_owned(),
            value.to_repr(),
        )
    }

    pub fn one_of_many(mut errs: Vec<anyhow::Error>) -> anyhow::Error {
        if errs.is_empty() {
            AnonTargetCoercionError::OneOfEmpty.into()
        } else if errs.len() == 1 {
            errs.pop().unwrap()
        } else {
            AnonTargetCoercionError::OneOfMany(errs).into()
        }
    }
}

fn to_anon_target_any(value: Value, ctx: &AnonAttrCtx) -> anyhow::Result<AnonTargetAttr> {
    if value.is_none() {
        Ok(AnonTargetAttr::None)
    } else if let Some(x) = value.unpack_bool() {
        Ok(AnonTargetAttr::Bool(BoolLiteral(x)))
    } else if let Some(x) = value.unpack_i32() {
        Ok(AnonTargetAttr::Int(x))
    } else if let Some(x) = DictRef::from_value(value) {
        Ok(AnonTargetAttr::Dict(
            x.iter()
                .map(|(k, v)| Ok((to_anon_target_any(k, ctx)?, to_anon_target_any(v, ctx)?)))
                .collect::<anyhow::Result<_>>()?,
        ))
    } else if let Some(x) = TupleRef::from_value(value) {
        // TODO(wendyy) intern attr
        Ok(AnonTargetAttr::Tuple(TupleLiteral(
            x.iter()
                .map(|v| to_anon_target_any(v, ctx))
                .collect::<anyhow::Result<Vec<_>>>()?
                .into(),
        )))
    } else if let Some(x) = ListRef::from_value(value) {
        // TODO(wendyy) intern attr
        Ok(AnonTargetAttr::List(ListLiteral(
            x.iter()
                .map(|v| to_anon_target_any(v, ctx))
                .collect::<anyhow::Result<Vec<_>>>()?
                .into(),
        )))
    } else if let Some(s) = value.unpack_str() {
        Ok(AnonTargetAttr::String(StringLiteral(ctx.intern_str(s))))
    } else {
        soft_error!(
            "coerce_to_any",
            AnonTargetCoercionError::CannotCoerceToAny(value.get_type(), value.to_repr()).into()
        )?;
        Ok(AnonTargetAttr::String(StringLiteral(
            ctx.intern_str(&value.to_str()),
        )))
    }
}

fn to_anon_target_dict(
    dict_attr_type: &DictAttrType,
    configurable: AttrIsConfigurable,
    ctx: &AnonAttrCtx,
    value: Value,
) -> anyhow::Result<AnonTargetAttr> {
    if let Some(dict) = DictRef::from_value(value) {
        let mut res = Vec::with_capacity(dict.len());
        if dict_attr_type.sorted {
            // First sort the values
            let mut items = dict.iter().collect::<Vec<_>>();
            // If two things are incompatible, just return Eq. The resulting order is undefined, but safely undefined.
            items.sort_by(|a, b| a.0.compare(b.0).unwrap_or(Ordering::Equal));

            for (k, v) in items {
                res.push((
                    dict_attr_type.key.coerce_item(configurable, ctx, k)?,
                    dict_attr_type.value.coerce_item(configurable, ctx, v)?,
                ));
            }
        } else {
            for (k, v) in dict.iter() {
                res.push((
                    dict_attr_type.key.coerce_item(configurable, ctx, k)?,
                    dict_attr_type.value.coerce_item(configurable, ctx, v)?,
                ));
            }
        }
        Ok(AnonTargetAttr::Dict(DictLiteral(res.into())))
    } else {
        Err(anyhow::anyhow!(AnonTargetCoercionError::type_error(
            Dict::TYPE,
            value,
        )))
    }
}

fn to_anon_target_one_of(
    one_of_attr_type: &OneOfAttrType,
    configurable: AttrIsConfigurable,
    ctx: &AnonAttrCtx,
    value: Value,
) -> anyhow::Result<AnonTargetAttr> {
    let mut errs = Vec::new();
    // Bias towards the start of the list - try and use success/failure from first in preference
    for (i, x) in one_of_attr_type.xs.iter().enumerate() {
        match x.coerce_item(configurable, ctx, value) {
            Ok(v) => return Ok(AnonTargetAttr::OneOf(Box::new(v), i as u32)),
            Err(e) => {
                // TODO(nga): anyhow error creation is expensive.
                errs.push(e)
            }
        }
    }
    Err(AnonTargetCoercionError::one_of_many(errs))
}

fn to_anon_target_tuple(
    tuple_attr_type: &TupleAttrType,
    configurable: AttrIsConfigurable,
    ctx: &AnonAttrCtx,
    value: Value,
) -> anyhow::Result<AnonTargetAttr> {
    let coerce = |value, items: &[Value]| {
        // Use comparison rather than equality below. If the tuple is too short,
        // it is implicitly extended using None.
        if items.len() <= tuple_attr_type.xs.len() {
            let mut res = Vec::with_capacity(tuple_attr_type.xs.len());
            for (c, v) in tuple_attr_type
                .xs
                .iter()
                .zip(items.iter().duped().chain(iter::repeat(Value::new_none())))
            {
                res.push(c.coerce_item(configurable, ctx, v)?);
            }
            // TODO(wendyy) intern attr
            Ok(AnonTargetAttr::Tuple(TupleLiteral(res.into())))
        } else {
            Err(anyhow::anyhow!(AnonTargetCoercionError::type_error(
                &format!("Tuple of at most length {}", tuple_attr_type.xs.len()),
                value
            )))
        }
    };
    if let Some(list) = TupleRef::from_value(value) {
        coerce(value, list.content())
    } else if let Some(list) = ListRef::from_value(value) {
        coerce(value, list.content())
    } else {
        Err(anyhow::anyhow!(AnonTargetCoercionError::type_error(
            TupleRef::TYPE,
            value,
        )))
    }
}

fn to_anon_target_list(
    list_attr_type: &ListAttrType,
    configurable: AttrIsConfigurable,
    ctx: &AnonAttrCtx,
    value: Value,
) -> anyhow::Result<AnonTargetAttr> {
    if let Some(list) = ListRef::from_value(value) {
        Ok(AnonTargetAttr::List(ListLiteral(
            // TODO(wendyy) intern attr
            list.content()
                .try_map(|v| list_attr_type.inner.coerce_item(configurable, ctx, *v))?
                .into(),
        )))
    } else if let Some(list) = TupleRef::from_value(value) {
        Ok(AnonTargetAttr::List(ListLiteral(
            // TODO(wendyy) intern attr
            list.content()
                .try_map(|v| list_attr_type.inner.coerce_item(configurable, ctx, *v))?
                .into(),
        )))
    } else {
        Err(anyhow::anyhow!(AnonTargetCoercionError::type_error(
            ListRef::TYPE,
            value,
        )))
    }
}


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

use buck2_analysis::attrs::resolve::attr_type::arg::ConfiguredStringWithMacrosExt;
use buck2_analysis::attrs::resolve::attr_type::dep::DepAttrTypeExt;
use buck2_analysis::attrs::resolve::ctx::AttrResolutionContext;
use buck2_build_api::interpreter::rule_defs::artifact::StarlarkArtifact;
use buck2_core::package::PackageLabel;
use buck2_node::attrs::attr_type::dep::DepAttrType;
use dupe::Dupe;
use starlark::values::dict::Dict;
use starlark::values::tuple::AllocTuple;
use starlark::values::Value;
use starlark_map::small_map::SmallMap;

use crate::anon_target_attr::AnonTargetAttr;

pub trait AnonTargetAttrExt {
    fn resolve<'v>(
        &self,
        pkg: PackageLabel,
        ctx: &dyn AttrResolutionContext<'v>,
    ) -> anyhow::Result<Vec<Value<'v>>>;

    fn resolve_single<'v>(
        &self,
        pkg: PackageLabel,
        ctx: &dyn AttrResolutionContext<'v>,
    ) -> anyhow::Result<Value<'v>>;
}

impl AnonTargetAttrExt for AnonTargetAttr {
    /// "Resolves" the anon target attr value to the resolved value provided to the rule implementation.
    ///
    /// `resolve` may return multiple values. It is up to the caller to fail if
    /// an inappropriate number of elements is returned. e.g. `attrs.list()` might
    /// accept and merge multiple returned values from `attrs.source()`, but
    /// `attrs.optional()` might only accept a single value, and fail otherwise.
    fn resolve<'v>(
        &self,
        pkg: PackageLabel,
        ctx: &dyn AttrResolutionContext<'v>,
    ) -> anyhow::Result<Vec<Value<'v>>> {
        Ok(vec![self.resolve_single(pkg, ctx)?])
    }

    /// Resolving a single value is common, so `resolve_single` will validate
    /// this function's output, and return a single value or an error.
    fn resolve_single<'v>(
        &self,
        pkg: PackageLabel,
        ctx: &dyn AttrResolutionContext<'v>,
    ) -> anyhow::Result<Value<'v>> {
        match self {
            AnonTargetAttr::Bool(v) => Ok(Value::new_bool(v.0)),
            AnonTargetAttr::Int(v) => Ok(ctx.heap().alloc(*v)),
            AnonTargetAttr::String(v) | AnonTargetAttr::EnumVariant(v) => {
                Ok(ctx.heap().alloc(v.as_str()))
            }
            AnonTargetAttr::List(list) => {
                let mut values = Vec::with_capacity(list.len());
                for v in list.iter() {
                    values.append(&mut v.resolve(pkg.dupe(), ctx)?);
                }
                Ok(ctx.heap().alloc(values))
            }
            AnonTargetAttr::Tuple(list) => {
                let mut values = Vec::with_capacity(list.len());
                for v in list.iter() {
                    values.append(&mut v.resolve(pkg.dupe(), ctx)?);
                }
                Ok(ctx.heap().alloc(AllocTuple(values)))
            }
            AnonTargetAttr::Dict(dict) => {
                let mut res = SmallMap::with_capacity(dict.len());
                for (k, v) in dict.iter() {
                    res.insert_hashed(
                        k.resolve_single(pkg.dupe(), ctx)?.get_hashed()?,
                        v.resolve_single(pkg.dupe(), ctx)?,
                    );
                }
                Ok(ctx.heap().alloc(Dict::new(res)))
            }
            AnonTargetAttr::None => Ok(Value::new_none()),
            AnonTargetAttr::OneOf(box l, _) => l.resolve_single(pkg, ctx),
            AnonTargetAttr::Dep(d) => DepAttrType::resolve_single(ctx, d),
            AnonTargetAttr::Artifact(d) => Ok(ctx.heap().alloc(StarlarkArtifact::new(d.clone()))),
            AnonTargetAttr::Arg(a) => a.resolve(ctx),
            AnonTargetAttr::PromiseArtifact(artifact) => Ok(ctx.heap().alloc(artifact.clone())),
        }
    }
}


/*
 * 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::any::Any;
use std::collections::hash_map::DefaultHasher;
use std::fmt;
use std::hash::Hash;
use std::hash::Hasher;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::base_deferred_key::BaseDeferredKeyDyn;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::configuration::pair::ConfigurationNoExec;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
use buck2_core::fs::project_rel_path::ProjectRelativePath;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_core::target::label::TargetLabel;
use buck2_data::action_key_owner::BaseDeferredKeyProto;
use buck2_data::ToProtoMessage;
use buck2_node::rule_type::StarlarkRuleType;
use buck2_util::collections::sorted_map::SortedMap;
use cmp_any::PartialEqAny;

use crate::anon_target_attr::AnonTargetAttr;

#[derive(Hash, Eq, PartialEq, Clone, Debug, Allocative)]
pub struct AnonTarget {
    /// Not necessarily a "real" target label that actually exists, but could be.
    name: TargetLabel,
    /// The type of the rule we are running.
    rule_type: Arc<StarlarkRuleType>,
    /// The attributes the target was defined with.
    /// We use a sorted map since we want to iterate in a defined order.
    attrs: SortedMap<String, AnonTargetAttr>,
    /// The hash of the `rule_type` and `attrs`
    hash: String,
    /// The execution configuration - same as the parent.
    exec_cfg: ConfigurationNoExec,
}

impl fmt::Display for AnonTarget {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} (anon: {}) ({})", self.name, self.hash, self.exec_cfg)
    }
}

impl AnonTarget {
    pub(crate) fn as_proto(&self) -> buck2_data::AnonTarget {
        buck2_data::AnonTarget {
            name: Some(self.name.as_proto()),
            execution_configuration: Some(self.exec_cfg.cfg().as_proto()),
            hash: self.hash.clone(),
        }
    }

    fn mk_hash(rule_type: &StarlarkRuleType, attrs: &SortedMap<String, AnonTargetAttr>) -> String {
        // This is the same hasher as we use for Configuration, so is probably fine.
        // But quite possibly should be a crypto hasher in future.
        let mut hasher = DefaultHasher::new();
        rule_type.hash(&mut hasher);
        attrs.hash(&mut hasher);
        format!("{:x}", hasher.finish())
    }

    pub fn new(
        rule_type: Arc<StarlarkRuleType>,
        name: TargetLabel,
        attrs: SortedMap<String, AnonTargetAttr>,
        exec_cfg: ConfigurationNoExec,
    ) -> Self {
        let hash = Self::mk_hash(&rule_type, &attrs);
        Self {
            name,
            rule_type,
            attrs,
            hash,
            exec_cfg,
        }
    }

    pub fn rule_type(&self) -> &Arc<StarlarkRuleType> {
        &self.rule_type
    }

    pub fn name(&self) -> &TargetLabel {
        &self.name
    }

    pub fn attrs(&self) -> &SortedMap<String, AnonTargetAttr> {
        &self.attrs
    }

    pub fn rule_type_attrs_hash(&self) -> &str {
        &self.hash
    }

    pub fn exec_cfg(&self) -> &ConfigurationNoExec {
        &self.exec_cfg
    }

    pub fn configured_label(&self) -> ConfiguredTargetLabel {
        // We need a configured label, but we don't have a real configuration (because it doesn't make sense),
        // so create a dummy version
        self.name().configure(ConfigurationData::unspecified())
    }
}

impl BaseDeferredKeyDyn for AnonTarget {
    fn eq_token(&self) -> PartialEqAny {
        PartialEqAny::new(self)
    }

    fn hash(&self) -> u64 {
        let mut hasher = DefaultHasher::new();
        Hash::hash(self, &mut hasher);
        hasher.finish()
    }

    fn make_hashed_path(
        &self,
        base: &ProjectRelativePath,
        prefix: &ForwardRelativePath,
        action_key: Option<&str>,
        path: &ForwardRelativePath,
    ) -> ProjectRelativePathBuf {
        let cell_relative_path = self.name().pkg().cell_relative_path().as_str();

        // It is performance critical that we use slices and allocate via `join` instead of
        // repeated calls to `join` on the path object because `join` allocates on each call,
        // which has a significant impact.
        let parts = [
            base.as_str(),
            "/",
            prefix.as_str(),
            "-anon/",
            self.name().pkg().cell_name().as_str(),
            "/",
            self.exec_cfg().cfg().output_hash().as_str(),
            cell_relative_path,
            if cell_relative_path.is_empty() {
                ""
            } else {
                "/"
            },
            self.rule_type_attrs_hash(),
            "/__",
            self.name().name().as_str(),
            "__",
            action_key.unwrap_or_default(),
            if action_key.is_none() { "" } else { "__" },
            "/",
            path.as_str(),
        ];

        ProjectRelativePathBuf::unchecked_new(parts.concat())
    }

    fn configured_label(&self) -> Option<ConfiguredTargetLabel> {
        Some(self.configured_label())
    }

    fn to_proto(&self) -> BaseDeferredKeyProto {
        BaseDeferredKeyProto::AnonTarget(self.as_proto())
    }

    fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
        self
    }
}


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

#![feature(async_closure)]
#![feature(box_patterns)]

use std::sync::Once;

pub(crate) mod anon_promises;
pub(crate) mod anon_target_attr;
pub(crate) mod anon_target_attr_coerce;
pub(crate) mod anon_target_attr_resolve;
pub(crate) mod anon_target_node;
pub(crate) mod anon_targets;
pub(crate) mod starlark_defs;

pub fn init_late_bindings() {
    static ONCE: Once = Once::new();
    ONCE.call_once(|| {
        anon_targets::init_anon_target_registry_new();
        anon_targets::init_eval_anon_target();
        starlark_defs::init_analysis_actions_methods_anon_target();
    });
}


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

use buck2_build_api::interpreter::rule_defs::context::AnalysisActions;
use buck2_build_api::interpreter::rule_defs::context::ANALYSIS_ACTIONS_METHODS_ANON_TARGET;
use buck2_interpreter::starlark_promise::StarlarkPromise;
use buck2_interpreter_for_build::rule::FrozenRuleCallable;
use starlark::environment::MethodsBuilder;
use starlark::starlark_module;
use starlark::values::dict::DictOf;
use starlark::values::Heap;
use starlark::values::Value;
use starlark::values::ValueTyped;

use crate::anon_targets::AnonTargetsRegistry;

#[starlark_module]
fn analysis_actions_methods_anon_target(builder: &mut MethodsBuilder) {
    /// An anonymous target is defined by the hash of its attributes, rather than its name.
    /// During analysis, rules can define and access the providers of anonymous targets before producing their own providers.
    /// Two distinct rules might ask for the same anonymous target, sharing the work it performs.
    ///
    /// For more details see https://buck2.build/docs/rule_authors/anon_targets/
    fn anon_target<'v>(
        this: &AnalysisActions<'v>,
        rule: ValueTyped<'v, FrozenRuleCallable>,
        attrs: DictOf<'v, &'v str, Value<'v>>,
        heap: &'v Heap,
    ) -> anyhow::Result<ValueTyped<'v, StarlarkPromise<'v>>> {
        let res = heap.alloc_typed(StarlarkPromise::new_unresolved());
        let mut this = this.state();
        AnonTargetsRegistry::downcast_mut(&mut *this.anon_targets)?
            .register_one(res, rule, attrs)?;
        Ok(res)
    }

    /// Generate a series of anonymous targets
    fn anon_targets<'v>(
        this: &AnalysisActions<'v>,
        rules: Vec<(
            ValueTyped<'v, FrozenRuleCallable>,
            DictOf<'v, &'v str, Value<'v>>,
        )>,
        heap: &'v Heap,
    ) -> anyhow::Result<ValueTyped<'v, StarlarkPromise<'v>>> {
        let res = heap.alloc_typed(StarlarkPromise::new_unresolved());
        let mut this = this.state();
        AnonTargetsRegistry::downcast_mut(&mut *this.anon_targets)?.register_many(res, rules)?;
        Ok(res)
    }
}

pub(crate) fn init_analysis_actions_methods_anon_target() {
    ANALYSIS_ACTIONS_METHODS_ANON_TARGET.init(analysis_actions_methods_anon_target);
}


/*
 * 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 actions;
pub mod artifact;
pub mod deferred;


/*
 * 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 buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_data::ToProtoMessage;
use dupe::Dupe;

use crate::deferred::key::DeferredKey;

/// A key to look up an 'Action' from the 'ActionAnalysisResult'.
/// Since 'Action's are registered as 'Deferred's
#[derive(
    Debug,
    Eq,
    PartialEq,
    Hash,
    Clone,
    Dupe,
    derive_more::Display,
    Allocative
)]
pub struct ActionKey(
    /// `DeferredData<Arc<RegisteredAction>>`.
    DeferredKey,
);

impl ActionKey {
    pub fn unchecked_new(key: DeferredKey) -> ActionKey {
        ActionKey(key)
    }

    pub fn deferred_key(&self) -> &DeferredKey {
        &self.0
    }

    pub fn owner(&self) -> &BaseDeferredKey {
        self.deferred_key().owner()
    }
}

impl ToProtoMessage for ActionKey {
    type Message = buck2_data::ActionKey;

    fn as_proto(&self) -> Self::Message {
        buck2_data::ActionKey {
            id: self.deferred_key().id().as_usize().to_ne_bytes().to_vec(),
            owner: Some(self.deferred_key().owner().to_proto().into()),
            key: self.deferred_key().action_key(),
        }
    }
}


/*
 * 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 key;


/*
 * 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::cell::Ref;
use std::cell::RefCell;
use std::hash::Hash;
use std::hash::Hasher;
use std::ops::Deref;
use std::rc::Rc;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_core::fs::artifact_path_resolver::ArtifactFs;
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_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_execute::artifact::artifact_dyn::ArtifactDyn;
use buck2_execute::execute::request::OutputType;
use buck2_execute::path::artifact_path::ArtifactPath;
use derivative::Derivative;
use derive_more::Display;
use derive_more::From;
use dupe::Dupe;
use either::Either;
use gazebo::cell::ARef;
use starlark_map::Hashed;

use crate::actions::key::ActionKey;
use crate::artifact::build_artifact::BuildArtifact;
use crate::artifact::projected_artifact::ProjectedArtifact;
use crate::artifact::source_artifact::SourceArtifact;

/// An 'Artifact' that can be materialized at its path. The underlying data is not very large here,
/// but we do store many copies of it, which is why we store this as an Arc.
#[derive(
    Clone, Debug, Display, Dupe, Allocative, Derivative, PartialEq, Eq, Hash
)]
pub struct Artifact(Arc<ArtifactData>);

#[derive(Clone, Debug, Display, Dupe, Allocative, Derivative)]
#[derivative(Hash, Eq, PartialEq)]
#[display(fmt = "{}", data)]
struct ArtifactData {
    data: Hashed<ArtifactKind>,

    /// The number of components at the prefix of that path that are internal details to the rule,
    /// not returned by `.short_path`. Omitted from Eq and Hash comparisons.
    #[derivative(Hash = "ignore", PartialEq = "ignore")]
    hidden_components_count: usize,
}

impl Artifact {
    pub fn new(
        artifact: impl Into<BaseArtifactKind>,
        projected_path: Option<Arc<ForwardRelativePathBuf>>,
        hidden_components_count: usize,
    ) -> Self {
        let artifact = match projected_path {
            Some(path) => ArtifactKind::Projected(ProjectedArtifact::new(artifact.into(), path)),
            None => ArtifactKind::Base(artifact.into()),
        };

        Self(Arc::new(ArtifactData {
            data: Hashed::new(artifact),
            hidden_components_count,
        }))
    }

    pub fn as_output_artifact(&self) -> Option<OutputArtifact> {
        let (kind, projected_path) = match self.0.data.key() {
            ArtifactKind::Base(a) => (a, None),
            ArtifactKind::Projected(a) => (a.base(), Some(a.path_shared())),
        };
        match kind {
            BaseArtifactKind::Source(_) => None,
            BaseArtifactKind::Build(artifact) => {
                let bound = BoundBuildArtifact {
                    artifact: artifact.dupe(),
                    projected_path: projected_path.cloned(),
                    hidden_components_count: self.0.hidden_components_count,
                };
                Some(bound.into_declared_artifact().into())
            }
        }
    }

    pub fn data(&self) -> &ArtifactKind {
        self.0.data.key()
    }

    pub fn is_source(&self) -> bool {
        match self.as_parts().0 {
            BaseArtifactKind::Source(_) => true,
            BaseArtifactKind::Build(_) => false,
        }
    }

    pub fn get_source(&self) -> Option<SourceArtifact> {
        match self.as_parts().0 {
            BaseArtifactKind::Source(x) => Some(x.dupe()),
            BaseArtifactKind::Build(_) => None,
        }
    }

    /// The callsite that declared this artifact, any.
    pub fn owner(&self) -> Option<&BaseDeferredKey> {
        match self.as_parts().0 {
            BaseArtifactKind::Source(_) => None,
            BaseArtifactKind::Build(b) => Some(b.get_path().owner()),
        }
    }

    /// The action that would produce this artifact, if any.
    pub fn action_key(&self) -> Option<&ActionKey> {
        match self.as_parts().0 {
            BaseArtifactKind::Source(_) => None,
            BaseArtifactKind::Build(b) => Some(b.key()),
        }
    }

    pub fn as_parts(&self) -> (&BaseArtifactKind, Option<&ForwardRelativePath>) {
        match self.0.data.key() {
            ArtifactKind::Base(a) => (a, None),
            ArtifactKind::Projected(a) => (a.base(), Some(a.path())),
        }
    }

    pub fn get_path(&self) -> ArtifactPath<'_> {
        let (base, projected_path) = self.as_parts();

        let base_path = match base {
            BaseArtifactKind::Build(b) => Either::Left(ARef::new_ptr(b.get_path())),
            BaseArtifactKind::Source(s) => Either::Right(s.get_path()),
        };

        ArtifactPath {
            base_path,
            projected_path,
            hidden_components_count: self.0.hidden_components_count,
        }
    }
}

impl ArtifactDyn for Artifact {
    fn resolve_path(&self, fs: &ArtifactFs) -> anyhow::Result<ProjectRelativePathBuf> {
        self.get_path().resolve(fs)
    }

    fn is_source(&self) -> bool {
        self.is_source()
    }
}

#[derive(Clone, Debug, Display, Dupe, PartialEq, Eq, Hash, From, Allocative)]
pub enum BaseArtifactKind {
    Source(SourceArtifact),
    Build(BuildArtifact),
}

#[derive(Clone, Debug, Display, Dupe, PartialEq, Eq, Hash, Allocative)]
pub enum ArtifactKind {
    Base(BaseArtifactKind),
    Projected(ProjectedArtifact),
}

impl From<SourceArtifact> for Artifact {
    fn from(a: SourceArtifact) -> Self {
        Self::new(a, None, 0)
    }
}

impl From<BuildArtifact> for Artifact {
    fn from(a: BuildArtifact) -> Self {
        Self::new(a, None, 0)
    }
}

/// An intermediate struct to respond to calls to `ensure_bound`.
#[derive(Clone, Dupe, Debug, Display, Allocative, Derivative)]
#[derivative(Hash, Eq, PartialEq)]
#[display(fmt = "{}", "self.get_path()")]
pub struct BoundBuildArtifact {
    artifact: BuildArtifact,
    projected_path: Option<Arc<ForwardRelativePathBuf>>,
    #[derivative(Hash = "ignore", PartialEq = "ignore")]
    hidden_components_count: usize,
}

impl BoundBuildArtifact {
    pub fn into_artifact(self) -> Artifact {
        Artifact::new(
            self.artifact,
            self.projected_path,
            self.hidden_components_count,
        )
    }

    pub fn into_declared_artifact(self) -> DeclaredArtifact {
        DeclaredArtifact {
            artifact: Rc::new(RefCell::new(DeclaredArtifactKind::Bound(self.artifact))),
            projected_path: self.projected_path,
            hidden_components_count: self.hidden_components_count,
        }
    }

    pub fn as_base_artifact(&self) -> &BuildArtifact {
        &self.artifact
    }

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

    pub fn get_path(&self) -> ArtifactPath<'_> {
        ArtifactPath {
            base_path: Either::Left(ARef::new_ptr(self.artifact.get_path())),
            projected_path: self
                .projected_path
                .as_ref()
                .map(|p| AsRef::<ForwardRelativePath>::as_ref(&**p)),
            hidden_components_count: self.hidden_components_count,
        }
    }
}

/// An artifact that is "declared" by a rule implementation, which will be an artifact that can be
/// made available by running some action created by that rule implementation.
///
/// This type should only exist within the concept of a rule implementation.
///
/// The 'DeclaredArtifact' can be either already "bound" if there is already an action attached,
/// in which case it is underneath a 'BuildArtifact', or be unbound if there is no action attached
/// to it yet, in which case it is a 'UnboundArtifact' underneath.
///
/// All 'DeclaredArtifact's are forced to be bound at the end of the analysis phase.
#[derive(Clone, Debug, Dupe, Display, Allocative)]
#[display(fmt = "{}", "self.get_path()")]
pub struct DeclaredArtifact {
    artifact: Rc<RefCell<DeclaredArtifactKind>>,
    projected_path: Option<Arc<ForwardRelativePathBuf>>,
    hidden_components_count: usize,
}

impl DeclaredArtifact {
    pub fn new(
        path: BuckOutPath,
        output_type: OutputType,
        hidden_components_count: usize,
    ) -> DeclaredArtifact {
        DeclaredArtifact {
            artifact: Rc::new(RefCell::new(DeclaredArtifactKind::Unbound(
                UnboundArtifact(path, output_type),
            ))),
            projected_path: None,
            hidden_components_count,
        }
    }

    pub fn project(&self, path: &ForwardRelativePath, hide_prefix: bool) -> Self {
        if path.is_empty() {
            return self.dupe();
        }

        let hidden_components_count = self.hidden_components_count
            + if hide_prefix {
                self.get_path().with_short_path(|p| p.iter().count())
            } else {
                0
            };

        Self {
            artifact: self.artifact.dupe(),
            projected_path: Some(Arc::new(match self.projected_path.as_ref() {
                Some(existing_path) => existing_path.join(path),
                None => path.to_owned(),
            })),
            hidden_components_count,
        }
    }

    pub fn as_output(&self) -> OutputArtifact {
        OutputArtifact(self.dupe())
    }

    pub fn get_path(&self) -> ArtifactPath<'_> {
        let borrow = self.artifact.borrow();

        let projected_path = self
            .projected_path
            .as_ref()
            .map(|p| AsRef::<ForwardRelativePath>::as_ref(&**p));

        let base_path = Ref::map(borrow, |a| match &a {
            DeclaredArtifactKind::Bound(a) => a.get_path(),
            DeclaredArtifactKind::Unbound(a) => &a.0,
        });

        ArtifactPath {
            base_path: Either::Left(ARef::new_ref(base_path)),
            projected_path,
            hidden_components_count: self.hidden_components_count,
        }
    }

    pub fn output_type(&self) -> OutputType {
        match &*self.artifact.borrow() {
            DeclaredArtifactKind::Bound(x) => x.output_type,
            DeclaredArtifactKind::Unbound(x) => x.1,
        }
    }

    /// Ensure that the artifact is bound.
    ///
    /// This is called before we freeze the artifacts by the artifact registry.
    /// When we freeze `DeclaredArtifact`, we then just call `get_bound_artifact()`, and `expect`
    /// it to be valid. We have the `ensure_bound` method to make sure that we can return
    /// a friendlier message to users because `freeze()` does not return error messages
    pub fn ensure_bound(self) -> anyhow::Result<BoundBuildArtifact> {
        let borrow = self.artifact.borrow();

        let artifact = match &*borrow {
            DeclaredArtifactKind::Bound(built) => built.dupe(),
            DeclaredArtifactKind::Unbound(unbound) => {
                return Err(anyhow::anyhow!(ArtifactErrors::UnboundArtifact(
                    unbound.dupe()
                )));
            }
        };

        Ok(BoundBuildArtifact {
            artifact,
            projected_path: self.projected_path,
            hidden_components_count: self.hidden_components_count,
        })
    }

    pub fn is_bound(&self) -> bool {
        self.artifact.borrow().is_bound()
    }

    pub fn owner(&self) -> Option<BaseDeferredKey> {
        match &*self.artifact.borrow() {
            DeclaredArtifactKind::Bound(b) => Some(b.get_path().owner().dupe()),
            DeclaredArtifactKind::Unbound(_) => None,
        }
    }
}

impl Hash for DeclaredArtifact {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.get_path().hash(state)
    }
}

impl PartialEq for DeclaredArtifact {
    fn eq(&self, other: &Self) -> bool {
        self.get_path() == other.get_path()
    }
}

impl Eq for DeclaredArtifact {}

/// A 'DeclaredArtifact' can be either "bound" to an 'Action', or "unbound"
#[derive(Clone, Dupe, Debug, Display, Allocative)]
enum DeclaredArtifactKind {
    Bound(BuildArtifact),
    Unbound(UnboundArtifact),
}

impl DeclaredArtifactKind {
    pub fn is_bound(&self) -> bool {
        match self {
            DeclaredArtifactKind::Bound(_) => true,
            DeclaredArtifactKind::Unbound(_) => false,
        }
    }
}

#[derive(thiserror::Error, Debug)]
pub enum ArtifactErrors {
    #[error("artifact `{0}` was already bound, but attempted to bind to action id `{1}`")]
    DuplicateBind(BuildArtifact, ActionKey),
    #[error(
        "artifact `{0}` must be bound by now. If you are intending to use this artifact as the output of `run`, are you missing an `.as_output()` call?"
    )]
    UnboundArtifact(UnboundArtifact),
}

/// An artifact that is marked as the output of a particular 'Action'.
#[derive(Clone, Debug, Display, Dupe, Hash, PartialEq, Eq, Allocative)]
pub struct OutputArtifact(DeclaredArtifact);

impl From<DeclaredArtifact> for OutputArtifact {
    fn from(artifact: DeclaredArtifact) -> Self {
        Self(artifact)
    }
}

impl OutputArtifact {
    pub fn bind(&self, key: ActionKey) -> anyhow::Result<BoundBuildArtifact> {
        match &mut *self.0.artifact.borrow_mut() {
            DeclaredArtifactKind::Bound(a) => {
                // NOTE: If the artifact was already bound to the same action, we leave it alone.
                // This can happen when we have projected artifacts used in a command: we'll visit
                // the projected artifacts and then try to bind each of them, but the same
                // underlying artifact is the one that gets bound.
                if *a.key() != key {
                    return Err(anyhow::anyhow!(ArtifactErrors::DuplicateBind(
                        a.dupe(),
                        key
                    )));
                }
            }
            a => take_mut::take(a, |artifact| match artifact {
                DeclaredArtifactKind::Unbound(unbound) => {
                    DeclaredArtifactKind::Bound(unbound.bind(key))
                }
                DeclaredArtifactKind::Bound(_) => {
                    unreachable!("should already be verified to be unbound")
                }
            }),
        };

        let artifact = match &*self.0.artifact.borrow() {
            DeclaredArtifactKind::Bound(b) => b.dupe(),
            _ => unreachable!("should already be bound"),
        };

        Ok(BoundBuildArtifact {
            artifact,
            projected_path: self.0.projected_path.dupe(),
            hidden_components_count: self.0.hidden_components_count,
        })
    }

    pub fn ensure_output_type(&self, output_type: OutputType) -> anyhow::Result<()> {
        output_type.check_path(self, self.0.output_type())
    }
}

impl Deref for OutputArtifact {
    type Target = DeclaredArtifact;

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

#[derive(Clone, Dupe, Debug, Display, Allocative)]
#[display(fmt = "{}", "self.0")]
pub struct UnboundArtifact(BuckOutPath, OutputType);

impl UnboundArtifact {
    fn bind(self, key: ActionKey) -> BuildArtifact {
        BuildArtifact::new(self.0, key, self.1)
    }
}

pub mod testing {
    use buck2_core::base_deferred_key::BaseDeferredKey;
    use buck2_core::fs::buck_out_path::BuckOutPath;
    use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
    use buck2_core::target::label::ConfiguredTargetLabel;
    use buck2_execute::execute::request::OutputType;
    use dupe::Dupe;

    use crate::actions::key::ActionKey;
    use crate::artifact::artifact_type::DeclaredArtifact;
    use crate::artifact::artifact_type::DeclaredArtifactKind;
    use crate::artifact::build_artifact::BuildArtifact;
    use crate::deferred::id::DeferredId;
    use crate::deferred::key::DeferredKey;

    pub trait ArtifactTestingExt {
        fn testing_is_bound(&self) -> bool;

        fn testing_action_key(&self) -> Option<ActionKey>;
    }

    impl ArtifactTestingExt for DeclaredArtifact {
        fn testing_is_bound(&self) -> bool {
            match &*self.artifact.borrow() {
                DeclaredArtifactKind::Bound(_) => true,
                DeclaredArtifactKind::Unbound(_) => false,
            }
        }

        fn testing_action_key(&self) -> Option<ActionKey> {
            match &*self.artifact.borrow() {
                DeclaredArtifactKind::Bound(built) => Some(built.key.dupe()),
                DeclaredArtifactKind::Unbound(_) => None,
            }
        }
    }

    impl ArtifactTestingExt for BuildArtifact {
        fn testing_is_bound(&self) -> bool {
            true
        }

        fn testing_action_key(&self) -> Option<ActionKey> {
            Some(self.key.dupe())
        }
    }

    pub trait BuildArtifactTestingExt {
        fn testing_new(
            target: ConfiguredTargetLabel,
            path: ForwardRelativePathBuf,
            id: DeferredId,
        ) -> BuildArtifact;
    }

    impl BuildArtifactTestingExt for BuildArtifact {
        fn testing_new(
            target: ConfiguredTargetLabel,
            path: ForwardRelativePathBuf,
            id: DeferredId,
        ) -> BuildArtifact {
            BuildArtifact::new(
                BuckOutPath::new(BaseDeferredKey::TargetLabel(target.dupe()), path),
                ActionKey::unchecked_new(DeferredKey::Base(
                    BaseDeferredKey::TargetLabel(target),
                    id,
                )),
                OutputType::File,
            )
        }
    }
}

#[cfg(test)]
mod tests {
    use std::collections::hash_map::DefaultHasher;
    use std::hash::Hash;
    use std::hash::Hasher;

    use assert_matches::assert_matches;
    use buck2_core::base_deferred_key::BaseDeferredKey;
    use buck2_core::buck_path::path::BuckPath;
    use buck2_core::cells::cell_root_path::CellRootPathBuf;
    use buck2_core::cells::name::CellName;
    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::BuckOutPath;
    use buck2_core::fs::buck_out_path::BuckOutPathResolver;
    use buck2_core::fs::fs_util;
    use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
    use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
    use buck2_core::fs::project::ProjectRoot;
    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::ConfiguredTargetLabel;
    use buck2_execute::execute::request::OutputType;
    use dupe::Dupe;

    use crate::actions::key::ActionKey;
    use crate::artifact::artifact_type::testing::BuildArtifactTestingExt;
    use crate::artifact::artifact_type::Artifact;
    use crate::artifact::artifact_type::DeclaredArtifact;
    use crate::artifact::artifact_type::DeclaredArtifactKind;
    use crate::artifact::build_artifact::BuildArtifact;
    use crate::artifact::source_artifact::SourceArtifact;
    use crate::deferred::id::DeferredId;
    use crate::deferred::key::DeferredKey;

    #[test]
    fn artifact_binding() -> anyhow::Result<()> {
        let target =
            ConfiguredTargetLabel::testing_parse("cell//pkg:foo", ConfigurationData::testing_new());
        let declared = DeclaredArtifact::new(
            BuckOutPath::new(
                BaseDeferredKey::TargetLabel(target.dupe()),
                ForwardRelativePathBuf::unchecked_new("bar.out".into()),
            ),
            OutputType::File,
            0,
        );
        let key = ActionKey::unchecked_new(DeferredKey::Base(
            BaseDeferredKey::TargetLabel(target.dupe()),
            DeferredId::testing_new(0),
        ));

        let out = declared.as_output();
        let bound = out.bind(key.dupe())?;

        assert_eq!(*bound.as_base_artifact().key(), key);
        assert_eq!(bound.get_path(), declared.get_path());

        match &*declared.artifact.borrow() {
            DeclaredArtifactKind::Bound(b) => {
                assert_eq!(b, bound.as_base_artifact());
            }
            _ => panic!("should be bound"),
        };

        // Binding again to the same key should succeed
        out.bind(key)?;

        // Binding again to a different key should fail
        let other_key = ActionKey::unchecked_new(DeferredKey::Base(
            BaseDeferredKey::TargetLabel(target),
            DeferredId::testing_new(1),
        ));

        assert_matches!(out.bind(other_key), Err(..));

        Ok(())
    }

    #[test]
    fn resolve_artifact() -> anyhow::Result<()> {
        let source = SourceArtifact::new(BuckPath::testing_new(
            PackageLabel::testing_parse("cell//pkg"),
            PackageRelativePathBuf::unchecked_new("src.cpp".into()),
        ));

        let project_fs =
            ProjectRoot::new(AbsNormPathBuf::try_from(std::env::current_dir().unwrap()).unwrap())
                .unwrap();
        let fs = ArtifactFs::new(
            CellResolver::testing_with_name_and_path(
                CellName::testing_new("cell"),
                CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new("cell_path".into())),
            ),
            BuckOutPathResolver::new(ProjectRelativePathBuf::unchecked_new("buck_out".into())),
            project_fs,
        );

        assert_eq!(
            Artifact::from(source).get_path().resolve(&fs)?,
            ProjectRelativePath::unchecked_new("cell_path/pkg/src.cpp")
        );

        Ok(())
    }

    #[test]
    fn writes_files() -> anyhow::Result<()> {
        let project_root = ProjectRootTemp::new().unwrap();
        let project_fs = project_root.path();

        let target =
            ConfiguredTargetLabel::testing_parse("cell//pkg:foo", ConfigurationData::testing_new());

        let artifact1 = BuildArtifact::testing_new(
            target.dupe(),
            ForwardRelativePathBuf::unchecked_new("foo/bar.cpp".to_owned()),
            DeferredId::testing_new(0),
        );
        let artifact2 = BuildArtifact::testing_new(
            target.dupe(),
            ForwardRelativePathBuf::unchecked_new("foo/bar.h".to_owned()),
            DeferredId::testing_new(0),
        );
        let artifact3 = BuildArtifact::testing_new(
            target,
            ForwardRelativePathBuf::unchecked_new("foo/bar.cpp/invalid_file.txt".to_owned()),
            DeferredId::testing_new(1),
        );

        let fs = ArtifactFs::new(
            CellResolver::testing_with_name_and_path(
                CellName::testing_new("cell"),
                CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new("cell_path".into())),
            ),
            BuckOutPathResolver::new(ProjectRelativePathBuf::unchecked_new("buck_out".into())),
            project_fs.dupe(),
        );
        let expected_path1 = project_fs.resolve(&fs.resolve_build(artifact1.get_path()));
        let expected_path2 = project_fs.resolve(&fs.resolve_build(artifact2.get_path()));

        let dest_path = fs.resolve_build(artifact1.get_path());
        fs.fs().write_file(&dest_path, "artifact1", false)?;

        assert_eq!("artifact1", fs_util::read_to_string(&expected_path1)?);

        let dest_path = fs.resolve_build(artifact2.get_path());
        fs.fs().write_file(&dest_path, "artifact2", true)?;

        assert_eq!("artifact2", fs_util::read_to_string(&expected_path2)?);

        let dest_path = fs.resolve_build(artifact3.get_path());
        fs.fs()
            .write_file(&dest_path, "artifact3", false)
            .expect_err("should fail because bar.cpp is a file");

        #[cfg(unix)]
        {
            // Check executable bit on unix
            use std::os::unix::fs::PermissionsExt;
            // Unix permission bits
            let artifact1_executable =
                fs_util::metadata(expected_path1)?.permissions().mode() & 0o100 != 0;
            let artifact2_executable =
                fs_util::metadata(expected_path2)?.permissions().mode() & 0o100 != 0;

            assert_eq!(false, artifact1_executable);
            assert_eq!(true, artifact2_executable);
        }

        Ok(())
    }

    #[test]
    fn test_eq_hash() -> anyhow::Result<()> {
        let target =
            ConfiguredTargetLabel::testing_parse("cell//pkg:foo", ConfigurationData::testing_new());

        let artifact = BuildArtifact::testing_new(
            target.dupe(),
            ForwardRelativePathBuf::unchecked_new("foo/bar.cpp".to_owned()),
            DeferredId::testing_new(0),
        );

        let full = Artifact::new(artifact.clone(), None, 0);
        let hidden = Artifact::new(artifact, None, 1);

        assert_eq!(full, hidden);

        let hash_full = {
            let mut hasher = DefaultHasher::new();
            full.hash(&mut hasher);
            hasher.finish()
        };

        let hash_hidden = {
            let mut hasher = DefaultHasher::new();
            hidden.hash(&mut hasher);
            hasher.finish()
        };

        assert_eq!(hash_full, hash_hidden);

        Ok(())
    }

    #[test]
    fn test_short_path() -> anyhow::Result<()> {
        let target =
            ConfiguredTargetLabel::testing_parse("cell//pkg:foo", ConfigurationData::testing_new());

        let artifact = BuildArtifact::testing_new(
            target.dupe(),
            ForwardRelativePathBuf::unchecked_new("foo/bar.cpp".to_owned()),
            DeferredId::testing_new(0),
        );

        let full = Artifact::new(artifact.clone(), None, 0);
        let hidden = Artifact::new(artifact, None, 1);

        full.get_path()
            .with_full_path(|p| assert_eq!(p, "foo/bar.cpp"));

        full.get_path()
            .with_short_path(|p| assert_eq!(p, "foo/bar.cpp"));

        hidden
            .get_path()
            .with_full_path(|p| assert_eq!(p, "foo/bar.cpp"));

        hidden
            .get_path()
            .with_short_path(|p| assert_eq!(p, "bar.cpp"));

        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 allocative::Allocative;
use buck2_core::fs::buck_out_path::BuckOutPath;
use buck2_data::ToProtoMessage;
use buck2_execute::execute::request::OutputType;
use derivative::Derivative;
use derive_more::Display;
use dupe::Dupe;

use crate::actions::key::ActionKey;

/// An artifact that is built by the build system
#[allow(clippy::derived_hash_with_manual_eq)] // The Eq is equivalent to what would have been generated
#[derive(Clone, Debug, Dupe, Display, Derivative, Allocative)]
#[derivative(PartialEq, Eq, Hash)]
#[display(fmt = "`{}`, action: {}", path, key)]
pub struct BuildArtifact {
    pub path: BuckOutPath,
    // If two BuildArtifact's have the same path then they are basically the same,
    // even if the ActionKey differs due to things like `dynamic_output`.
    // TODO(ndmitchell): Clean this up by making it more explicit in ActionKey.
    #[derivative(PartialEq = "ignore", Hash = "ignore")]
    pub key: ActionKey,
    #[derivative(PartialEq = "ignore", Hash = "ignore")]
    pub output_type: OutputType,
}

impl BuildArtifact {
    pub fn new(path: BuckOutPath, key: ActionKey, output_type: OutputType) -> Self {
        BuildArtifact {
            path,
            key,
            output_type,
        }
    }

    pub fn get_path(&self) -> &BuckOutPath {
        &self.path
    }

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

    pub fn output_type(&self) -> OutputType {
        self.output_type
    }
}

impl ToProtoMessage for BuildArtifact {
    type Message = buck2_data::BuildArtifact;

    fn as_proto(&self) -> Self::Message {
        buck2_data::BuildArtifact {
            key: Some(self.key().as_proto()),
            path: self.get_path().path().to_string(),
        }
    }
}


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

//! An 'Artifact' represents a File to be used as part of the build. It can be either a file in the
//! source tree, or a file to be generated as part of the build.
//!
//! The existence of an Artifact does not mean the actual file exists. Artifacts needs to be
//! 'made available' before its existence on the filesystem is guaranteed.
//!
//! An 'Artifact' is first "declared" by rule implementation as a 'DeclaredArtifact'. The artifact
//! will need to be "bound" to an 'Action' through being used as an 'OutputArtifact'. Once bound,
//! it becomes a 'BuildArtifact' that can be available.

pub mod artifact_type;
pub mod build_artifact;
pub mod projected_artifact;
pub mod provide_outputs;
pub mod source_artifact;


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

use std::sync::Arc;

use allocative::Allocative;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use derive_more::Display;
use dupe::Dupe;

use crate::artifact::artifact_type::BaseArtifactKind;

/// A path within another Artifact.
#[derive(Clone, Debug, Display, Dupe, Hash, PartialEq, Eq, Allocative)]
#[display(fmt = "{}/{}", base, path)]
pub struct ProjectedArtifact {
    base: BaseArtifactKind,
    path: Arc<ForwardRelativePathBuf>,
}

impl ProjectedArtifact {
    pub fn new(base: BaseArtifactKind, path: Arc<ForwardRelativePathBuf>) -> Self {
        Self { base, path }
    }

    pub fn base(&self) -> &BaseArtifactKind {
        &self.base
    }

    pub fn path(&self) -> &ForwardRelativePath {
        &self.path
    }

    pub fn path_shared(&self) -> &Arc<ForwardRelativePathBuf> {
        &self.path
    }
}


/*
 * 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 crate::artifact::build_artifact::BuildArtifact;

/// Dynamic data requested from deferreds.
///
/// Newtype to grep easier and to provide some type safety.
pub struct ProvideOutputs(pub anyhow::Result<Vec<BuildArtifact>>);


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

use allocative::Allocative;
use buck2_core::buck_path::path::BuckPath;
use buck2_core::buck_path::path::BuckPathRef;
use derive_more::Display;
use dupe::Dupe;

/// An artifact in the source tree
#[derive(
    Clone, Debug, Display, Dupe, Hash, PartialEq, Eq, PartialOrd, Ord, Allocative
)]
pub struct SourceArtifact(Arc<SourceArtifactData>);

#[derive(Debug, Display, Hash, PartialEq, Eq, PartialOrd, Ord, Allocative)]
struct SourceArtifactData(BuckPath);

impl SourceArtifact {
    pub fn new(path: BuckPath) -> Self {
        Self(Arc::new(SourceArtifactData(path)))
    }

    pub fn get_path(&self) -> BuckPathRef {
        self.0.0.as_ref()
    }
}


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

use allocative::Allocative;
use derivative::Derivative;
use dupe::Clone_;
use dupe::Dupe;

use crate::deferred::key::DeferredKey;

/// A value to be stored in 'Provider' fields representing an asynchronously computed value
#[derive(Clone_, Dupe, derive_more::Display, Derivative, Allocative)]
#[derivative(Debug, Eq, Hash, PartialEq)]
#[display(fmt = "{}", key)]
#[allocative(bound = "")]
#[repr(C)]
pub struct DeferredData<T> {
    key: DeferredKey,
    // by invariant of construction, the key is uniquely identifying of the 'DeferredData'
    #[derivative(Debug = "ignore", Hash = "ignore", PartialEq = "ignore")]
    p: PhantomData<T>,
}

impl<T> DeferredData<T> {
    pub fn unchecked_new(key: DeferredKey) -> Self {
        Self {
            key,
            p: PhantomData,
        }
    }

    pub fn unchecked_new_ref(key: &DeferredKey) -> &Self {
        // SAFETY: `repr(C)`.
        unsafe { &*(key as *const DeferredKey as *const Self) }
    }

    #[inline]
    pub fn into_deferred_key(self) -> DeferredKey {
        self.key
    }

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


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

/// An id to look up the deferred work
#[derive(Clone, Copy, Debug, Dupe, derive_more::Display, Allocative)]
// comment because linters and fmt don't agree
#[derive(Eq, Hash, Ord, PartialEq, PartialOrd)]
#[display(fmt = "{}", id)]
pub struct DeferredId {
    pub id: u32,
    pub trivial: bool,
}

impl DeferredId {
    /// Gets the underlying ID for this DeferredId. This should be used for logging only.
    #[inline]
    pub fn as_usize(self) -> usize {
        self.id as _
    }

    #[inline]
    pub fn is_trivial(self) -> bool {
        self.trivial
    }

    pub fn testing_new(id: u32) -> DeferredId {
        DeferredId { id, trivial: false }
    }
}


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

use std::sync::Arc;

use allocative::Allocative;
use buck2_core::base_deferred_key::BaseDeferredKey;
use dupe::Dupe;
use itertools::Itertools;

use crate::deferred::id::DeferredId;

/// A key to lookup a 'Deferred' of any result type
#[derive(
    Clone,
    Dupe,
    derive_more::Display,
    Debug,
    Eq,
    Hash,
    PartialEq,
    Allocative
)]
#[cfg_attr(feature = "gazebo_lint", allow(gazebo_lint_arc_on_dupe))] // Recursive type
pub enum DeferredKey {
    /// Base means it's the first deferred registered that can be looked up via the ID based on
    /// analysis of the 'ConfiguredTargetLabel'.
    #[display(fmt = "(target: `{}`, id: `{}`)", _0, _1)]
    Base(BaseDeferredKey, DeferredId),
    /// Points to a 'Deferred' that is generated from another 'Deferred'. The 'DeferredID' can only
    /// be looked up based on the results of executing the deferred at 'DeferredKey'
    #[display(fmt = "(target: `{}`, id: `{}`)", _0, _1)]
    Deferred(Arc<DeferredKey>, DeferredId),
}

impl DeferredKey {
    pub fn id(&self) -> DeferredId {
        *match self {
            DeferredKey::Base(_, id) | DeferredKey::Deferred(_, id) => id,
        }
    }

    /// Create action_key information from the ids, uniquely
    /// identifying this action within this target.
    pub fn action_key(&self) -> String {
        let mut ids = Vec::new();
        let mut x = self;
        loop {
            match x {
                DeferredKey::Base(_, id) => {
                    ids.push(id);
                    break;
                }
                DeferredKey::Deferred(base, id) => {
                    ids.push(id);
                    x = base
                }
            }
        }
        // FIXME(ndmitchell): We'd like to have some kind of user supplied name/category here,
        // rather than using the usize ids, so things are a bit more stable and as these strings
        // are likely to come up in error messages users might see (e.g. with paths).
        ids.iter().rev().map(|x| x.as_usize().to_string()).join("_")
    }

    pub fn owner(&self) -> &BaseDeferredKey {
        let mut x = self;
        loop {
            match x {
                DeferredKey::Base(base, _) => return base,
                DeferredKey::Deferred(base, _) => x = base,
            }
        }
    }
}


/*
 * 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 data;
pub mod id;
pub mod key;


/*
 * 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_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-analysis-queries",
    about = "buck audit analysis resolving query attrs"
)]
pub struct AuditAnalysisQueriesCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    #[clap(
        name = "TARGET_PATTERNS",
        help = "Patterns to evaluate. The query attributes for targets matching these patterns will be evaluated"
    )]
    pub patterns: Vec<String>,

    #[clap(
        long,
        help = "Enable to print the outputs for the targets in the resolved queries"
    )]
    pub include_outputs: bool,
}

#[async_trait]
impl AuditSubcommand for AuditAnalysisQueriesCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


/*
 * 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_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-cell",
    about = "Query information about the [repositories] list in .buckconfig."
)]
pub struct AuditCellCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    #[clap(long = "json", help = "Output in JSON format")]
    pub json: bool,

    #[clap(
        long = "paths-only",
        help = "Don't include the cell name in the output"
    )]
    pub paths_only: bool,

    #[clap(
        long = "aliases",
        help = "If enabled and no explicit aliases are passed, will query for all aliases in the working directory cell."
    )]
    pub aliases: bool,

    #[clap(
        name = "CELL_ALIASES",
        help = "Cell aliases to query. These aliases will be resolved in the working directory cell."
    )]
    pub aliases_to_resolve: Vec<String>,
}

#[async_trait]
impl AuditSubcommand for AuditCellCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


/*
 * 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_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-classpath",
    about = "Prints out a target's classpaths if it has one.
    This command is deprecated and currently available for compatibility with buck1.
    We will replace this command with something that can audit the entire `TemplatePlaceholderInfo` in the future."
)]
pub struct AuditClasspathCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    #[clap(name = "TARGET_PATTERNS", help = "Target patterns to audit")]
    pub patterns: Vec<String>,

    /// Output in JSON format
    #[clap(long)]
    pub json: bool,
    // TODO(scottcao): Add --show-targets, --dot, and other relevant flags
}

#[async_trait]
impl AuditSubcommand for AuditClasspathCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


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

use async_trait::async_trait;
use buck2_client_ctx::common::CommonCommandOptions;
use dupe::Dupe;

use crate::AuditSubcommand;

#[derive(
    Debug,
    Dupe,
    Clone,
    Copy,
    serde::Serialize,
    serde::Deserialize,
    clap::ArgEnum
)]
#[clap(rename_all = "snake_case")]
pub enum OutputFormat {
    Simple,
    Json,
}

#[derive(Debug, Clone, Copy, Dupe, serde::Serialize, serde::Deserialize)]
pub enum LocationStyle {
    None,
    Direct,
    Extended,
}

impl FromStr for LocationStyle {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "none" => Ok(Self::None),
            "direct" => Ok(Self::Direct),
            "extended" => Ok(Self::Extended),
            _ => Err("bad".to_owned()),
        }
    }
}

#[derive(Debug, Clone, Copy, Dupe, serde::Serialize, serde::Deserialize)]
pub enum ValueStyle {
    Resolved,
    Raw,
    Both,
}

impl FromStr for ValueStyle {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "resolved" => Ok(Self::Resolved),
            "raw" => Ok(Self::Raw),
            "both" => Ok(Self::Both),
            _ => Err("bad".to_owned()),
        }
    }
}

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(name = "audit-config", about = "buck audit config")]
pub struct AuditConfigCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    #[clap(long = "cell")]
    pub cell: Option<String>,

    #[clap(long, alias = "style", ignore_case = true, arg_enum)]
    pub output_format: Option<OutputFormat>,

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

    #[clap(long = "location", default_value = "none", possible_values=&["none", "direct", "extended"])]
    pub location_style: LocationStyle,

    #[clap(long = "value", default_value = "resolved", possible_values=&["resolved", "raw", "both"])]
    pub value_style: ValueStyle,

    #[clap(
        name = "SPECS",
        help = "config section/key specs of the form `section` or `section.key`. If any specs are provided, only values matching a spec will be printed (section headers will be printed only for sections with a key matching the spec)."
    )]
    pub specs: Vec<String>,
}

impl AuditConfigCommand {
    pub fn output_format(&self) -> OutputFormat {
        if let Some(format) = &self.output_format {
            *format
        } else if self.json {
            OutputFormat::Json
        } else {
            OutputFormat::Simple
        }
    }
}

#[async_trait]
impl AuditSubcommand for AuditConfigCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


/*
 * 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_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-configuration",
    about = "prints the constraints for configuration IDs"
)]
pub struct AuditConfigurationsCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    #[clap(
        name = "configurations",
        multiple_values = true,
        help = "configurations to audit (example: `cell//package:target-105fe3389fc7e436`). If none provided, will print information about all known configurations."
    )]
    pub configs: Vec<String>,
}

#[async_trait]
impl AuditSubcommand for AuditConfigurationsCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


/*
 * 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_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "deferred-materializer",
    about = "Access and interact with the deferred materializer"
)]
pub struct DeferredMaterializerCommand {
    #[clap(flatten)]
    pub common_opts: CommonCommandOptions,

    #[clap(subcommand)]
    pub subcommand: DeferredMaterializerSubcommand,
}

#[derive(Debug, clap::Subcommand, serde::Serialize, serde::Deserialize)]
pub enum DeferredMaterializerSubcommand {
    List,
    Fsck,
    Refresh {
        /// Minimum TTL to require for actions.
        #[clap()]
        min_ttl: i64,
    },
    /// Get the log for TTL refreshes.
    GetRefreshLog,
    TestIter {
        #[clap(long, default_value = "1")]
        count: usize,
    },
}

#[async_trait]
impl AuditSubcommand for DeferredMaterializerCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


/*
 * 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_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-dep-files",
    about = "prints out the select files for a command"
)]
pub struct AuditDepFilesCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    #[clap(help = "Target to query dep files for")]
    pub pattern: String,

    #[clap(help = "Action category")]
    pub category: String,

    #[clap(help = "Action identifier")]
    pub identifier: Option<String>,
}

#[async_trait]
impl AuditSubcommand for AuditDepFilesCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


/*
 * 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_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-execution-platform-resolution",
    about = "prints out information about execution platform resolution"
)]
pub struct AuditExecutionPlatformResolutionCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    #[clap(name = "TARGET_PATTERNS", help = "Patterns to analyze")]
    pub patterns: Vec<String>,
}

#[async_trait]
impl AuditSubcommand for AuditExecutionPlatformResolutionCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


/*
 * 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_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-includes",
    about = "list build file extensions imported at parse time."
)]
pub struct AuditIncludesCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,

    /// Print json representation of outputs
    #[clap(long)]
    pub json: bool,

    #[clap(
        name = "BUILD_FILES",
        help = "Build files to audit. These are expected to be relative paths from the working dir cell."
    )]
    pub patterns: Vec<String>,
}

#[async_trait]
impl AuditSubcommand for AuditIncludesCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}


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

#![feature(async_closure)]
#![feature(try_blocks)]
#![feature(provide_any)]

use async_trait::async_trait;
use buck2_cli_proto::GenericRequest;
use buck2_client_ctx::client_ctx::ClientCommandContext;
use buck2_client_ctx::common::CommonBuildConfigurationOptions;
use buck2_client_ctx::common::CommonCommandOptions;
use buck2_client_ctx::common::CommonConsoleOptions;
use buck2_client_ctx::common::CommonDaemonCommandOptions;
use buck2_client_ctx::daemon::client::BuckdClientConnector;
use buck2_client_ctx::daemon::client::StdoutPartialResultHandler;
use buck2_client_ctx::exit_result::ExitResult;
use buck2_client_ctx::streaming::StreamingCommand;
use classpath::AuditClasspathCommand;

use crate::analysis_queries::AuditAnalysisQueriesCommand;
use crate::cell::AuditCellCommand;
use crate::config::AuditConfigCommand;
use crate::configurations::AuditConfigurationsCommand;
use crate::deferred_materializer::DeferredMaterializerCommand;
use crate::dep_files::AuditDepFilesCommand;
use crate::execution_platform_resolution::AuditExecutionPlatformResolutionCommand;
use crate::includes::AuditIncludesCommand;
use crate::output::command::AuditOutputCommand;
use crate::output::parse::AuditParseCommand;
use crate::prelude::AuditPreludeCommand;
use crate::providers::AuditProvidersCommand;
use crate::starlark::StarlarkCommand;
use crate::visibility::AuditVisibilityCommand;

pub mod analysis_queries;
pub mod cell;
pub mod classpath;
pub mod config;
pub mod configurations;
pub mod deferred_materializer;
pub mod dep_files;
pub mod execution_platform_resolution;
pub mod includes;
pub mod output;
pub mod prelude;
pub mod providers;
pub mod starlark;
pub mod visibility;

#[derive(Debug, clap::Subcommand, serde::Serialize, serde::Deserialize)]
#[clap(name = "audit", about = "Perform lower level queries")]
pub enum AuditCommand {
    Cell(AuditCellCommand),
    Classpath(AuditClasspathCommand),
    Config(AuditConfigCommand),
    Configurations(AuditConfigurationsCommand),
    Includes(AuditIncludesCommand),
    Prelude(AuditPreludeCommand),
    Providers(AuditProvidersCommand),
    AnalysisQueries(AuditAnalysisQueriesCommand),
    ExecutionPlatformResolution(AuditExecutionPlatformResolutionCommand),
    Visibility(AuditVisibilityCommand),
    #[clap(subcommand)]
    Starlark(StarlarkCommand),
    DepFiles(AuditDepFilesCommand),
    DeferredMaterializer(DeferredMaterializerCommand),
    Output(AuditOutputCommand),
    Parse(AuditParseCommand),
}

/// `buck2 audit` subcommands have a somewhat unique approach to make it really easy to
/// add them without the boilerplate necessary for normal commands. The main difference
/// is that there is not a custom endpoint added in the daemon for each subcommand, instead
/// there is a single endpoint where we send the entire serialized AuditCommand and
/// reproduce it in the daemon.
///
/// Audit subcommands implement this trait so that we can handle the entire client side
/// logic here and to support that serialization to the daemon.
#[async_trait]
pub trait AuditSubcommand: Send + Sync + 'static {
    fn common_opts(&self) -> &CommonCommandOptions;
}

impl AuditCommand {
    fn as_subcommand(&self) -> &dyn AuditSubcommand {
        match self {
            AuditCommand::Cell(cmd) => cmd,
            AuditCommand::Classpath(cmd) => cmd,
            AuditCommand::Config(cmd) => cmd,
            AuditCommand::Configurations(cmd) => cmd,
            AuditCommand::Includes(cmd) => cmd,
            AuditCommand::Prelude(cmd) => cmd,
            AuditCommand::Providers(cmd) => cmd,
            AuditCommand::AnalysisQueries(cmd) => cmd,
            AuditCommand::ExecutionPlatformResolution(cmd) => cmd,
            AuditCommand::Starlark(cmd) => cmd,
            AuditCommand::DepFiles(cmd) => cmd,
            AuditCommand::DeferredMaterializer(cmd) => cmd,
            AuditCommand::Visibility(cmd) => cmd,
            AuditCommand::Output(cmd) => cmd,
            AuditCommand::Parse(cmd) => cmd,
        }
    }
}

#[async_trait]
impl StreamingCommand for AuditCommand {
    const COMMAND_NAME: &'static str = "audit";

    /// Audit subcommands are all implemented as a generic request to the buckd server that will deserialize the command object.
    async fn exec_impl(
        self,
        buckd: &mut BuckdClientConnector,
        matches: &clap::ArgMatches,
        ctx: &mut ClientCommandContext<'_>,
    ) -> ExitResult {
        let serialized = serde_json::to_string(&self)?;

        let config_opts = &self.as_subcommand().common_opts().config_opts;

        let submatches = match matches.subcommand().map(|s| s.1) {
            Some(submatches) => submatches,
            None => panic!("Parsed a subcommand but couldn't extract subcommand argument matches"),
        };

        let context =
            ctx.client_context(config_opts, submatches, ctx.sanitized_argv.argv.clone())?;

        buckd
            .with_flushing()
            .audit(
                GenericRequest {
                    context: Some(context),
                    serialized_opts: serialized,
                },
                ctx.stdin().console_interaction_stream(self.console_opts()),
                &mut StdoutPartialResultHandler,
            )
            .await??;
        ExitResult::success()
    }

    fn console_opts(&self) -> &CommonConsoleOptions {
        &self.as_subcommand().common_opts().console_opts
    }

    fn event_log_opts(&self) -> &CommonDaemonCommandOptions {
        &self.as_subcommand().common_opts().event_log_opts
    }

    fn common_opts(&self) -> &CommonBuildConfigurationOptions {
        &self.as_subcommand().common_opts().config_opts
    }
}


/*
 * 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_client_ctx::common::CommonCommandOptions;

use crate::AuditSubcommand;

#[derive(Debug, clap::Parser, serde::Serialize, serde::Deserialize)]
#[clap(
    name = "audit-prelude",
    about = "print the interpreter prelude to stdout"
)]
pub struct AuditPreludeCommand {
    #[clap(flatten)]
    common_opts: CommonCommandOptions,
}

#[async_trait]
impl AuditSubcommand for AuditPreludeCommand {
    fn common_opts(&self) -> &CommonCommandOptions {
        &self.common_opts
    }
}