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

use allocative::Allocative;
use buck2_core::provider::id::ProviderId;
use buck2_core::provider::id::ProviderIdWithType;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::NonDefaultProvidersName;
use buck2_core::provider::label::ProviderName;
use buck2_core::provider::label::ProvidersName;
use buck2_interpreter::starlark_promise::StarlarkPromise;
use buck2_interpreter::types::provider::callable::ValueAsProviderCallableLike;
use display_container::fmt_container;
use dupe::Dupe;
use either::Either;
use serde::Serialize;
use serde::Serializer;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::collections::SmallMap;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::values::list::ListRef;
use starlark::values::starlark_value;
use starlark::values::Freeze;
use starlark::values::Freezer;
use starlark::values::FrozenRef;
use starlark::values::FrozenValue;
use starlark::values::Heap;
use starlark::values::OwnedFrozenValue;
use starlark::values::OwnedFrozenValueTyped;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::Tracer;
use starlark::values::Value;
use starlark::values::ValueLike;

use crate::interpreter::rule_defs::provider::DefaultInfo;
use crate::interpreter::rule_defs::provider::DefaultInfoCallable;
use crate::interpreter::rule_defs::provider::FrozenDefaultInfo;
use crate::interpreter::rule_defs::provider::ValueAsProviderLike;

fn format_provider_keys_for_error(keys: &[String]) -> String {
    format!(
        "[{}]",
        keys.iter()
            .map(|k| format!("`{}`", k))
            .collect::<Vec<_>>()
            .join(", ")
    )
}

#[derive(Debug, thiserror::Error)]
enum ProviderCollectionError {
    #[error("expected a list of Provider objects, got {repr}")]
    CollectionNotAList { repr: String },
    #[error("expected a Provider object, got {repr}")]
    CollectionElementNotAProvider { repr: String },
    #[error("provider of type {provider_name} specified twice ({original_repr} and {new_repr})")]
    CollectionSpecifiedProviderTwice {
        provider_name: String,
        original_repr: String,
        new_repr: String,
    },
    #[error("collection {repr} did not receive a DefaultInfo provider")]
    CollectionMissingDefaultInfo { repr: String },
    #[error(
        "requested sub target named `{0}` of target `{1}` is not available. Available subtargets are: `{2:?}`"
    )]
    RequestedInvalidSubTarget(ProviderName, ConfiguredProvidersLabel, Vec<String>),
    #[error(
        "Cannot handle flavor `{flavor}` on target `{target}`. Most flavors are unsupported in Buck2."
    )]
    UnknownFlavors { target: String, flavor: String },
    #[error(
        "provider value that should have been `DefaultInfo` was not. It was `{repr}`. This is an internal error."
    )]
    ValueIsNotDefaultInfo { repr: String },
    #[error(
        "provider collection operation {0} parameter type must be a provider type \
        but not and instance of provider (for example, `RunInfo` or user defined provider type), \
        got `{1}`"
    )]
    AtTypeNotProvider(GetOp, &'static str),
    #[error(
        "provider collection does not have a key `{0}`, available keys are: {}",
        format_provider_keys_for_error(_1)
    )]
    AtNotFound(String, Vec<String>),
}

/// Holds a collection of `UserProvider`s. These can be accessed in Starlark by indexing on
/// a `ProviderCallable` object.
///
/// e.g.
/// ```ignore
/// FooInfo = provider(fields=["bar"])
/// ....
/// collection[FooInfo] # None if absent, a FooInfo instance if present
/// ```
///
/// This is the result of all UDR implementation functions
#[derive(Debug, ProvidesStaticType, Allocative)]
#[repr(C)]
pub struct ProviderCollectionGen<V> {
    pub(crate) providers: SmallMap<Arc<ProviderId>, V>,
}

// Can't derive this since no instance for Arc
unsafe impl<From: Coerce<To>, To> Coerce<ProviderCollectionGen<To>>
    for ProviderCollectionGen<From>
{
}

starlark_complex_value!(pub ProviderCollection);

impl<V: Display> Display for ProviderCollectionGen<V> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt_container(
            f,
            "Providers([",
            "])",
            self.providers.iter().map(|(_, v)| v),
        )
    }
}

impl<'v, V: ValueLike<'v>> Serialize for ProviderCollectionGen<V> {
    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        s.collect_map(self.providers.iter().map(|(id, v)| (id.name(), v)))
    }
}

/// Provider collection access operator.
#[derive(derive_more::Display, Debug)]
enum GetOp {
    #[display(fmt = "[]")]
    At,
    #[display(fmt = "in")]
    In,
    #[display(fmt = ".get")]
    Get,
}

impl<'v, V: ValueLike<'v>> ProviderCollectionGen<V> {
    /// Create most of the collection but don't do final assembly, or validate DefaultInfo here.
    /// This is an internal detail
    fn try_from_value_impl(
        mut value: Value<'v>,
    ) -> anyhow::Result<SmallMap<Arc<ProviderId>, Value<'v>>> {
        // Sometimes we might have a resolved promise here, in which case see through that
        value = StarlarkPromise::get_recursive(value);

        let list = match ListRef::from_value(value) {
            Some(v) => v,
            None => {
                return Err(ProviderCollectionError::CollectionNotAList {
                    repr: value.to_repr(),
                }
                .into());
            }
        };

        let mut providers = SmallMap::with_capacity(list.len());
        for value in list.iter() {
            match value.as_provider() {
                Some(provider) => {
                    if let Some(existing_value) = providers.insert(provider.id().dupe(), value) {
                        return Err(ProviderCollectionError::CollectionSpecifiedProviderTwice {
                            provider_name: provider.id().name.clone(),
                            original_repr: existing_value.to_repr(),
                            new_repr: value.to_repr(),
                        }
                        .into());
                    };
                }
                None => {
                    return Err(ProviderCollectionError::CollectionElementNotAProvider {
                        repr: value.to_repr(),
                    }
                    .into());
                }
            }
        }

        Ok(providers)
    }

    /// Takes a value, e.g. a return from a `rule()` implementation function, and builds a `ProviderCollection` from it.
    ///
    /// An error is returned if:
    ///  - `value` is not a list
    ///  - Two instances of the same provider are provided
    ///  - `DefaultInfo` is not provided
    pub fn try_from_value(value: Value<'v>) -> anyhow::Result<ProviderCollection<'v>> {
        let providers = Self::try_from_value_impl(value)?;
        if !providers.contains_key(DefaultInfoCallable::provider_id()) {
            return Err(ProviderCollectionError::CollectionMissingDefaultInfo {
                repr: value.to_repr(),
            }
            .into());
        }

        Ok(ProviderCollection::<'v> { providers })
    }

    /// Takes a value, e.g. a return from a `rule()` implementation function, and builds a `ProviderCollection` from it.
    ///
    /// An error is returned if:
    ///  - `value` is not a list
    ///  - Two instances of the same provider are provided
    ///
    /// `default_info_creator` is only invoked if `DefaultInfo` was not in the collection
    pub fn try_from_value_with_default_info(
        value: Value<'v>,
        default_info_creator: impl FnOnce() -> Value<'v>,
    ) -> anyhow::Result<ProviderCollection<'v>> {
        let mut providers = Self::try_from_value_impl(value)?;

        if !providers.contains_key(DefaultInfoCallable::provider_id()) {
            let di_value = default_info_creator();
            if DefaultInfo::from_value(di_value).is_none() {
                return Err(ProviderCollectionError::ValueIsNotDefaultInfo {
                    repr: di_value.to_repr(),
                }
                .into());
            }
            providers.insert(DefaultInfoCallable::provider_id().dupe(), di_value);
        }
        Ok(ProviderCollection::<'v> { providers })
    }

    /// Common implementation of `[]`, `in`, and `.get`.
    fn get_impl(
        &self,
        index: Value<'v>,
        op: GetOp,
    ) -> anyhow::Result<Either<Value<'v>, Arc<ProviderId>>> {
        match index.as_provider_callable() {
            Some(callable) => {
                let provider_id = callable.require_id()?;
                match self.providers.get(&provider_id) {
                    Some(v) => Ok(Either::Left(v.to_value())),
                    None => Ok(Either::Right(provider_id)),
                }
            }
            None => Err(ProviderCollectionError::AtTypeNotProvider(op, index.get_type()).into()),
        }
    }

    /// `.get` function implementation.
    pub(crate) fn get(&self, index: Value<'v>) -> anyhow::Result<Value<'v>> {
        Ok(self.get_impl(index, GetOp::Get)?.left_or(Value::new_none()))
    }
}

#[starlark_module]
fn provider_collection_methods(builder: &mut MethodsBuilder) {
    fn get<'v>(this: &ProviderCollection<'v>, index: Value<'v>) -> anyhow::Result<Value<'v>> {
        this.get(index)
    }
}

#[starlark_value(type = "provider_collection")]
impl<'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for ProviderCollectionGen<V>
where
    Self: ProvidesStaticType<'v>,
{
    fn at(&self, index: Value<'v>, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        match self.get_impl(index, GetOp::At)? {
            Either::Left(v) => Ok(v),
            Either::Right(provider_id) => Err(ProviderCollectionError::AtNotFound(
                provider_id.name.clone(),
                self.providers.keys().map(|k| k.name.clone()).collect(),
            )
            .into()),
        }
    }

    fn is_in(&self, other: Value<'v>) -> anyhow::Result<bool> {
        Ok(self.get_impl(other, GetOp::In)?.is_left())
    }

    fn get_methods() -> Option<&'static Methods>
    where
        Self: Sized,
    {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(provider_collection_methods)
    }
}

unsafe impl<'v> Trace<'v> for ProviderCollection<'v> {
    fn trace(&mut self, tracer: &Tracer<'v>) {
        self.providers.values_mut().for_each(|v| tracer.trace(v))
    }
}

impl<'v> Freeze for ProviderCollection<'v> {
    type Frozen = FrozenProviderCollection;
    fn freeze(self, freezer: &Freezer) -> anyhow::Result<Self::Frozen> {
        let providers = self
            .providers
            .into_iter()
            .map(|(k, v)| anyhow::Ok((k, freezer.freeze(v)?)))
            .collect::<anyhow::Result<_>>()?;
        Ok(FrozenProviderCollection { providers })
    }
}

impl<'v> ProviderCollection<'v> {
    pub fn default_info(&self) -> FrozenRef<'static, FrozenDefaultInfo> {
        self.providers
            .get(DefaultInfoCallable::provider_id())
            .expect("DefaultInfo should always be set")
            .unpack_frozen()
            .expect("Provider collections are always frozen")
            .downcast_frozen_ref::<FrozenDefaultInfo>()
            .expect("DefaultInfo should be of the right type")
    }
}

impl FrozenProviderCollection {
    pub fn default_info(&self) -> FrozenRef<'static, FrozenDefaultInfo> {
        self.get_provider(DefaultInfoCallable::provider_id_t())
            .expect("DefaultInfo should always be set")
    }

    pub fn default_info_value(&self) -> FrozenValue {
        *self
            .providers
            .get(DefaultInfoCallable::provider_id())
            .expect("DefaultInfo should always be set")
    }

    pub fn contains_provider(&self, provider_id: &ProviderId) -> bool {
        self.providers.contains_key(provider_id)
    }

    pub fn get_provider<T: StarlarkValue<'static>>(
        &self,
        provider_id: &ProviderIdWithType<T>,
    ) -> Option<FrozenRef<'static, T>> {
        self.providers
            .get(provider_id.id())
            .and_then(|v| v.downcast_frozen_ref::<T>())
    }

    pub fn get_provider_raw(&self, provider_id: &ProviderId) -> Option<&FrozenValue> {
        self.providers.get(provider_id)
    }

    pub fn provider_names(&self) -> Vec<String> {
        self.providers.keys().map(|k| k.name.to_owned()).collect()
    }

    pub fn provider_ids(&self) -> Vec<&ProviderId> {
        self.providers.keys().map(|k| &**k).collect()
    }
}

/// Thin wrapper around `FrozenValue` that can only be constructed if that value is a `FrozenProviderCollection`
#[derive(Debug, Clone, Dupe, Allocative)]
pub struct FrozenProviderCollectionValue {
    #[allocative(skip)] // TODO(nga): do not skip.
    value: OwnedFrozenValueTyped<FrozenProviderCollection>,
}

impl Serialize for FrozenProviderCollectionValue {
    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        (*self.value).serialize(s)
    }
}

impl FrozenProviderCollectionValue {
    pub fn from_value(value: OwnedFrozenValueTyped<FrozenProviderCollection>) -> Self {
        Self { value }
    }

    pub fn try_from_value(value: OwnedFrozenValue) -> anyhow::Result<Self> {
        Ok(Self {
            value: value.downcast().map_err(|value| {
                anyhow::anyhow!("{:?} was not a FrozenProviderCollection", value)
            })?,
        })
    }

    pub fn value(&self) -> &OwnedFrozenValueTyped<FrozenProviderCollection> {
        &self.value
    }

    pub fn provider_collection(&self) -> &FrozenProviderCollection {
        self.value.as_ref()
    }

    pub fn lookup_inner(&self, label: &ConfiguredProvidersLabel) -> anyhow::Result<Self> {
        match label.name() {
            ProvidersName::Default => anyhow::Ok(self.dupe()),
            ProvidersName::NonDefault(box NonDefaultProvidersName::Named(provider_names)) => {
                Ok(FrozenProviderCollectionValue::from_value(
                    self.value().try_map(|v| {
                        let mut collection_value = v;

                        for provider_name in &**provider_names {
                            let maybe_di = collection_value
                                .default_info()
                                .get_sub_target_providers(provider_name.as_str());

                            match maybe_di {
                                // The inner values should all be frozen if in a frozen provider collection
                                Some(inner) => {
                                    collection_value = inner;
                                }
                                None => {
                                    return Err(anyhow::anyhow!(
                                        ProviderCollectionError::RequestedInvalidSubTarget(
                                            provider_name.clone(),
                                            label.clone(),
                                            collection_value
                                                .default_info()
                                                .sub_targets()
                                                .keys()
                                                .map(|s| (*s).to_owned())
                                                .collect()
                                        )
                                    ));
                                }
                            }
                        }

                        Ok(collection_value)
                    })?,
                ))
            }
            ProvidersName::NonDefault(box NonDefaultProvidersName::UnrecognizedFlavor(flavor)) => {
                Err(ProviderCollectionError::UnknownFlavors {
                    target: label.unconfigured().to_string(),
                    flavor: (**flavor).to_owned(),
                }
                .into())
            }
        }
    }
}

pub mod tester {
    use buck2_interpreter::types::provider::callable::ValueAsProviderCallableLike;
    use dupe::Dupe;
    use starlark::environment::GlobalsBuilder;
    use starlark::values::Value;
    use starlark::values::ValueLike;

    use crate::interpreter::rule_defs::provider::collection::FrozenProviderCollection;
    use crate::interpreter::rule_defs::provider::ProviderCollection;

    #[starlark_module]
    pub fn collection_creator(builder: &mut GlobalsBuilder) {
        fn create_collection<'v>(value: Value<'v>) -> anyhow::Result<ProviderCollection<'v>> {
            ProviderCollection::try_from_value(value)
        }

        fn get_default_info_default_outputs<'v>(value: Value<'v>) -> anyhow::Result<Value<'v>> {
            let frozen = value
                .unpack_frozen()
                .expect("a frozen value to fetch DefaultInfo");
            let collection = frozen
                .downcast_ref::<FrozenProviderCollection>()
                .ok_or_else(|| anyhow::anyhow!("{:?} was not a FrozenProviderCollection", value))?;

            let ret = collection.default_info().default_outputs_raw().to_value();
            Ok(ret)
        }

        fn get_default_info_sub_targets<'v>(value: Value<'v>) -> anyhow::Result<Value<'v>> {
            let frozen = value
                .unpack_frozen()
                .expect("a frozen value to fetch DefaultInfo");
            let collection = frozen
                .downcast_ref::<FrozenProviderCollection>()
                .ok_or_else(|| anyhow::anyhow!("{:?} was not a FrozenProviderCollection", value))?;

            let ret = collection.default_info().sub_targets_raw().to_value();
            Ok(ret)
        }

        fn contains_provider<'v>(
            collection: Value<'v>,
            provider: Value<'v>,
        ) -> anyhow::Result<bool> {
            let id = provider
                .as_provider_callable()
                .unwrap()
                .id()
                .unwrap()
                .dupe();

            let res = collection
                .unpack_frozen()
                .expect("a frozen value")
                .downcast_ref::<FrozenProviderCollection>()
                .ok_or_else(|| {
                    anyhow::anyhow!("{:?} was not a FrozenProviderCollection", collection)
                })?
                .contains_provider(&id);

            Ok(res)
        }

        fn providers_list<'v>(collection: Value<'v>) -> anyhow::Result<Vec<String>> {
            Ok(collection
                .unpack_frozen()
                .expect("a frozen value")
                .downcast_ref::<FrozenProviderCollection>()
                .ok_or_else(|| {
                    anyhow::anyhow!("{:?} was not a FrozenProviderCollection", collection)
                })?
                .provider_names())
        }
    }
}


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

use allocative::Allocative;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProviderName;
use buck2_interpreter::types::label::Label;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::values::none::NoneOr;
use starlark::values::starlark_value;
use starlark::values::Freeze;
use starlark::values::Heap;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::UnpackValue;
use starlark::values::Value;
use starlark::values::ValueLike;
use thiserror::Error;

use crate::interpreter::rule_defs::provider::execution_platform::StarlarkExecutionPlatformResolution;
use crate::interpreter::rule_defs::provider::ProviderCollection;

#[derive(Debug, Error)]
enum DependencyError {
    #[error("Unknown subtarget, could not find `{0}`")]
    UnknownSubtarget(String),
}

/// Wraps a dependency's `ProvidersLabel` and the result of analysis together for users' rule implementation functions
///
/// From Starlark, the label is accessible with `.label`, and providers from the underlying
/// `ProviderCollection` are available via `[]` (`get()`)
#[derive(
    Debug,
    Trace,
    Coerce,
    Freeze,
    ProvidesStaticType,
    NoSerialize,
    Allocative,
    StarlarkDocs
)]
#[repr(C)]
pub struct DependencyGen<V> {
    label: V,
    providers_collection: V,
    execution_platform: V,
}

starlark_complex_value!(pub Dependency);

impl<V: Display> Display for DependencyGen<V> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "<dependency ")?;
        self.label.fmt(f)?;
        write!(f, ">")
    }
}

impl<'v> Dependency<'v> {
    pub fn new(
        heap: &'v Heap,
        label: ConfiguredProvidersLabel,
        providers_collection: Value<'v>,
        execution_platform: Option<&ExecutionPlatformResolution>,
    ) -> Self {
        let execution_platform = match execution_platform {
            Some(e) => NoneOr::Other(StarlarkExecutionPlatformResolution(e.clone())),
            None => NoneOr::None,
        };
        Dependency {
            label: heap.alloc(Label::new(label)),
            providers_collection,
            execution_platform: heap.alloc(execution_platform),
        }
    }

    pub fn label(&self) -> &Label {
        Label::from_value(self.label).unwrap()
    }

    pub fn execution_platform(&self) -> Option<&ExecutionPlatformResolution> {
        match NoneOr::unpack_value(self.execution_platform)
            .unwrap()
            .into_option()
        {
            Some(v) => match StarlarkExecutionPlatformResolution::from_value(v) {
                Some(starlark_execution_platform) => Some(&starlark_execution_platform.0),
                None => None,
            },
            None => None,
        }
    }

    fn provider_collection(&self) -> anyhow::Result<&ProviderCollection<'v>> {
        ProviderCollection::from_value(self.providers_collection)
            .ok_or_else(|| anyhow::anyhow!("internal error: not a ProviderCollection"))
    }
}

#[starlark_value(type = "dependency")]
impl<'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for DependencyGen<V>
where
    Self: ProvidesStaticType<'v>,
{
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(dependency_functions)
    }

    fn at(&self, index: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        self.providers_collection.to_value().at(index, heap)
    }

    fn is_in(&self, other: Value<'v>) -> anyhow::Result<bool> {
        self.providers_collection.to_value().is_in(other)
    }
}

#[starlark_module]
fn dependency_functions(builder: &mut MethodsBuilder) {
    #[starlark(attribute)]
    fn label<'v>(this: &Dependency) -> anyhow::Result<Value<'v>> {
        Ok(this.label.to_value())
    }

    // TODO(nga): should return provider collection.
    #[starlark(attribute)]
    fn providers<'v>(this: &Dependency) -> anyhow::Result<Vec<Value<'v>>> {
        Ok(this
            .provider_collection()?
            .providers
            .values()
            .copied()
            .collect())
    }

    /// Obtain the dependency representing a subtarget. In most cases you will want to use
    /// `x[DefaultInfo].sub_targets["foo"]` to get the _providers_ of the subtarget, but if you
    /// need a real `"dependency"` type (e.g. for use with `ctx.action.anon_target`) then use
    /// this method.
    fn sub_target<'v>(
        this: &Dependency<'v>,
        #[starlark(require = pos)] subtarget: &str,
        heap: &'v Heap,
    ) -> anyhow::Result<Dependency<'v>> {
        let di = this.provider_collection()?.default_info();
        let providers = di
            .get_sub_target_providers(subtarget)
            .ok_or_else(|| DependencyError::UnknownSubtarget(subtarget.to_owned()))?;
        let lbl = Label::from_value(this.label).unwrap().inner();
        let lbl = ConfiguredProvidersLabel::new(
            lbl.target().clone(),
            lbl.name().push(ProviderName::new(subtarget.to_owned())?),
        );
        Ok(Dependency::new(heap, lbl, providers.to_value(), None))
    }

    fn get<'v>(this: &Dependency<'v>, index: Value<'v>) -> anyhow::Result<Value<'v>> {
        this.provider_collection()?.get(index)
    }
}


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

use std::fmt::Display;
use std::fmt::Formatter;

use allocative::Allocative;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use starlark::any::ProvidesStaticType;
use starlark::starlark_simple_value;
use starlark::values::starlark_value;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::StarlarkDocs;

#[derive(ProvidesStaticType, Debug, NoSerialize, StarlarkDocs, Allocative)]
pub struct StarlarkExecutionPlatformResolution(pub ExecutionPlatformResolution);

starlark_simple_value!(StarlarkExecutionPlatformResolution);

#[starlark_value(type = "execution_platform_resolution")]
impl<'v> StarlarkValue<'v> for StarlarkExecutionPlatformResolution {}

impl Display for StarlarkExecutionPlatformResolution {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self.0.platform() {
            Ok(p) => write!(f, "{}", p.id()),
            Err(e) => write!(f, "{}", e),
        }
    }
}


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

//! Providers are the data returned from a rule, and are the only way that information from this
//! rule is available to rules that depend on it. Every rule must return at least the `DefaultInfo`
//! provider, but most will also return either `RunInfo` (because they are executable) or some
//! custom provider (because they are incorporated into something that is ultimately executable).
//!
//! Internal providers (those defined and used by buck itself) can be defined easily using the
//! #[internal_provider(creator_func)] macro. This will generate all the code needed for that
//! provider to be used in starlark and to be treated as a provider in the various rust utilities
//! we have for providers.
//!
//! For an internal provider like:
//! ```skip
//! #[internal_provider(create_my_prov)]
//! #[derive(Clone, Debug, Trace, Coerce)]
//! #[repr(transparent)]
//! pub struct MyProviderGen<V> {
//!    field1: V,
//!    field2: V,
//! }
//!
//! #[starlark_module]
//! fn create_my_prov(globals: &mut GlobalsBuilder) {
//!    fn NameDoesntMatter(
//!        // It's not enforced that the args here match the fields, but it's generally the user expectation that they do.
//!        field1: Value<'v>,
//!        field2: Value<'v>,
//!    ) -> MyProvider<'v> {
//!       // Can do some arg validation or computation here, just need to construct the provider.
//!       Ok(MyProvider {
//!            field1,
//!            field2
//!        })
//!    }
//! }
//! ```
//!
//! This will generate a "ProviderCallable" starlark type named (in starlark) `MyProvider` that acts like
//! the instance returned by a `provider()` call in starlark (so can be used to construct instances of the
//! provider or used in places like `attrs.dep(required_providers=[MyProvider]))`.
//!
//! For provider instances, in starlark all of their fields will be accessible by the field name.
//!
//! In rust, a StarlarkValue can be converted to the provider like normal with `MyProvider::from_value()`.
//! Often internally we'd have the analysis result (`FrozenProviderCollection`) and want to get the
//! provider out of their so there's a convenience function for that: `MyProvider::from_providers(collect)`.
// TODO(cjhopman): That last one would be more discoverable if we moved it onto the
// `FrozenProviderCollectionValue` itself so you could do `collection.get::<MyProvider>()`.
use std::fmt::Debug;
use std::sync::Arc;

use buck2_core::provider::id::ProviderId;
use starlark::any::ProvidesStaticType;
use starlark::environment::MethodsBuilder;
use starlark::values::Value;
use starlark::values::ValueLike;

use crate::interpreter::rule_defs::provider::builtin::default_info::DefaultInfo;
use crate::interpreter::rule_defs::provider::builtin::default_info::DefaultInfoCallable;
use crate::interpreter::rule_defs::provider::builtin::default_info::FrozenDefaultInfo;
use crate::interpreter::rule_defs::provider::collection::ProviderCollection;

pub mod builtin;
pub mod callable;
pub mod collection;
pub mod dependency;
pub mod execution_platform;
pub mod registration;
pub mod test_provider;
pub(crate) mod user;

pub(crate) trait ProviderLike<'v>: Debug {
    /// The ID. Guaranteed to be set on the `ProviderCallable` before constructing this object
    fn id(&self) -> &Arc<ProviderId>;
    /// Gets the value for a given field.
    fn get_field(&self, name: &str) -> Option<Value<'v>>;
    /// Returns a list of all the keys and values.
    // TODO(cjhopman): I'd rather return an iterator. I couldn't get that to work, though.
    fn items(&self) -> Vec<(&str, Value<'v>)>;
}

unsafe impl<'v> ProvidesStaticType<'v> for &'v dyn ProviderLike<'v> {
    type StaticType = &'static dyn ProviderLike<'static>;
}

/// Common methods on user and builtin providers.
#[starlark_module]
pub(crate) fn provider_methods(builder: &mut MethodsBuilder) {
    fn to_json(this: Value) -> anyhow::Result<String> {
        this.to_json()
    }
}

pub(crate) trait ValueAsProviderLike<'v> {
    fn as_provider(&self) -> Option<&'v dyn ProviderLike<'v>>;
}

impl<'v, V: ValueLike<'v>> ValueAsProviderLike<'v> for V {
    fn as_provider(&self) -> Option<&'v dyn ProviderLike<'v>> {
        self.to_value().request_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 starlark::environment::GlobalsBuilder;

pub(crate) struct ProviderRegistration {
    pub(crate) register_globals: fn(&mut GlobalsBuilder),
}

inventory::collect!(ProviderRegistration);

pub fn register_builtin_providers(registry: &mut GlobalsBuilder) {
    for registration in inventory::iter::<ProviderRegistration> {
        (registration.register_globals)(registry);
    }
}


/*
 * 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 buck2_core::cells::name::CellName;
use buck2_test_api::data::ConfiguredTarget;
use buck2_test_api::data::ExternalRunnerSpec;
use buck2_test_api::data::ExternalRunnerSpecValue;
use buck2_test_api::protocol::TestExecutor;
use futures::future::BoxFuture;
use futures::future::FutureExt;
use itertools::Itertools;

use crate::interpreter::rule_defs::cmd_args::CommandLineArtifactVisitor;
use crate::interpreter::rule_defs::provider::builtin::external_runner_test_info::ExternalRunnerTestInfoCallable;
use crate::interpreter::rule_defs::provider::builtin::external_runner_test_info::FrozenExternalRunnerTestInfo;
use crate::interpreter::rule_defs::provider::builtin::external_runner_test_info::TestCommandMember;
use crate::interpreter::rule_defs::provider::collection::FrozenProviderCollection;

pub trait TestProvider {
    fn visit_artifacts(&self, visitor: &mut dyn CommandLineArtifactVisitor) -> anyhow::Result<()>;

    fn labels(&self) -> Vec<&str>;

    fn dispatch<'exec>(
        &self,
        target: ConfiguredTarget,
        executor: Arc<dyn TestExecutor + 'exec>,
        working_dir_cell: CellName,
    ) -> BoxFuture<'exec, anyhow::Result<()>>;
}

impl TestProvider for FrozenExternalRunnerTestInfo {
    fn visit_artifacts(&self, visitor: &mut dyn CommandLineArtifactVisitor) -> anyhow::Result<()> {
        FrozenExternalRunnerTestInfo::visit_artifacts(self, visitor)
    }

    fn labels(&self) -> Vec<&str> {
        FrozenExternalRunnerTestInfo::labels(self).collect()
    }

    fn dispatch<'exec>(
        &self,
        target: ConfiguredTarget,
        executor: Arc<dyn TestExecutor + 'exec>,
        working_dir_cell: CellName,
    ) -> BoxFuture<'exec, anyhow::Result<()>> {
        let mut handle_index = 0;

        let command = self
            .command()
            .map(|c| match c {
                TestCommandMember::Literal(l) => ExternalRunnerSpecValue::Verbatim(l.to_owned()),
                TestCommandMember::Arglike(_) => {
                    // We assign indices to handles, which Tpx can use to reference them later.
                    // We don't count literals in here since Tpx won't use handles to
                    // communicate those (it would just use a literal instead).
                    let handle = ExternalRunnerSpecValue::ArgHandle(handle_index.into());
                    handle_index += 1;
                    handle
                }
            })
            .collect();

        let env = self
            .env()
            .map(|(k, _)| {
                (
                    k.to_owned(),
                    ExternalRunnerSpecValue::EnvHandle(k.to_owned().into()),
                )
            })
            .collect();

        let spec = ExternalRunnerSpec {
            target,
            test_type: self.test_type().to_owned(),
            command,
            env,
            labels: self.labels().map(|l| l.to_owned()).collect(),
            contacts: self.contacts().map(|l| l.to_owned()).collect(),
            oncall: self.contacts().exactly_one().ok().map(str::to_owned),
            working_dir_cell,
        };

        async move { executor.external_runner_spec(spec).await }.boxed()
    }
}

impl dyn TestProvider {
    pub fn from_collection<'a>(
        providers: &'a FrozenProviderCollection,
    ) -> Option<&'a dyn TestProvider> {
        if let Some(provider) =
            providers.get_provider(ExternalRunnerTestInfoCallable::provider_id_t())
        {
            return Some(provider.as_ref());
        }

        None
    }
}


/*
 * 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;
use std::fmt::Debug;
use std::fmt::Display;
use std::hash::Hash;
use std::marker::PhantomData;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::provider::id::ProviderId;
use display_container::fmt_keyed_container;
use serde::Serializer;
use starlark::any::ProvidesStaticType;
use starlark::coerce::coerce;
use starlark::coerce::Coerce;
use starlark::collections::Hashed;
use starlark::collections::StarlarkHasher;
use starlark::environment::Methods;
use starlark::environment::MethodsStatic;
use starlark::eval::Evaluator;
use starlark::eval::ParametersParser;
use starlark::values::starlark_value;
use starlark::values::Demand;
use starlark::values::Freeze;
use starlark::values::FrozenRef;
use starlark::values::Heap;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::Value;
use starlark::values::ValueLike;

use crate::interpreter::rule_defs::provider::callable::UserProviderCallableData;
use crate::interpreter::rule_defs::provider::provider_methods;
use crate::interpreter::rule_defs::provider::ProviderLike;

/// The result of calling the output of `provider()`. This is just a simple data structure of
/// either immediately available values or, later, `FutureValue` types that are resolved
/// asynchronously

#[derive(Debug, Clone, Coerce, Trace, Freeze, ProvidesStaticType, Allocative)]
#[repr(C)]
pub struct UserProviderGen<'v, V: ValueLike<'v>> {
    callable: FrozenRef<'static, UserProviderCallableData>,
    attributes: Vec<V>,
    _marker: PhantomData<&'v ()>,
}

starlark_complex_value!(pub UserProvider<'v>);

impl<'v, V: ValueLike<'v>> UserProviderGen<'v, V> {
    fn iter_items(&self) -> impl Iterator<Item = (&str, V)> {
        assert_eq!(self.callable.fields.len(), self.attributes.len());
        self.callable
            .fields
            .iter()
            .map(|s| s.as_str())
            .zip(self.attributes.iter().copied())
    }
}

impl<'v, V: ValueLike<'v>> Display for UserProviderGen<'v, V> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt_keyed_container(
            f,
            &format!("{}(", self.callable.provider_id.name),
            ")",
            "=",
            self.iter_items(),
        )
    }
}

#[starlark_value(type = "provider")]
impl<'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for UserProviderGen<'v, V>
where
    Self: ProvidesStaticType<'v>,
{
    fn matches_type(&self, ty: &str) -> bool {
        ty == "provider" || ty == self.callable.provider_id.name
    }

    fn dir_attr(&self) -> Vec<String> {
        self.callable.fields.iter().cloned().collect()
    }

    fn get_attr(&self, attribute: &str, heap: &'v Heap) -> Option<Value<'v>> {
        self.get_attr_hashed(Hashed::new(attribute), heap)
    }

    fn get_attr_hashed(&self, attribute: Hashed<&str>, _heap: &'v Heap) -> Option<Value<'v>> {
        let index = self.callable.fields.get_index_of_hashed(attribute)?;
        Some(self.attributes[index].to_value())
    }

    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(provider_methods)
    }

    fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
        let this: &UserProvider = coerce(self);
        let other: &UserProvider = match UserProvider::from_value(other) {
            Some(other) => other,
            None => return Ok(false),
        };
        if this.callable.provider_id != other.callable.provider_id {
            return Ok(false);
        }
        if this.attributes.len() != other.attributes.len() {
            // If provider ids are equal, then providers point to the same provider callable,
            // and lengths should be equal. So this code is unreachable.
            return Ok(false);
        }
        for ((k1, v1), (k2, v2)) in this.iter_items().zip(other.iter_items()) {
            if k1 != k2 {
                // If provider ids are equal, then providers point to the same provider callable,
                // and keys should be equal. So this code is unreachable.
                return Ok(false);
            }
            if !v1.equals(v2)? {
                return Ok(false);
            }
        }
        Ok(true)
    }

    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        self.callable.provider_id.hash(hasher);
        for (k, v) in self.iter_items() {
            k.hash(hasher);
            v.write_hash(hasher)?;
        }
        Ok(())
    }

    fn provide(&'v self, demand: &mut Demand<'_, 'v>) {
        demand.provide_value::<&dyn ProviderLike>(self);
    }
}

impl<'v, V: ValueLike<'v>> serde::Serialize for UserProviderGen<'v, V> {
    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        s.collect_map(self.iter_items())
    }
}

impl<'v, V: ValueLike<'v>> ProviderLike<'v> for UserProviderGen<'v, V> {
    fn id(&self) -> &Arc<ProviderId> {
        &self.callable.provider_id
    }

    fn get_field(&self, name: &str) -> Option<Value<'v>> {
        let index = self.callable.fields.get_index_of(name)?;
        Some(self.attributes[index].to_value())
    }

    fn items(&self) -> Vec<(&str, Value<'v>)> {
        self.iter_items().map(|(k, v)| (k, v.to_value())).collect()
    }
}

/// Creates instances of mutable `UserProvider`s; called from a `NativeFunction`
pub(crate) fn user_provider_creator<'v>(
    callable: FrozenRef<'static, UserProviderCallableData>,
    eval: &Evaluator<'v, '_>,
    mut param_parser: ParametersParser<'v, '_>,
) -> anyhow::Result<Value<'v>> {
    let heap = eval.heap();
    let values = callable
        .fields
        .iter()
        .map(|field| param_parser.next(field))
        .collect::<anyhow::Result<Vec<Value>>>()?;
    Ok(heap.alloc(UserProvider {
        callable,
        attributes: values,
        _marker: PhantomData,
    }))
}


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

use std::collections::BTreeMap;
use std::fmt::Debug;

use allocative::Allocative;
use buck2_build_api_derive::internal_provider;
use buck2_common::legacy_configs::parse_config_section_and_key;
use buck2_core::configuration::config_setting::ConfigSettingData;
use buck2_core::configuration::constraints::ConstraintKey;
use buck2_core::configuration::constraints::ConstraintValue;
use buck2_core::configuration::data::ConfigurationDataData;
use buck2_interpreter::types::target_label::StarlarkTargetLabel;
use dupe::Dupe;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::collections::SmallMap;
use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::values::dict::AllocDict;
use starlark::values::dict::Dict;
use starlark::values::dict::DictOf;
use starlark::values::dict::DictRef;
use starlark::values::type_repr::DictType;
use starlark::values::Freeze;
use starlark::values::Heap;
use starlark::values::Trace;
use starlark::values::ValueLike;
use starlark::values::ValueOf;
use thiserror::Error;

use crate::interpreter::rule_defs::provider::builtin::constraint_setting_info::ConstraintSettingInfo;
use crate::interpreter::rule_defs::provider::builtin::constraint_value_info::ConstraintValueInfo;

/// Provider that signals that a rule contains configuration info. This is used both as part of
/// defining configurations (`platform()`, `constraint_value()`) and defining whether a target "matches"
/// a configuration or not (`config_setting()`, `constraint_value()`)
#[internal_provider(configuration_info_creator)]
#[derive(Debug, Trace, Coerce, Freeze, ProvidesStaticType, Allocative)]
#[repr(C)]
pub struct ConfigurationInfoGen<V> {
    #[provider(field_type = "DictType<StarlarkTargetLabel, ConstraintValueInfo>")]
    constraints: V,
    #[provider(field_type = "DictType<String, String>")]
    values: V,
}

impl<'v, V: ValueLike<'v>> ConfigurationInfoGen<V> {
    pub fn to_config_setting_data(&self) -> ConfigSettingData {
        let constraints =
            DictRef::from_value(self.constraints.to_value()).expect("type checked on construction");
        let mut converted_constraints = BTreeMap::new();
        for (k, v) in constraints.iter() {
            let key_target = StarlarkTargetLabel::from_value(k.to_value())
                .expect("type checked on construction");
            let value_target = ConstraintValueInfo::from_value(v.to_value())
                .expect("type checked on construction");
            converted_constraints.insert(
                ConstraintKey(key_target.label().dupe()),
                ConstraintValue(value_target.label().label().dupe()),
            );
        }

        let values =
            DictRef::from_value(self.values.to_value()).expect("type checked on construction");
        let mut converted_values = BTreeMap::new();
        for (k, v) in values.iter() {
            let key_config = k.to_value().to_str();
            let value_config = v.to_value().to_str();
            converted_values.insert(key_config, value_config);
        }

        ConfigSettingData {
            constraints: converted_constraints,
            buckconfigs: converted_values,
        }
    }

    pub fn to_configuration_data(&self) -> anyhow::Result<ConfigurationDataData> {
        let ConfigSettingData {
            constraints,
            buckconfigs,
        } = self.to_config_setting_data();
        if !buckconfigs.is_empty() {
            return Err(ConfigurationInfoError::BuckConfigsNotAllowed.into());
        }
        Ok(ConfigurationDataData { constraints })
    }
}

impl<'v> ConfigurationInfo<'v> {
    /// Create a provider from configuration data.
    pub fn from_configuration_data(conf: &ConfigurationDataData, heap: &'v Heap) -> Self {
        let mut constraints = SmallMap::new();
        for (k, v) in &conf.constraints {
            let constraint_setting_label =
                heap.alloc_value_of(StarlarkTargetLabel::new(k.0.dupe()));
            let constraint_value_label = heap.alloc_value_of(StarlarkTargetLabel::new(v.0.dupe()));
            let constraint_setting =
                heap.alloc_value_of(ConstraintSettingInfo::new(constraint_setting_label));
            let constraint_value =
                ConstraintValueInfo::new(constraint_setting, constraint_value_label);
            let prev = constraints.insert_hashed(
                constraint_setting_label
                    .get_hashed()
                    .expect("StarlarkTargetLabel is hashable"),
                heap.alloc_complex(constraint_value),
            );
            assert!(prev.is_none());
        }

        ConfigurationInfoGen {
            constraints: heap.alloc(Dict::new(constraints)),
            values: heap.alloc(AllocDict::EMPTY),
        }
    }
}

#[derive(Debug, Error)]
enum ConfigurationInfoError {
    #[error("key `{0}` in constraints dict does not match constraint value `{1}`")]
    ConstraintsKeyValueMismatch(String, String),
    #[error("`ConfigurationInfo` cannot have buckconfigs when it is used to create a platform")]
    BuckConfigsNotAllowed,
}

#[starlark_module]
fn configuration_info_creator(globals: &mut GlobalsBuilder) {
    #[starlark(as_type = FrozenConfigurationInfo)]
    fn ConfigurationInfo<'v>(
        #[starlark(require = named)] constraints: DictOf<
            'v,
            ValueOf<'v, &'v StarlarkTargetLabel>,
            ValueOf<'v, &'v ConstraintValueInfo<'v>>,
        >,
        #[starlark(require = named)] values: DictOf<'v, &'v str, &'v str>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<ConfigurationInfo<'v>> {
        let mut new_constraints = SmallMap::new();
        for (constraint_setting, constraint_value) in constraints.collect_entries() {
            let constraint_setting_hashed = constraint_setting
                .value
                .get_hashed()
                .expect("should be hashable, we picked it from dict");
            let constraint_setting_from_constraint_value =
                constraint_value.typed.setting().typed.label();
            if *constraint_setting.typed != *constraint_setting_from_constraint_value {
                return Err(ConfigurationInfoError::ConstraintsKeyValueMismatch(
                    constraint_setting.value.to_string(),
                    constraint_value.to_string(),
                )
                .into());
            }
            let prev =
                new_constraints.insert_hashed(constraint_setting_hashed, constraint_value.value);
            assert!(prev.is_none());
        }
        for (k, _) in values.collect_entries() {
            // Validate the config section and key can be parsed correctly
            parse_config_section_and_key(k, None)?;
        }
        Ok(ConfigurationInfo {
            constraints: eval.heap().alloc(Dict::new(new_constraints)),
            values: *values,
        })
    }
}


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

//! ConstraintSettingInfo is the provider info returned from a constraint_setting() rule. Currently, all
//! it really does is indicate that the rule is actually a constraint_setting as a constraint_setting()
//! rule has no arguments.
//!
//! A constraint_value() rule will check that the referenced setting produces a ConstraintSettingInfo.

use std::fmt::Debug;

use allocative::Allocative;
use buck2_build_api_derive::internal_provider;
use buck2_interpreter::types::target_label::StarlarkTargetLabel;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::environment::GlobalsBuilder;
use starlark::values::Freeze;
use starlark::values::Trace;
use starlark::values::ValueLike;
use starlark::values::ValueOf;
use starlark::values::ValueTyped;

/// Provider that signals that a target can be used as a constraint key. This is the only provider
/// returned by a `constraint_setting()` target.
#[internal_provider(constraint_info_creator)]
#[derive(Clone, Debug, Trace, Coerce, Freeze, ProvidesStaticType, Allocative)]
#[repr(transparent)]
pub(crate) struct ConstraintSettingInfoGen<V> {
    #[provider(field_type = "StarlarkTargetLabel")]
    label: V,
}

impl<'v, V: ValueLike<'v>> ConstraintSettingInfoGen<V> {
    pub(crate) fn label(&self) -> ValueTyped<'v, StarlarkTargetLabel> {
        ValueTyped::new(self.label.to_value()).expect("validated at construction")
    }
}

impl<'v> ConstraintSettingInfo<'v> {
    pub(crate) fn new(label: ValueOf<'v, &'v StarlarkTargetLabel>) -> ConstraintSettingInfo<'v> {
        ConstraintSettingInfoGen { label: label.value }
    }
}

#[starlark_module]
fn constraint_info_creator(globals: &mut GlobalsBuilder) {
    #[starlark(as_type = FrozenConstraintSettingInfo)]
    fn ConstraintSettingInfo<'v>(
        #[starlark(require = named)] label: ValueOf<'v, &'v StarlarkTargetLabel>,
    ) -> anyhow::Result<ConstraintSettingInfo<'v>> {
        Ok(ConstraintSettingInfo { label: *label })
    }
}


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

//! `ConstraintValueInfo` is the provider info returned from a `constraint_value()` rule.

use std::fmt::Debug;

use allocative::Allocative;
use buck2_build_api_derive::internal_provider;
use buck2_interpreter::types::target_label::StarlarkTargetLabel;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::environment::GlobalsBuilder;
use starlark::values::Freeze;
use starlark::values::Trace;
use starlark::values::UnpackValue;
use starlark::values::ValueLike;
use starlark::values::ValueOf;
use starlark::values::ValueTyped;

use crate::interpreter::rule_defs::provider::builtin::constraint_setting_info::ConstraintSettingInfo;

/// Provider that signals that a target can be used as a constraint key. This is the only provider
/// returned by a `constraint_value()` target.
#[internal_provider(constraint_value_info_creator)]
#[derive(Clone, Debug, Trace, Coerce, Freeze, ProvidesStaticType, Allocative)]
#[repr(C)]
pub(crate) struct ConstraintValueInfoGen<V> {
    #[provider(field_type = "ConstraintSettingInfo")]
    setting: V,
    #[provider(field_type = "StarlarkTargetLabel")]
    label: V,
}

impl<'v, V: ValueLike<'v>> ConstraintValueInfoGen<V> {
    pub(crate) fn setting(&self) -> ValueOf<'v, &'v ConstraintSettingInfo<'v>> {
        ValueOf::unpack_value(self.setting.to_value()).expect("validated at construction")
    }

    pub(crate) fn label(&self) -> ValueTyped<'v, StarlarkTargetLabel> {
        ValueTyped::new(self.label.to_value()).expect("validated at construction")
    }
}

impl<'v> ConstraintValueInfo<'v> {
    pub(crate) fn new(
        setting: ValueOf<'v, &'v ConstraintSettingInfo<'v>>,
        label: ValueOf<'v, &'v StarlarkTargetLabel>,
    ) -> ConstraintValueInfo<'v> {
        ConstraintValueInfoGen {
            setting: setting.value,
            label: label.value,
        }
    }
}

#[starlark_module]
fn constraint_value_info_creator(globals: &mut GlobalsBuilder) {
    #[starlark(as_type = FrozenConstraintValueInfo)]
    fn ConstraintValueInfo<'v>(
        #[starlark(require = named)] setting: ValueOf<'v, &'v ConstraintSettingInfo<'v>>,
        #[starlark(require = named)] label: ValueOf<'v, &'v StarlarkTargetLabel>,
    ) -> anyhow::Result<ConstraintValueInfo<'v>> {
        Ok(ConstraintValueInfo {
            setting: *setting,
            label: *label,
        })
    }
}


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

use allocative::Allocative;
use anyhow::Context;
use buck2_artifact::artifact::artifact_type::Artifact;
use buck2_build_api_derive::internal_provider;
use dupe::Dupe;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::collections::SmallMap;
use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::values::dict::Dict;
use starlark::values::dict::FrozenDictRef;
use starlark::values::list::AllocList;
use starlark::values::list::ListRef;
use starlark::values::none::NoneType;
use starlark::values::type_repr::DictType;
use starlark::values::Freeze;
use starlark::values::FrozenRef;
use starlark::values::FrozenValue;
use starlark::values::FrozenValueTyped;
use starlark::values::Trace;
use starlark::values::UnpackValue;
use starlark::values::Value;
use starlark::values::ValueError;
use starlark::values::ValueLike;
use thiserror::Error;

use crate::artifact_groups::ArtifactGroup;
use crate::interpreter::rule_defs::artifact::StarlarkArtifact;
use crate::interpreter::rule_defs::artifact::StarlarkArtifactLike;
use crate::interpreter::rule_defs::artifact::ValueAsArtifactLike;
use crate::interpreter::rule_defs::cmd_args::value_as::ValueAsCommandLineLike;
use crate::interpreter::rule_defs::cmd_args::CommandLineArgLike;
use crate::interpreter::rule_defs::cmd_args::SimpleCommandLineArtifactVisitor;
use crate::interpreter::rule_defs::provider::collection::FrozenProviderCollection;
use crate::interpreter::rule_defs::provider::ProviderCollection;

/// A provider that all rules' implementations must return
///
/// In many simple cases, this can be inferred for the user.
///
/// Example of a rule's implementation function and how these fields are used by the framework:
///
/// ```starlark
/// # //foo_binary.bzl
/// def impl(ctx):
///     ctx.action.run([ctx.attrs._cc[RunInfo], "-o", ctx.attrs.out.as_output()] + ctx.attrs.srcs)
///     ctx.action.run([
///         ctx.attrs._strip[RunInfo],
///         "--binary",
///         ctx.attrs.out,
///         "--stripped-out",
///         ctx.attrs.stripped.as_output(),
///         "--debug-symbols-out",
///         ctx.attrs.debug_info.as_output(),
///     ])
///     return [
///         DefaultInfo(
///             sub_targets = {
///                 "stripped": [
///                     DefaultInfo(default_outputs = [ctx.attrs.stripped, ctx.attrs.debug_info]),
///                 ],
///             },
///             default_output = ctx.attrs.out,
///     ]
///
/// foo_binary = rule(
///     impl=impl,
///     attrs={
///         "srcs": attrs.list(attrs.source()),
///         "out": attrs.output(),
///         "stripped": attrs.output(),
///         "debug_info": attrs.output(),
///         "_cc": attrs.dep(default="//tools:cc", providers=[RunInfo]),
///         "_strip_script": attrs.dep(default="//tools:strip", providers=[RunInfo])
/// )
///
/// def foo_binary_wrapper(name, srcs):
///     foo_binary(
///         name = name,
///         srcs = src,
///         out = name,
///         stripped = name + ".stripped",
///         debug_info = name + ".debug_info",
///     )
///
/// # //subdir/BUCK
/// load("//:foo_binary.bzl", "foo_binary_wrapper")
///
/// genrule(name = "gen_stuff", ...., default_outs = ["foo.cpp"])
///
/// # ":gen_stuff" pulls the default_outputs for //subdir:gen_stuff
/// foo_binary_wrapper(name = "foo", srcs = glob(["*.cpp"]) + [":gen_stuff"])
///
/// # Builds just 'foo' binary. The strip command is never invoked.
/// $ buck build //subdir:foo
///
/// # builds the 'foo' binary, because it is needed by the 'strip' command. Ensures that
/// # both the stripped binary and the debug symbols are built.
/// $ buck build //subdir:foo[stripped]
/// ```
#[internal_provider(default_info_creator)]
#[derive(Clone, Debug, Freeze, Trace, Coerce, ProvidesStaticType, Allocative)]
#[freeze(validator = validate_default_info, bounds = "V: ValueLike<'freeze>")]
#[repr(C)]
pub struct DefaultInfoGen<V> {
    /// A mapping of names to `ProviderCollection`s. The keys are used when
    /// resolving the `ProviderName` portion of a `ProvidersLabel`. These collections
    /// can contain, and actually /must/ contain a `DefaultInfo` provider. However,
    /// nested label syntax is not supported. That is, `cell//foo:bar[baz]` is valid,
    /// `cell//foo:bar[baz][quz]` is not.
    #[provider(field_type = "DictType<String, ProviderCollection>")]
    sub_targets: V,
    /// A list of `Artifact`s that are built by default if this rule is requested
    /// explicitly, or depended on as as a "source".
    #[provider(field_type = "Vec<StarlarkArtifact>")]
    default_outputs: V,
    /// A list of `ArtifactTraversable`. The underlying `Artifact`s they define will
    /// be built by default if this rule is requested, but _not_ when it's depended
    /// on as as a "source". `ArtifactTraversable` can be an `Artifact` (which yields
    /// itself), or `cmd_args`, which expand to all their inputs.
    #[provider(field_type = "Vec<StarlarkArtifact>")]
    other_outputs: V,
}

fn validate_default_info(info: &FrozenDefaultInfo) -> anyhow::Result<()> {
    // Check length of default outputs
    let default_output_list = ListRef::from_value(info.default_outputs.to_value())
        .expect("should be a list from constructor");
    if default_output_list.len() > 1 {
        tracing::info!("DefaultInfo.default_output should only have a maximum of 1 item.");
        // TODO use soft_error when landed
        // TODO error rather than soft warning
        // return Err(anyhow::anyhow!(
        //     "DefaultInfo.default_output can only have a maximum of 1 item."
        // ));
    }

    // Check mutable data hasn't been modified.
    for output in info.default_outputs_impl()? {
        output?;
    }
    for sub_target in info.sub_targets_impl()? {
        sub_target?;
    }

    Ok(())
}

impl FrozenDefaultInfo {
    fn get_sub_target_providers_impl(
        &self,
        name: &str,
    ) -> anyhow::Result<Option<FrozenValueTyped<'static, FrozenProviderCollection>>> {
        FrozenDictRef::from_frozen_value(self.sub_targets)
            .context("sub_targets should be a dict-like object")?
            .get_str(name)
            .map(|v| {
                FrozenValueTyped::new(v).context(
                    "Values inside of a frozen provider should be frozen provider collection",
                )
            })
            .transpose()
    }

    pub fn get_sub_target_providers(
        &self,
        name: &str,
    ) -> Option<FrozenValueTyped<'static, FrozenProviderCollection>> {
        self.get_sub_target_providers_impl(name).unwrap()
    }

    fn default_outputs_impl(
        &self,
    ) -> anyhow::Result<impl Iterator<Item = anyhow::Result<StarlarkArtifact>> + '_> {
        let list = ListRef::from_frozen_value(self.default_outputs)
            .context("Should be list of artifacts")?;

        Ok(list.iter().map(|v| {
            let frozen_value = v.unpack_frozen().context("should be frozen")?;

            Ok(
                if let Some(starlark_artifact) = frozen_value.downcast_ref::<StarlarkArtifact>() {
                    starlark_artifact.dupe()
                } else {
                    // This code path is for StarlarkPromiseArtifact. We have to create a `StarlarkArtifact` object here.
                    let artifact_like = ValueAsArtifactLike::unpack_value(frozen_value.to_value())
                        .context("Should be list of artifacts")?;
                    artifact_like.0.get_bound_starlark_artifact()?
                },
            )
        }))
    }

    pub fn default_outputs<'a>(&'a self) -> Vec<StarlarkArtifact> {
        self.default_outputs_impl()
            .unwrap()
            .collect::<Result<_, _>>()
            .unwrap()
    }

    pub fn default_outputs_raw(&self) -> FrozenValue {
        self.default_outputs
    }

    fn sub_targets_impl(
        &self,
    ) -> anyhow::Result<
        impl Iterator<Item = anyhow::Result<(&str, FrozenRef<'static, FrozenProviderCollection>)>> + '_,
    > {
        let sub_targets = FrozenDictRef::from_frozen_value(self.sub_targets)
            .context("sub_targets should be a dict-like object")?;

        Ok(sub_targets.iter().map(|(k, v)| {
            anyhow::Ok((
                k.to_value()
                    .unpack_str()
                    .context("sub_targets should have string keys")?,
                v.downcast_frozen_ref::<FrozenProviderCollection>()
                    .context(
                        "Values inside of a frozen provider should be frozen provider collection",
                    )?,
            ))
        }))
    }

    pub fn sub_targets(&self) -> SmallMap<&str, FrozenRef<'static, FrozenProviderCollection>> {
        self.sub_targets_impl()
            .unwrap()
            .collect::<Result<_, _>>()
            .unwrap()
    }

    pub fn sub_targets_raw(&self) -> FrozenValue {
        self.sub_targets
    }

    pub fn for_each_default_output_artifact_only(
        &self,
        processor: &mut dyn FnMut(Artifact) -> anyhow::Result<()>,
    ) -> anyhow::Result<()> {
        self.for_each_in_list(self.default_outputs, |value| {
            processor(
                ValueAsArtifactLike::unpack_value(value)
                    .ok_or_else(|| anyhow::anyhow!("not an artifact"))?
                    .0
                    .get_bound_artifact()?,
            )
        })
    }

    pub fn for_each_default_output_other_artifacts_only(
        &self,
        processor: &mut dyn FnMut(ArtifactGroup) -> anyhow::Result<()>,
    ) -> anyhow::Result<()> {
        self.for_each_in_list(self.default_outputs, |value| {
            let others = ValueAsArtifactLike::unpack_value(value)
                .ok_or_else(|| anyhow::anyhow!("not an artifact"))?
                .0
                .get_associated_artifacts();
            others
                .iter()
                .flat_map(|v| v.iter())
                .for_each(|other| processor(other.dupe()).unwrap());
            Ok(())
        })
    }

    // TODO(marwhal): We can remove this once we migrate all other outputs to be handled with Artifacts directly
    pub fn for_each_other_output(
        &self,
        processor: &mut dyn FnMut(ArtifactGroup) -> anyhow::Result<()>,
    ) -> anyhow::Result<()> {
        self.for_each_in_list(self.other_outputs, |value| {
            value
                .as_artifact_traversable()
                .with_context(|| format!("Expected artifact traversable, got: {:?}", value))?
                .traverse(processor)
        })
    }

    pub fn for_each_output(
        &self,
        processor: &mut dyn FnMut(ArtifactGroup) -> anyhow::Result<()>,
    ) -> anyhow::Result<()> {
        self.for_each_default_output_artifact_only(&mut |a| processor(ArtifactGroup::Artifact(a)))?;
        self.for_each_default_output_other_artifacts_only(processor)?;
        // TODO(marwhal): We can remove this once we migrate all other outputs to be handled with Artifacts directly
        self.for_each_other_output(processor)
    }

    fn for_each_in_list(
        &self,
        value: FrozenValue,
        mut processor: impl FnMut(Value) -> anyhow::Result<()>,
    ) -> anyhow::Result<()> {
        let outputs_list = ListRef::from_frozen_value(value)
            .unwrap_or_else(|| panic!("expected list, got `{:?}` from info `{:?}`", value, self));

        for value in outputs_list.iter() {
            processor(value)?;
        }

        Ok(())
    }
}

impl PartialEq for FrozenDefaultInfo {
    // frozen default infos can be compared by ptr for a simple equality
    fn eq(&self, other: &Self) -> bool {
        ptr::eq(self, other)
    }
}

trait ArtifactTraversable {
    fn traverse(
        &self,
        processor: &mut dyn FnMut(ArtifactGroup) -> anyhow::Result<()>,
    ) -> anyhow::Result<()>;
}

// TODO: This is a hack. We need a way to express "the inputs of that other thing", but at the
// moment we don't have one, so we allow adding a command line (which is often the input container
// we care about) as an "other" output on DefaultInfo. We could use a better abstraction for this.
impl ArtifactTraversable for &dyn CommandLineArgLike {
    fn traverse(
        &self,
        processor: &mut dyn FnMut(ArtifactGroup) -> anyhow::Result<()>,
    ) -> anyhow::Result<()> {
        let mut acc = SimpleCommandLineArtifactVisitor::new();
        CommandLineArgLike::visit_artifacts(*self, &mut acc)?;
        for input in acc.inputs {
            processor(input)?;
        }
        Ok(())
    }
}

impl ArtifactTraversable for &dyn StarlarkArtifactLike {
    fn traverse(
        &self,
        processor: &mut dyn FnMut(ArtifactGroup) -> anyhow::Result<()>,
    ) -> anyhow::Result<()> {
        processor(ArtifactGroup::Artifact(self.get_bound_artifact()?))?;
        Ok(())
    }
}

trait ValueAsArtifactTraversable<'v> {
    fn as_artifact_traversable(&self) -> Option<Box<dyn ArtifactTraversable + 'v>>;
}

impl<'v, V: ValueLike<'v>> ValueAsArtifactTraversable<'v> for V {
    fn as_artifact_traversable(&self) -> Option<Box<dyn ArtifactTraversable + 'v>> {
        if let Some(artifact) = ValueAsArtifactLike::unpack_value(self.to_value()) {
            return Some(Box::new(artifact.0));
        }

        if let Some(cli) = self.to_value().as_command_line() {
            return Some(Box::new(cli));
        }

        None
    }
}

#[derive(Debug, Error)]
enum DefaultOutputError {
    #[error("Cannot specify both `default_output` and `default_outputs`.")]
    ConflictingArguments,
}

#[starlark_module]
fn default_info_creator(builder: &mut GlobalsBuilder) {
    #[starlark(as_type = FrozenDefaultInfo)]
    fn DefaultInfo<'v>(
        #[starlark(default = NoneType)] default_output: Value<'v>,
        #[starlark(default = NoneType)] default_outputs: Value<'v>,
        #[starlark(default = AllocList::EMPTY)] other_outputs: Value<'v>,
        #[starlark(default = SmallMap::new())] sub_targets: SmallMap<String, Value<'v>>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<DefaultInfo<'v>> {
        let heap = eval.heap();
        let default_info_creator = || {
            let default_outputs = heap.alloc(AllocList::EMPTY);
            let other_outputs = heap.alloc(AllocList::EMPTY);
            let sub_targets = heap.alloc(Dict::default());
            heap.alloc(DefaultInfo {
                sub_targets,
                default_outputs,
                other_outputs,
            })
        };

        // support both list and singular options for now until we migrate all the rules.
        let valid_default_outputs = if !default_outputs.is_none() {
            match ListRef::from_value(default_outputs) {
                Some(list) => {
                    if !default_output.is_none() {
                        return Err(anyhow::anyhow!(DefaultOutputError::ConflictingArguments));
                    }

                    if list
                        .iter()
                        .all(|v| ValueAsArtifactLike::unpack_value(v).is_some())
                    {
                        default_outputs
                    } else {
                        return Err(anyhow::anyhow!(ValueError::IncorrectParameterTypeNamed(
                            "default_outputs".to_owned()
                        )));
                    }
                }
                None => {
                    return Err(anyhow::anyhow!(ValueError::IncorrectParameterTypeNamed(
                        "default_outputs".to_owned()
                    )));
                }
            }
        } else {
            // handle where we didn't specify `default_outputs`, which means we should use the new
            // `default_output`.
            if default_output.is_none() {
                eval.heap().alloc(AllocList::EMPTY)
            } else if ValueAsArtifactLike::unpack_value(default_output).is_some() {
                eval.heap().alloc(AllocList([default_output]))
            } else {
                return Err(anyhow::anyhow!(ValueError::IncorrectParameterTypeNamed(
                    "default_output".to_owned()
                )));
            }
        };

        let valid_other_outputs = match ListRef::from_value(other_outputs) {
            Some(list) => {
                if list.iter().all(|v| v.as_artifact_traversable().is_some()) {
                    Ok(other_outputs)
                } else {
                    Err(())
                }
            }
            None => Err(()),
        }
        .map_err(|_| ValueError::IncorrectParameterTypeNamed("other_outputs".to_owned()))?;

        let valid_sub_targets = sub_targets
            .into_iter()
            .map(|(k, v)| {
                let as_provider_collection =
                    ProviderCollection::try_from_value_with_default_info(v, default_info_creator)?;
                Ok((
                    heap.alloc_str(&k).get_hashed_value(),
                    heap.alloc(as_provider_collection),
                ))
            })
            .collect::<anyhow::Result<SmallMap<Value<'v>, Value<'v>>>>()?;

        Ok(DefaultInfo {
            default_outputs: valid_default_outputs,
            other_outputs: valid_other_outputs,
            sub_targets: heap.alloc(Dict::new(valid_sub_targets)),
        })
    }
}


/*
 * 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 allocative::Allocative;
use buck2_build_api_derive::internal_provider;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::target::label::TargetLabel;
use buck2_interpreter::types::target_label::StarlarkTargetLabel;
use buck2_node::configuration::execution::ExecutionPlatform;
use dupe::Dupe;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::environment::GlobalsBuilder;
use starlark::values::Freeze;
use starlark::values::Trace;
use starlark::values::Value;
use starlark::values::ValueLike;
use thiserror::Error;

use crate::interpreter::rule_defs::command_executor_config::StarlarkCommandExecutorConfig;
use crate::interpreter::rule_defs::provider::builtin::configuration_info::ConfigurationInfo;

#[derive(Debug, Error)]
enum ExecutionPlatformProviderErrors {
    #[error("expected a label, got `{0}` (type `{1}`)")]
    ExpectedLabel(String, String),
    #[error("expected a ConfigurationInfo, got `{0}` (type `{1}`)")]
    ExpectedConfigurationInfo(String, String),
    #[error("expected a CommandExecutorConfig, got `{0}` (type `{1}`)")]
    ExpectedCommandExecutorConfig(String, String),
}

/// Provider that signals that a target represents an execution platform.
#[internal_provider(info_creator)]
#[derive(Clone, Debug, Trace, Coerce, Freeze, ProvidesStaticType, Allocative)]
#[repr(C)]
pub struct ExecutionPlatformInfoGen<V> {
    /// label of the defining rule, used in informative messages
    #[provider(field_type = "StarlarkTargetLabel")]
    label: V,
    /// The configuration of the execution platform
    #[provider(field_type = "ConfigurationInfo")]
    configuration: V,
    /// The executor config
    #[provider(field_type = "StarlarkCommandExecutorConfig")]
    executor_config: V,
}

impl<'v, V: ValueLike<'v>> ExecutionPlatformInfoGen<V> {
    pub fn to_execution_platform(&self) -> anyhow::Result<ExecutionPlatform> {
        let target = StarlarkTargetLabel::from_value(self.label.to_value())
            .ok_or_else(|| {
                ExecutionPlatformProviderErrors::ExpectedLabel(
                    self.label.to_value().to_repr(),
                    self.label.to_value().get_type().to_owned(),
                )
            })?
            .label()
            .dupe();
        let cfg = ConfigurationInfo::from_value(self.configuration.to_value())
            .ok_or_else(|| {
                ExecutionPlatformProviderErrors::ExpectedConfigurationInfo(
                    self.configuration.to_value().to_repr(),
                    self.configuration.to_value().get_type().to_owned(),
                )
            })?
            .to_configuration_data()?;
        let cfg = ConfigurationData::from_platform(TargetLabel::to_string(&target), cfg)?;
        let executor_config =
            StarlarkCommandExecutorConfig::from_value(self.executor_config.to_value())
                .ok_or_else(|| {
                    ExecutionPlatformProviderErrors::ExpectedCommandExecutorConfig(
                        self.configuration.to_value().to_repr(),
                        self.configuration.to_value().get_type().to_owned(),
                    )
                })?
                .0
                .dupe();
        Ok(ExecutionPlatform::platform(target, cfg, executor_config))
    }
}

#[starlark_module]
fn info_creator(globals: &mut GlobalsBuilder) {
    fn ExecutionPlatformInfo<'v>(
        #[starlark(require = named)] label: Value<'v>,
        #[starlark(require = named)] configuration: Value<'v>,
        #[starlark(require = named)] executor_config: Value<'v>,
    ) -> anyhow::Result<ExecutionPlatformInfo<'v>> {
        let info = ExecutionPlatformInfo {
            label,
            configuration,
            executor_config,
        };
        // This checks that the values are valid.
        info.to_execution_platform()?;
        Ok(info)
    }
}


/*
 * 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 allocative::Allocative;
use buck2_build_api_derive::internal_provider;
use buck2_node::configuration::execution_platforms::ExecutionPlatformFallback;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::environment::GlobalsBuilder;
use starlark::values::list::ListRef;
use starlark::values::Freeze;
use starlark::values::FrozenRef;
use starlark::values::Trace;
use starlark::values::Value;
use thiserror::Error;

use crate::interpreter::rule_defs::provider::builtin::execution_platform_info::ExecutionPlatformInfo;
use crate::interpreter::rule_defs::provider::builtin::execution_platform_info::FrozenExecutionPlatformInfo;

#[derive(Debug, Error)]
enum ExecutionPlatformRegistrationTypeError {
    #[error("expected a list of ExecutionPlatformInfo, got `{0}` (type `{1}`)")]
    ExpectedListOfPlatforms(String, String),
    #[error("expected a ExecutionPlatformInfo, got `{0}` (type `{1}`)")]
    NotAPlatform(String, String),
    #[error(
        "expected an ExecutionPlatformInfo or one of the strings \"error\" or \"use_unspecified\", got `{0}` (type `{1}`)"
    )]
    InvalidFallback(String, String),
}

/// Provider that gives the list of all execution platforms available for this build.
#[internal_provider(info_creator)]
#[derive(Clone, Debug, Trace, Coerce, Freeze, ProvidesStaticType, Allocative)]
#[repr(C)]
pub struct ExecutionPlatformRegistrationInfoGen<V> {
    #[provider(field_type = "Vec<FrozenExecutionPlatformInfo>")]
    platforms: V,
    // OneOf<ExecutionPlatformInfo, \"error\", \"unspecified\", None>
    fallback: V,
}

impl FrozenExecutionPlatformRegistrationInfo {
    // TODO(cjhopman): If we impl this on the non-frozen one, we can check validity when constructed rather than only when used.
    pub fn platforms(
        &self,
    ) -> anyhow::Result<Vec<FrozenRef<'static, FrozenExecutionPlatformInfo>>> {
        ListRef::from_frozen_value(self.platforms)
            .ok_or_else(|| {
                ExecutionPlatformRegistrationTypeError::ExpectedListOfPlatforms(
                    self.platforms.to_value().to_repr(),
                    self.platforms.to_value().get_type().to_owned(),
                )
            })?
            .iter()
            .map(|v| {
                v.unpack_frozen()
                    .expect("should be frozen")
                    .downcast_frozen_ref::<FrozenExecutionPlatformInfo>()
                    .ok_or_else(|| {
                        anyhow::anyhow!(ExecutionPlatformRegistrationTypeError::NotAPlatform(
                            v.to_repr(),
                            v.get_type().to_owned(),
                        ))
                    })
            })
            .collect::<anyhow::Result<_>>()
    }

    pub fn fallback(&self) -> anyhow::Result<ExecutionPlatformFallback> {
        if self.fallback.is_none() {
            return Ok(ExecutionPlatformFallback::UseUnspecifiedExec);
        }
        let fallback = self.fallback.to_value();
        if let Some(v) = ExecutionPlatformInfo::from_value(fallback) {
            return Ok(ExecutionPlatformFallback::Platform(
                v.to_execution_platform()?,
            ));
        }

        match fallback.unpack_str() {
            Some("error") => Ok(ExecutionPlatformFallback::Error),
            Some("use_unspecified") => Ok(ExecutionPlatformFallback::UseUnspecifiedExec),
            _ => Err(anyhow::anyhow!(
                ExecutionPlatformRegistrationTypeError::InvalidFallback(
                    fallback.to_repr(),
                    fallback.get_type().to_owned(),
                )
            )),
        }
    }
}

#[starlark_module]
fn info_creator(globals: &mut GlobalsBuilder) {
    fn ExecutionPlatformRegistrationInfo<'v>(
        #[starlark(require = named)] platforms: Value<'v>,
        #[starlark(require = named)] fallback: Option<Value<'v>>,
    ) -> anyhow::Result<ExecutionPlatformRegistrationInfo<'v>> {
        Ok(ExecutionPlatformRegistrationInfo {
            platforms,
            fallback: fallback.unwrap_or_else(Value::new_none),
        })
    }
}


/*
 * 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::iter::empty;
use std::iter::once;

use allocative::Allocative;
use anyhow::Context as _;
use buck2_build_api_derive::internal_provider;
use either::Either;
use indexmap::IndexMap;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::environment::GlobalsBuilder;
use starlark::values::dict::DictRef;
use starlark::values::list::ListRef;
use starlark::values::none::NoneOr;
use starlark::values::none::NoneType;
use starlark::values::tuple::TupleRef;
use starlark::values::type_repr::DictType;
use starlark::values::Freeze;
use starlark::values::FrozenValue;
use starlark::values::Trace;
use starlark::values::UnpackValue;
use starlark::values::Value;
use starlark::values::ValueLike;

use crate::interpreter::rule_defs::cmd_args::value_as::ValueAsCommandLineLike;
use crate::interpreter::rule_defs::cmd_args::CommandLineArgLike;
use crate::interpreter::rule_defs::cmd_args::CommandLineArtifactVisitor;
use crate::interpreter::rule_defs::cmd_args::CommandLineBuilder;
use crate::interpreter::rule_defs::cmd_args::CommandLineContext;
use crate::interpreter::rule_defs::command_executor_config::StarlarkCommandExecutorConfig;
use crate::interpreter::rule_defs::provider::builtin::local_resource_info::FrozenLocalResourceInfo;
use crate::interpreter::rule_defs::resolved_macro::ResolvedStringWithMacros;

/// Provider that signals that a rule can be tested using an external runner. This is the
/// Buck1-compatible API for tests.
#[internal_provider(external_runner_test_info_creator)]
#[derive(Clone, Debug, Trace, Coerce, Freeze, ProvidesStaticType, Allocative)]
#[freeze(validator = validate_external_runner_test_info, bounds = "V: ValueLike<'freeze>")]
#[repr(C)]
pub struct ExternalRunnerTestInfoGen<V> {
    /// A Starlark value representing the type of this test.
    /// This is of type str.type
    #[provider(field_type = "String")]
    test_type: V,

    /// A Starlark value representing the command for this test. The external test runner is what
    /// gives meaning to this command.
    /// This is of type [[str.type, "_arglike"]]
    #[provider(field_type = "Vec<Either<String, FrozenValue>>")]
    command: V,

    /// A Starlark value representing the environment for this test. Here again, the external test
    /// runner is what will this meaning.
    /// This is of type {str.type: _arglike}
    #[provider(field_type = "DictType<String, FrozenValue>")]
    env: V,

    /// A starlark value representing the labels for this test.
    /// This is of type [str.type]
    #[provider(field_type = "Vec<String>")]
    labels: V,

    /// A starlark value representing the contacts for this test. This is largely expected to be an
    /// oncall, though it's not validated in any way.
    /// This is of type [str.type]
    #[provider(field_type = "Vec<String>")]
    contacts: V,

    /// Whether this test should use relative paths. The default is not to.
    /// This is of type [bool.type]
    #[provider(field_type = "Vec<bool>")]
    use_project_relative_paths: V,

    /// Whether this test should run from the project root, as opposed to the cell root. The
    /// default is not to.
    /// This is of type [bool.type]
    #[provider(field_type = "Vec<bool>")]
    run_from_project_root: V,

    /// Default executor to use to run tests.  This is of type CommandExecutorConfig. If none is
    /// passed we will default to the execution platform.
    #[provider(field_type = "StarlarkCommandExecutorConfig")]
    default_executor: V,

    /// Executors that Tpx can use to override the default executor.
    /// This is of type {str.type: CommandExecutorConfig}
    #[provider(field_type = "DictType<String, StarlarkCommandExecutorConfig>")]
    executor_overrides: V,

    /// Mapping from a local resource type to a corresponding provider.
    /// Required types are passed from test runner.
    /// If the value for a corresponding type is omitted it means local resource
    /// should be ignored when executing tests even if those are passed as required from test runner.
    #[provider(field_type = "DictType<String, Option<FrozenLocalResourceInfo>>")]
    local_resources: V,
}

// NOTE: All the methods here unwrap because we validate at freeze time.
impl FrozenExternalRunnerTestInfo {
    pub fn test_type(&self) -> &str {
        self.test_type.to_value().unpack_str().unwrap()
    }

    pub fn command(&self) -> impl Iterator<Item = TestCommandMember<'_>> {
        unwrap_all(iter_test_command(self.command.to_value()))
    }

    pub fn env(&self) -> impl Iterator<Item = (&str, &dyn CommandLineArgLike)> {
        unwrap_all(iter_test_env(self.env.to_value()))
    }

    pub fn labels(&self) -> impl Iterator<Item = &str> {
        unwrap_all(iter_opt_str_list(self.labels.to_value(), "labels"))
    }

    pub fn contacts(&self) -> impl Iterator<Item = &str> {
        unwrap_all(iter_opt_str_list(self.contacts.to_value(), "contacts"))
    }

    pub fn use_project_relative_paths(&self) -> bool {
        NoneOr::<bool>::unpack_value(self.use_project_relative_paths.to_value())
            .unwrap()
            .into_option()
            .unwrap_or_else(buck2_core::is_open_source)
    }

    pub fn run_from_project_root(&self) -> bool {
        NoneOr::<bool>::unpack_value(self.run_from_project_root.to_value())
            .unwrap()
            .into_option()
            .unwrap_or_else(buck2_core::is_open_source)
    }

    pub fn default_executor(&self) -> Option<&StarlarkCommandExecutorConfig> {
        unpack_opt_executor(self.default_executor.to_value()).unwrap()
    }

    /// Access a specific executor override.
    pub fn executor_override(&self, key: &str) -> Option<&StarlarkCommandExecutorConfig> {
        let executor_overrides = DictRef::from_value(self.executor_overrides.to_value()).unwrap();
        executor_overrides
            .get_str(key)
            .map(|v| StarlarkCommandExecutorConfig::from_value(v.to_value()).unwrap())
    }

    pub fn local_resources(&self) -> IndexMap<&str, Option<&FrozenLocalResourceInfo>> {
        unwrap_all(iter_local_resources(self.local_resources.to_value())).collect()
    }

    pub fn visit_artifacts(
        &self,
        visitor: &mut dyn CommandLineArtifactVisitor,
    ) -> anyhow::Result<()> {
        for member in self.command() {
            match member {
                TestCommandMember::Literal(..) => {}
                TestCommandMember::Arglike(arglike) => {
                    arglike.visit_artifacts(visitor)?;
                }
            }
        }

        for (_, arglike) in self.env() {
            arglike.visit_artifacts(visitor)?;
        }

        // Ignoring local resources as those are built on-demand.

        Ok(())
    }
}

pub enum TestCommandMember<'v> {
    Literal(&'v str),
    Arglike(&'v dyn CommandLineArgLike),
}

impl<'v> TestCommandMember<'v> {
    pub fn add_to_command_line(
        &self,
        cli: &mut dyn CommandLineBuilder,
        context: &mut dyn CommandLineContext,
    ) -> anyhow::Result<()> {
        match self {
            Self::Literal(literal) => literal.add_to_command_line(cli, context),
            Self::Arglike(arglike) => arglike.add_to_command_line(cli, context),
        }
    }
}

fn iter_value<'v>(value: Value<'v>) -> anyhow::Result<impl Iterator<Item = Value<'v>> + 'v> {
    match (ListRef::from_value(value), TupleRef::from_value(value)) {
        (Some(list), None) => Ok(Either::Left(list.iter())),
        (None, Some(tuple)) => Ok(Either::Right(tuple.iter())),
        _ => Err(anyhow::anyhow!(
            "Expected a list or a tuple, got `{}`",
            value
        )),
    }
}

fn iter_test_command<'v>(
    command: Value<'v>,
) -> impl Iterator<Item = anyhow::Result<TestCommandMember<'v>>> {
    if command.is_none() {
        return Either::Left(Either::Left(empty()));
    }

    let iterable = match iter_value(command) {
        Ok(v) => v,
        Err(e) => {
            return Either::Left(Either::Right(once(Err(e.context("Invalid `command`")))));
        }
    };

    Either::Right(iterable.map(|item| {
        if let Some(s) = item.unpack_str() {
            return Ok(TestCommandMember::Literal(s));
        }

        if let Some(s) = item.downcast_ref::<ResolvedStringWithMacros>() {
            if let Some(s) = s.downcast_str() {
                return Ok(TestCommandMember::Literal(s));
            }
        }

        let arglike = item
            .as_command_line_err()
            .context("Invalid item in `command`")?;

        Ok(TestCommandMember::Arglike(arglike))
    }))
}

fn iter_test_env<'v>(
    env: Value<'v>,
) -> impl Iterator<Item = anyhow::Result<(&'v str, &'v dyn CommandLineArgLike)>> {
    if env.is_none() {
        return Either::Left(Either::Left(empty()));
    }

    let env = match DictRef::from_value(env) {
        Some(env) => env,
        None => {
            return Either::Left(Either::Right(once(Err(anyhow::anyhow!(
                "Invalid `env`: Expected a dict, got: `{}`",
                env
            )))));
        }
    };

    // TODO: In an ideal world this wouldnt be necessary, but env's lifetime is bound by this
    // function.
    #[allow(clippy::needless_collect)]
    let env = env.iter().collect::<Vec<_>>();

    Either::Right(env.into_iter().map(|(key, value)| {
        let key = key
            .unpack_str()
            .with_context(|| format!("Invalid key in `env`: Expected a str, got: `{}`", key))?;

        let arglike = value
            .as_command_line_err()
            .with_context(|| format!("Invalid value in `env` for key `{}`", key))?;

        Ok((key, arglike))
    }))
}

fn iter_opt_str_list<'v>(
    list: Value<'v>,
    name: &'static str,
) -> impl Iterator<Item = anyhow::Result<&'v str>> {
    if list.is_none() {
        return Either::Left(Either::Left(empty()));
    }

    let iterable = match iter_value(list) {
        Ok(v) => v,
        Err(e) => {
            return Either::Left(Either::Right(once(Err(
                e.context(format!("Invalid `{}`", name))
            ))));
        }
    };

    Either::Right(iterable.map(move |item| {
        let item = item
            .unpack_str()
            .with_context(|| format!("Invalid item in `{}`: {}", name, item))?;

        Ok(item)
    }))
}

fn iter_executor_overrides<'v>(
    executor_overrides: Value<'v>,
) -> impl Iterator<Item = anyhow::Result<(&'v str, &'v StarlarkCommandExecutorConfig)>> {
    if executor_overrides.is_none() {
        return Either::Left(Either::Left(empty()));
    }

    let executor_overrides = match DictRef::from_value(executor_overrides) {
        Some(executor_overrides) => executor_overrides,
        None => {
            return Either::Left(Either::Right(once(Err(anyhow::anyhow!(
                "Invalid `executor_overrides`: Expected a dict, got: `{}`",
                executor_overrides
            )))));
        }
    };

    // TODO: In an ideal world this wouldnt be necessary, but executor_overrides's lifetime is
    // bound by this function.
    #[allow(clippy::needless_collect)]
    let executor_overrides = executor_overrides.iter().collect::<Vec<_>>();

    Either::Right(executor_overrides.into_iter().map(|(key, value)| {
        let key = key.unpack_str().with_context(|| {
            format!(
                "Invalid key in `executor_overrides`: Expected a str, got: `{}`",
                key
            )
        })?;

        let config = StarlarkCommandExecutorConfig::from_value(value)
            .with_context(|| format!("Invalid value in `executor_overrides` for key `{}`", key))?;

        Ok((key, config))
    }))
}

fn iter_local_resources<'v>(
    local_resources: Value<'v>,
) -> impl Iterator<Item = anyhow::Result<(&'v str, Option<&'v FrozenLocalResourceInfo>)>> {
    if local_resources.is_none() {
        return Either::Left(Either::Left(empty()));
    }

    let local_resources = match DictRef::from_value(local_resources) {
        Some(local_resources) => local_resources,
        None => {
            return Either::Left(Either::Right(once(Err(anyhow::anyhow!(
                "Invalid `local_resources`: Expected a dict, got: `{}`",
                local_resources
            )))));
        }
    };

    // TODO: In an ideal world this wouldnt be necessary, but local_resources's lifetime is
    // bound by this function.
    #[allow(clippy::needless_collect)]
    let local_resources = local_resources.iter().collect::<Vec<_>>();

    Either::Right(local_resources.into_iter().map(|(key, value)| {
        let key = key.unpack_str().with_context(|| {
            format!(
                "Invalid key in `local_resources`: Expected a str, got: `{}`",
                key
            )
        })?;

        let resource = if value.is_none() {
            None
        } else {
            Some(
                value
                    .downcast_ref::<FrozenLocalResourceInfo>()
                    .with_context(|| {
                        format!("Invalid value in `local_resources` for key `{}`", key)
                    })?,
            )
        };

        Ok((key, resource))
    }))
}

fn unpack_opt_executor<'v>(
    executor: Value<'v>,
) -> anyhow::Result<Option<&'v StarlarkCommandExecutorConfig>> {
    if executor.is_none() {
        return Ok(None);
    }

    let executor = StarlarkCommandExecutorConfig::from_value(executor)
        .with_context(|| format!("Value is not an executor config: `{}`", executor))?;

    Ok(Some(executor))
}

fn check_all<I, T>(it: I) -> anyhow::Result<()>
where
    I: IntoIterator<Item = anyhow::Result<T>>,
{
    for e in it {
        e?;
    }
    Ok(())
}

fn unwrap_all<I, T>(it: I) -> impl Iterator<Item = T>
where
    I: IntoIterator<Item = anyhow::Result<T>>,
{
    it.into_iter().map(|e| e.unwrap())
}

fn validate_external_runner_test_info<'v, V>(
    info: &ExternalRunnerTestInfoGen<V>,
) -> anyhow::Result<()>
where
    V: ValueLike<'v>,
{
    check_all(iter_test_command(info.command.to_value()))?;
    check_all(iter_test_env(info.env.to_value()))?;
    check_all(iter_opt_str_list(info.labels.to_value(), "labels"))?;
    check_all(iter_opt_str_list(info.contacts.to_value(), "contacts"))?;
    check_all(iter_executor_overrides(info.executor_overrides.to_value()))?;
    check_all(iter_local_resources(info.local_resources.to_value()))?;
    NoneOr::<bool>::unpack_value(info.use_project_relative_paths.to_value())
        .context("`use_project_relative_paths` must be a bool if provided")?;
    NoneOr::<bool>::unpack_value(info.run_from_project_root.to_value())
        .context("`run_from_project_root` must be a bool if provided")?;
    unpack_opt_executor(info.default_executor.to_value()).context("Invalid `default_executor`")?;
    info.test_type
        .to_value()
        .unpack_str()
        .context("`type` must be a str")?;
    Ok(())
}

#[starlark_module]
fn external_runner_test_info_creator(globals: &mut GlobalsBuilder) {
    #[starlark(as_type = FrozenExternalRunnerTestInfo)]
    fn ExternalRunnerTestInfo<'v>(
        r#type: Value<'v>,
        #[starlark(default = NoneType)] command: Value<'v>,
        #[starlark(default = NoneType)] env: Value<'v>,
        #[starlark(default = NoneType)] labels: Value<'v>,
        #[starlark(default = NoneType)] contacts: Value<'v>,
        #[starlark(default = NoneType)] use_project_relative_paths: Value<'v>,
        #[starlark(default = NoneType)] run_from_project_root: Value<'v>,
        #[starlark(default = NoneType)] default_executor: Value<'v>,
        #[starlark(default = NoneType)] executor_overrides: Value<'v>,
        #[starlark(default = NoneType)] local_resources: Value<'v>,
    ) -> anyhow::Result<ExternalRunnerTestInfo<'v>> {
        let res = ExternalRunnerTestInfo {
            test_type: r#type,
            command,
            env,
            labels,
            contacts,
            use_project_relative_paths,
            run_from_project_root,
            default_executor,
            executor_overrides,
            local_resources,
        };
        validate_external_runner_test_info(&res)?;
        Ok(res)
    }
}