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

use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use std::time::Instant;

use allocative::Allocative;
use anyhow::Context;
use async_trait::async_trait;
use buck2_build_api::analysis::calculation::RuleAnalsysisCalculationImpl;
use buck2_build_api::analysis::calculation::RuleAnalysisCalculation;
use buck2_build_api::analysis::calculation::EVAL_ANALYSIS_QUERY;
use buck2_build_api::analysis::calculation::RULE_ANALYSIS_CALCULATION;
use buck2_build_api::analysis::AnalysisResult;
use buck2_build_api::keep_going;
use buck2_common::result::SharedResult;
use buck2_common::result::ToUnsharedResultExt;
use buck2_core::configuration::compatibility::MaybeCompatible;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_data::ToProtoMessage;
use buck2_events::dispatch::async_record_root_spans;
use buck2_events::dispatch::span_async;
use buck2_events::span::SpanId;
use buck2_interpreter::dice::starlark_profiler::GetStarlarkProfilerInstrumentation;
use buck2_interpreter::load_module::InterpreterCalculation;
use buck2_interpreter::starlark_profiler::StarlarkProfileDataAndStats;
use buck2_interpreter::starlark_profiler::StarlarkProfileModeOrInstrumentation;
use buck2_node::attrs::attr_type::query::ResolvedQueryLiterals;
use buck2_node::nodes::configured::ConfiguredTargetNode;
use buck2_node::nodes::configured_frontend::ConfiguredTargetNodeCalculation;
use buck2_node::rule_type::RuleType;
use buck2_node::rule_type::StarlarkRuleType;
use buck2_query::query::syntax::simple::eval::label_indexed::LabelIndexedSet;
use buck2_query::query::syntax::simple::eval::set::TargetSet;
use dice::CancellationContext;
use dice::DiceComputations;
use dice::Key;
use dupe::Dupe;
use dupe::IterDupedExt;
use futures::stream::FuturesOrdered;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use smallvec::SmallVec;
use starlark::eval::ProfileMode;

use crate::analysis::env::get_user_defined_rule_impl;
use crate::analysis::env::run_analysis;
use crate::analysis::env::RuleImplFunction;
use crate::attrs::resolve::ctx::AnalysisQueryResult;

#[derive(Debug, thiserror::Error)]
enum AnalysisCalculationError {
    #[error("Internal error: literal `{0}` not found in `deps`")]
    LiteralNotFoundInDeps(String),
}

struct RuleAnalysisCalculationInstance;

#[derive(
    Clone,
    Dupe,
    derive_more::Display,
    Debug,
    Eq,
    Hash,
    PartialEq,
    Allocative
)]
#[display(fmt = "{}", "_0")]
pub struct AnalysisKey(pub ConfiguredTargetLabel);

pub(crate) fn init_rule_analysis_calculation() {
    RULE_ANALYSIS_CALCULATION.init(&RuleAnalysisCalculationInstance);
}

#[async_trait]
impl RuleAnalsysisCalculationImpl for RuleAnalysisCalculationInstance {
    async fn get_analysis_result(
        &self,
        ctx: &DiceComputations,
        target: &ConfiguredTargetLabel,
    ) -> anyhow::Result<MaybeCompatible<AnalysisResult>> {
        #[async_trait]
        impl Key for AnalysisKey {
            type Value = SharedResult<MaybeCompatible<AnalysisResult>>;
            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                let profile_mode = ctx.get_profile_mode_for_intermediate_analysis().await?;
                Ok(get_analysis_result(ctx, &self.0, &profile_mode)
                    .await
                    .with_context(|| format!("Error running analysis for `{}`", &self.0))?)
            }

            fn equality(_: &Self::Value, _: &Self::Value) -> bool {
                // analysis result is not comparable
                // TODO consider if we want analysis result to be eq
                false
            }
        }

        ctx.compute(&AnalysisKey(target.dupe()))
            .await?
            .unshared_error()
    }
}

pub async fn resolve_queries(
    ctx: &DiceComputations,
    configured_node: &ConfiguredTargetNode,
) -> anyhow::Result<HashMap<String, Arc<AnalysisQueryResult>>> {
    let mut queries = configured_node.queries().peekable();

    if queries.peek().is_none() {
        return Ok(Default::default());
    }

    span_async(
        buck2_data::AnalysisStageStart {
            stage: Some(buck2_data::analysis_stage_start::Stage::ResolveQueries(())),
        },
        async {
            (
                resolve_queries_impl(ctx, configured_node, queries).await,
                buck2_data::AnalysisStageEnd {},
            )
        },
    )
    .await
}

async fn resolve_queries_impl(
    ctx: &DiceComputations,
    configured_node: &ConfiguredTargetNode,
    queries: impl Iterator<Item = (String, ResolvedQueryLiterals<ConfiguredProvidersLabel>)>,
) -> anyhow::Result<HashMap<String, Arc<AnalysisQueryResult>>> {
    let deps: TargetSet<_> = configured_node.deps().duped().collect();
    let query_results =
        futures::future::try_join_all(queries.map(|(query, resolved_literals_labels)| {
            let ctx = ctx;
            let deps = &deps;
            async move {
                let mut resolved_literals =
                    HashMap::with_capacity(resolved_literals_labels.0.len());
                for (literal, label) in resolved_literals_labels.0 {
                    let node = deps.get(label.target()).ok_or_else(|| {
                        AnalysisCalculationError::LiteralNotFoundInDeps(literal.clone())
                    })?;
                    resolved_literals.insert(literal, node.dupe());
                }

                let result = (EVAL_ANALYSIS_QUERY.get()?)(ctx, &query, resolved_literals).await?;

                // analysis for all the deps in the query result should already have been run since they must
                // be in our dependency graph, and so we don't worry about parallelizing these lookups.
                let mut query_results = Vec::new();
                for node in result.iter() {
                    let label = node.label();
                    query_results.push((
                        label.dupe(),
                        ctx.get_analysis_result(label)
                            .await?
                            .require_compatible()?
                            .provider_collection,
                    ))
                }

                anyhow::Ok((query.to_owned(), Arc::new(query_results)))
            }
        }))
        .await?;

    let query_results: HashMap<_, _> = query_results.into_iter().collect();
    Ok(query_results)
}

pub async fn get_dep_analysis<'v>(
    configured_node: &'v ConfiguredTargetNode,
    ctx: &DiceComputations,
) -> anyhow::Result<Vec<(&'v ConfiguredTargetLabel, AnalysisResult)>> {
    keep_going::try_join_all(
        ctx,
        configured_node
            .deps()
            .map(async move |dep| {
                let res = ctx
                    .get_analysis_result(dep.label())
                    .await
                    .and_then(|v| v.require_compatible());
                res.map(|x| (dep.label(), x))
            })
            .collect::<FuturesUnordered<_>>(),
    )
    .await
}

pub async fn get_rule_impl(
    ctx: &DiceComputations,
    func: &StarlarkRuleType,
) -> anyhow::Result<impl RuleImplFunction> {
    let module = ctx
        .get_loaded_module_from_import_path(&func.import_path)
        .await?;
    Ok(get_user_defined_rule_impl(module.env().dupe(), func))
}

async fn get_analysis_result(
    ctx: &DiceComputations,
    target: &ConfiguredTargetLabel,
    profile_mode: &StarlarkProfileModeOrInstrumentation,
) -> anyhow::Result<MaybeCompatible<AnalysisResult>> {
    let configured_node: MaybeCompatible<ConfiguredTargetNode> =
        ctx.get_configured_target_node(target).await?;
    let configured_node: ConfiguredTargetNode = match configured_node {
        MaybeCompatible::Incompatible(reason) => {
            return Ok(MaybeCompatible::Incompatible(reason));
        }
        MaybeCompatible::Compatible(configured_node) => configured_node,
    };

    let configured_node = &configured_node;
    let mut dep_analysis = get_dep_analysis(configured_node, ctx).await?;

    let now = Instant::now();

    let (res, spans) = async_record_root_spans(async move {
        let func = configured_node.rule_type();
        match func {
            RuleType::Starlark(func) => {
                let rule_impl = get_rule_impl(ctx, func).await?;
                let start_event = buck2_data::AnalysisStart {
                    target: Some(target.as_proto().into()),
                    rule: func.to_string(),
                };

                span_async(start_event, async {
                    let mut profile = None;

                    let result: anyhow::Result<_> = try {
                        let query_results = resolve_queries(ctx, configured_node).await?;

                        let result = span_async(
                            buck2_data::AnalysisStageStart {
                                stage: Some(buck2_data::analysis_stage_start::Stage::EvaluateRule(
                                    (),
                                )),
                            },
                            async {
                                (
                                    run_analysis(
                                        ctx,
                                        target,
                                        dep_analysis,
                                        query_results,
                                        configured_node.execution_platform_resolution(),
                                        &rule_impl,
                                        configured_node,
                                        profile_mode,
                                    )
                                    .await,
                                    buck2_data::AnalysisStageEnd {},
                                )
                            },
                        )
                        .await?;

                        profile = Some(make_analysis_profile(&result));

                        MaybeCompatible::Compatible(result)
                    };

                    (
                        result,
                        buck2_data::AnalysisEnd {
                            target: Some(target.as_proto().into()),
                            rule: func.to_string(),
                            profile,
                        },
                    )
                })
                .await
            }
            RuleType::Forward => {
                assert!(dep_analysis.len() == 1);
                Ok(MaybeCompatible::Compatible(dep_analysis.pop().unwrap().1))
            }
        }
    })
    .await;

    ctx.store_evaluation_data(AnalysisKeyActivationData {
        duration: now.elapsed(),
        spans,
    })?;

    res
}

fn make_analysis_profile(res: &AnalysisResult) -> buck2_data::AnalysisProfile {
    let heap = res.providers().value().owner();

    buck2_data::AnalysisProfile {
        starlark_allocated_bytes: heap.allocated_bytes() as u64,
        starlark_available_bytes: heap.available_bytes() as u64,
    }
}

#[derive(Debug, thiserror::Error)]
enum ProfileAnalysisError {
    #[error("recursive analysis configured incorrectly (internal error)")]
    RecursiveProfileConfiguredIncorrectly,
}

/// Run get_analysis_result but discard the results (public outside the `analysis` module, unlike
/// get_analysis_result)
pub async fn profile_analysis(
    ctx: &DiceComputations,
    target: &ConfiguredTargetLabel,
    profile_mode: &ProfileMode,
) -> anyhow::Result<Arc<StarlarkProfileDataAndStats>> {
    get_analysis_result(
        ctx,
        target,
        &StarlarkProfileModeOrInstrumentation::Profile(profile_mode.dupe()),
    )
    .await?
    .require_compatible()?
    .profile_data
    .context("profile_data not set (internal error)")
}

fn all_deps(node: ConfiguredTargetNode) -> LabelIndexedSet<ConfiguredTargetNode> {
    let mut stack = vec![node];
    let mut visited = LabelIndexedSet::new();
    while let Some(node) = stack.pop() {
        if visited.insert(node.dupe()) {
            stack.extend(node.deps().duped());
        }
    }
    visited
}

pub async fn profile_analysis_recursively(
    ctx: &DiceComputations,
    target: &ConfiguredTargetLabel,
) -> anyhow::Result<StarlarkProfileDataAndStats> {
    // Self check.
    let profile_mode = ctx.get_profile_mode_for_intermediate_analysis().await?;
    if !matches!(
        profile_mode,
        StarlarkProfileModeOrInstrumentation::Profile(_)
    ) {
        return Err(ProfileAnalysisError::RecursiveProfileConfiguredIncorrectly.into());
    }

    let node = ctx
        .get_configured_target_node(target)
        .await?
        .require_compatible()?;

    let all_deps = all_deps(node);

    let mut futures = all_deps
        .iter()
        .map(|node| ctx.get_analysis_result(node.label()))
        .collect::<FuturesOrdered<_>>();

    let mut profile_datas: Vec<Arc<StarlarkProfileDataAndStats>> = Vec::new();
    while let Some(result) = futures.next().await {
        profile_datas.push(
            result?
                .require_compatible()?
                .profile_data
                .context("profile_data not set (internal error)")?,
        );
    }

    StarlarkProfileDataAndStats::merge(profile_datas.iter().map(|x| &**x))
}

pub struct AnalysisKeyActivationData {
    pub duration: Duration,
    pub spans: SmallVec<[SpanId; 1]>,
}


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

use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;

use anyhow::Context;
use buck2_build_api::analysis::registry::AnalysisRegistry;
use buck2_build_api::analysis::AnalysisResult;
use buck2_build_api::deferred::types::DeferredTable;
use buck2_build_api::interpreter::rule_defs::cmd_args::value::FrozenCommandLineArg;
use buck2_build_api::interpreter::rule_defs::context::AnalysisContext;
use buck2_build_api::interpreter::rule_defs::provider::builtin::template_placeholder_info::FrozenTemplatePlaceholderInfo;
use buck2_build_api::interpreter::rule_defs::provider::collection::FrozenProviderCollectionValue;
use buck2_build_api::interpreter::rule_defs::provider::collection::ProviderCollection;
use buck2_common::result::SharedResult;
use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersName;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_core::unsafe_send_future::UnsafeSendFuture;
use buck2_events::dispatch::get_dispatcher;
use buck2_execute::digest_config::HasDigestConfig;
use buck2_interpreter::dice::starlark_provider::with_starlark_eval_provider;
use buck2_interpreter::print_handler::EventDispatcherPrintHandler;
use buck2_interpreter::starlark_profiler::StarlarkProfileModeOrInstrumentation;
use buck2_interpreter::starlark_profiler::StarlarkProfiler;
use buck2_interpreter::starlark_profiler::StarlarkProfilerOrInstrumentation;
use buck2_interpreter::types::label::Label;
use buck2_interpreter::types::rule::FROZEN_RULE_GET_IMPL;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use buck2_node::nodes::configured::ConfiguredTargetNode;
use buck2_node::rule_type::StarlarkRuleType;
use dice::DiceComputations;
use dupe::Dupe;
use starlark::environment::FrozenModule;
use starlark::environment::Module;
use starlark::eval::Evaluator;
use starlark::values::Value;
use starlark::values::ValueTyped;

use crate::attrs::resolve::ctx::AnalysisQueryResult;
use crate::attrs::resolve::ctx::AttrResolutionContext;
use crate::attrs::resolve::node_to_attrs_struct::node_to_attrs_struct;

#[derive(thiserror::Error, Debug)]
enum AnalysisError {
    #[error(
        "Analysis context was missing a query result, this shouldn't be possible. Query was `{0}`"
    )]
    MissingQuery(String),
    #[error("required dependency `{0}` was not found")]
    MissingDep(ConfiguredProvidersLabel),
}

// Contains a `module` that things must live on, and various `FrozenProviderCollectionValue`s
// that are NOT tied to that module. Must claim ownership of them via `add_reference` before returning them.
pub struct RuleAnalysisAttrResolutionContext<'v> {
    pub module: &'v Module,
    pub dep_analysis_results: HashMap<&'v ConfiguredTargetLabel, FrozenProviderCollectionValue>,
    pub query_results: HashMap<String, Arc<AnalysisQueryResult>>,
    pub execution_platform_resolution: ExecutionPlatformResolution,
}

impl<'v> AttrResolutionContext<'v> for RuleAnalysisAttrResolutionContext<'v> {
    fn starlark_module(&self) -> &'v Module {
        self.module
    }

    fn get_dep(
        &self,
        target: &ConfiguredProvidersLabel,
    ) -> anyhow::Result<FrozenProviderCollectionValue> {
        get_dep(&self.dep_analysis_results, target, self.module)
    }

    fn resolve_unkeyed_placeholder(
        &self,
        name: &str,
    ) -> anyhow::Result<Option<FrozenCommandLineArg>> {
        Ok(resolve_unkeyed_placeholder(
            &self.dep_analysis_results,
            name,
            self.module,
        ))
    }

    fn resolve_query(&self, query: &str) -> SharedResult<Arc<AnalysisQueryResult>> {
        resolve_query(&self.query_results, query, self.module)
    }

    fn execution_platform_resolution(&self) -> &ExecutionPlatformResolution {
        &self.execution_platform_resolution
    }
}

pub fn get_dep<'v>(
    dep_analysis_results: &HashMap<&'v ConfiguredTargetLabel, FrozenProviderCollectionValue>,
    target: &ConfiguredProvidersLabel,
    module: &'v Module,
) -> anyhow::Result<FrozenProviderCollectionValue> {
    match dep_analysis_results.get(target.target()) {
        None => Err(AnalysisError::MissingDep(target.clone()).into()),
        Some(x) => {
            let x = x.lookup_inner(target)?;
            // IMPORTANT: Anything given back to the user must be kept alive
            module.frozen_heap().add_reference(x.value().owner());
            Ok(x.dupe())
        }
    }
}

pub fn resolve_unkeyed_placeholder<'v>(
    dep_analysis_results: &HashMap<&'v ConfiguredTargetLabel, FrozenProviderCollectionValue>,
    name: &str,
    module: &'v Module,
) -> Option<FrozenCommandLineArg> {
    // TODO(cjhopman): Make it an error if two deps provide a value for the placeholder.
    for providers in dep_analysis_results.values() {
        if let Some(placeholder_info) =
            FrozenTemplatePlaceholderInfo::from_providers(providers.provider_collection())
        {
            if let Some(value) = placeholder_info.unkeyed_variables().get(name) {
                // IMPORTANT: Anything given back to the user must be kept alive
                module
                    .frozen_heap()
                    .add_reference(providers.value().owner());
                return Some(*value);
            }
        }
    }
    None
}

pub fn resolve_query<'v>(
    query_results: &HashMap<String, Arc<AnalysisQueryResult>>,
    query: &str,
    module: &'v Module,
) -> SharedResult<Arc<AnalysisQueryResult>> {
    match query_results.get(query) {
        None => Err(anyhow::anyhow!(AnalysisError::MissingQuery(query.to_owned())).into()),
        Some(x) => {
            for (_, y) in x.iter() {
                // IMPORTANT: Anything given back to the user must be kept alive
                module.frozen_heap().add_reference(y.value().owner());
            }
            Ok(x.dupe())
        }
    }
}

pub trait RuleImplFunction: Sync {
    fn invoke<'v>(
        &self,
        eval: &mut Evaluator<'v, '_>,
        ctx: ValueTyped<'v, AnalysisContext<'v>>,
    ) -> anyhow::Result<Value<'v>>;
}

/// Container for the environment that analysis implementation functions should run in
struct AnalysisEnv<'a> {
    impl_function: &'a dyn RuleImplFunction,
    deps: HashMap<&'a ConfiguredTargetLabel, FrozenProviderCollectionValue>,
    query_results: HashMap<String, Arc<AnalysisQueryResult>>,
    execution_platform: &'a ExecutionPlatformResolution,
    label: ConfiguredTargetLabel,
}

pub(crate) async fn run_analysis<'a>(
    dice: &DiceComputations,
    label: &ConfiguredTargetLabel,
    results: Vec<(&'a ConfiguredTargetLabel, AnalysisResult)>,
    query_results: HashMap<String, Arc<AnalysisQueryResult>>,
    execution_platform: &'a ExecutionPlatformResolution,
    impl_function: &'a dyn RuleImplFunction,
    node: &ConfiguredTargetNode,
    profile_mode: &StarlarkProfileModeOrInstrumentation,
) -> anyhow::Result<AnalysisResult> {
    let analysis_env = AnalysisEnv::new(
        label,
        results,
        query_results,
        execution_platform,
        impl_function,
    )?;
    run_analysis_with_env(dice, analysis_env, node, profile_mode).await
}

impl<'a> AnalysisEnv<'a> {
    /// Create a new `AnalysisEnv`, ensuring that all heaps are kept alive that need to be
    fn new(
        label: &ConfiguredTargetLabel,
        results: Vec<(&'a ConfiguredTargetLabel, AnalysisResult)>,
        query_results: HashMap<String, Arc<AnalysisQueryResult>>,
        execution_platform: &'a ExecutionPlatformResolution,
        impl_function: &'a dyn RuleImplFunction,
    ) -> anyhow::Result<Self> {
        Ok(AnalysisEnv {
            impl_function,
            deps: get_deps_from_analysis_results(results)?,
            query_results,
            execution_platform,
            label: label.dupe(),
        })
    }
}

pub fn get_deps_from_analysis_results<'v>(
    results: Vec<(&'v ConfiguredTargetLabel, AnalysisResult)>,
) -> anyhow::Result<HashMap<&'v ConfiguredTargetLabel, FrozenProviderCollectionValue>> {
    results
        .into_iter()
        .map(|(label, result)| Ok((label, result.providers().dupe())))
        .collect::<anyhow::Result<HashMap<&ConfiguredTargetLabel, FrozenProviderCollectionValue>>>()
}

fn run_analysis_with_env<'a>(
    dice: &'a DiceComputations,
    analysis_env: AnalysisEnv<'a>,
    node: &'a ConfiguredTargetNode,
    profile_mode: &'a StarlarkProfileModeOrInstrumentation,
) -> impl Future<Output = anyhow::Result<AnalysisResult>> + Send + 'a {
    let fut = async move {
        run_analysis_with_env_underlying(dice, analysis_env, node, profile_mode).await
    };
    unsafe { UnsafeSendFuture::new_encapsulates_starlark(fut) }
}

async fn run_analysis_with_env_underlying(
    dice: &DiceComputations,
    analysis_env: AnalysisEnv<'_>,
    node: &ConfiguredTargetNode,
    profile_mode: &StarlarkProfileModeOrInstrumentation,
) -> anyhow::Result<AnalysisResult> {
    let env = Module::new();
    let print = EventDispatcherPrintHandler(get_dispatcher());

    let attributes = {
        let resolution_ctx = RuleAnalysisAttrResolutionContext {
            module: &env,
            dep_analysis_results: analysis_env.deps,
            query_results: analysis_env.query_results,
            execution_platform_resolution: node.execution_platform_resolution().clone(),
        };

        node_to_attrs_struct(node, &resolution_ctx)?
    };

    let registry = AnalysisRegistry::new_from_owner(
        BaseDeferredKey::TargetLabel(node.label().dupe()),
        analysis_env.execution_platform.dupe(),
    )?;

    let mut profiler_opt = profile_mode
        .profile_mode()
        .map(|profile_mode| StarlarkProfiler::new(profile_mode.dupe(), true));

    let mut profiler = match &mut profiler_opt {
        None => StarlarkProfilerOrInstrumentation::disabled(),
        Some(profiler) => StarlarkProfilerOrInstrumentation::for_profiler(profiler),
    };

    let (mut eval, ctx, list_res) = with_starlark_eval_provider(
        dice,
        &mut profiler,
        format!("analysis:{}", node.label()),
        |provider| {
            let mut eval = provider.make(&env)?;
            eval.set_print_handler(&print);

            let ctx = env.heap().alloc_typed(AnalysisContext::new(
                eval.heap(),
                attributes,
                Some(
                    eval.heap()
                        .alloc_typed(Label::new(ConfiguredProvidersLabel::new(
                            analysis_env.label,
                            ProvidersName::Default,
                        ))),
                ),
                registry,
                dice.global_data().get_digest_config(),
            ));

            let list_res = analysis_env.impl_function.invoke(&mut eval, ctx)?;

            // TODO(cjhopman): This seems quite wrong. This should be happening after run_promises.
            provider
                .evaluation_complete(&mut eval)
                .context("Profiler finalization failed")?;
            // TODO(cjhopman): This is gross, but we can't await on running the promises within
            // the with_starlark_eval_provider scoped thing (as we may be holding a debugger
            // permit, running the promises may require doing more starlark evaluation which in
            // turn requires those permits). We will actually re-enter a provider scope in the
            // run_promises call when we get back to resolving the promises (and running the starlark
            // Promise::map() lambdas).
            Ok((eval, ctx, list_res))
        },
    )
    .await?;

    ctx.actions
        .run_promises(
            dice,
            &mut eval,
            format!("anon_analysis$promises:{}", node.label()),
        )
        .await?;

    // TODO: Convert the ValueError from `try_from_value` better than just printing its Debug
    let res_typed = ProviderCollection::try_from_value(list_res)?;
    let res = env.heap().alloc(res_typed);
    env.set_extra_value(res);

    // Pull the ctx object back out, and steal ctx.action's state back
    let analysis_registry = ctx.take_state();
    std::mem::drop(eval);
    let (frozen_env, deferreds) = analysis_registry.finalize(&env)?(env)?;

    profiler
        .visit_frozen_module(Some(&frozen_env))
        .context("Profiler heap visitation failed")?;

    let profile_data = profiler_opt.map(|p| p.finish()).transpose()?.map(Arc::new);

    let res = frozen_env
        .owned_extra_value()
        .context("extra_value not set (internal error)")?;
    let provider_collection = FrozenProviderCollectionValue::try_from_value(res)
        .expect("just created this, this shouldn't happen");

    // this could look nicer if we had the entire analysis be a deferred
    let deferred = DeferredTable::new(deferreds.take_result()?);
    Ok(AnalysisResult::new(
        provider_collection,
        deferred,
        profile_data,
    ))
}

pub fn get_user_defined_rule_impl(
    module: FrozenModule,
    rule_type: &StarlarkRuleType,
) -> impl RuleImplFunction {
    struct Impl {
        module: FrozenModule,
        name: String,
    }

    impl RuleImplFunction for Impl {
        fn invoke<'v>(
            &self,
            eval: &mut Evaluator<'v, '_>,
            ctx: ValueTyped<'v, AnalysisContext<'v>>,
        ) -> anyhow::Result<Value<'v>> {
            let rule_callable = self
                .module
                .get_any_visibility(&self.name)
                .with_context(|| format!("Couldn't find rule `{}`", self.name))?
                .0;
            let rule_impl = {
                // Need to free up the starlark_ctx borrow before we return
                let rule_callable = rule_callable.owned_value(eval.frozen_heap());
                let rule_callable = rule_callable
                    .unpack_frozen()
                    .context("Must be frozen (internal error)")?;

                (FROZEN_RULE_GET_IMPL.get()?)(rule_callable)?
            };
            eval.eval_function(rule_impl.to_value(), &[ctx.to_value()], &[])
        }
    }

    Impl {
        module,
        name: rule_type.name.clone(),
    }
}


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

pub mod calculation;
pub mod env;


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

pub mod resolve;


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

use buck2_artifact::artifact::artifact_type::Artifact;
use buck2_artifact::artifact::source_artifact::SourceArtifact;
use buck2_build_api::interpreter::rule_defs::artifact::StarlarkArtifact;
use buck2_build_api::interpreter::rule_defs::provider::dependency::DependencyGen;
use buck2_core::buck_path::path::BuckPath;
use buck2_core::package::PackageLabel;
use buck2_interpreter::types::label::Label;
use buck2_interpreter::types::target_label::StarlarkTargetLabel;
use buck2_node::attrs::attr_type::configuration_dep::ConfigurationDepAttrType;
use buck2_node::attrs::attr_type::configured_dep::ExplicitConfiguredDepAttrType;
use buck2_node::attrs::attr_type::dep::DepAttrType;
use buck2_node::attrs::attr_type::source::SourceAttrType;
use buck2_node::attrs::attr_type::split_transition_dep::SplitTransitionDepAttrType;
use buck2_node::attrs::configured_attr::ConfiguredAttr;
use buck2_node::visibility::VisibilityPatternList;
use buck2_node::visibility::VisibilitySpecification;
use buck2_node::visibility::WithinViewSpecification;
use dupe::Dupe;
use gazebo::prelude::SliceExt;
use starlark::values::dict::Dict;
use starlark::values::list::AllocList;
use starlark::values::list::ListRef;
use starlark::values::none::NoneType;
use starlark::values::tuple::AllocTuple;
use starlark::values::FrozenValue;
use starlark::values::Heap;
use starlark::values::StarlarkValue;
use starlark::values::Value;
use starlark_map::small_map::SmallMap;

use crate::attrs::resolve::attr_type::arg::ConfiguredStringWithMacrosExt;
use crate::attrs::resolve::attr_type::configuration_dep::ConfigurationDepAttrTypeExt;
use crate::attrs::resolve::attr_type::dep::DepAttrTypeExt;
use crate::attrs::resolve::attr_type::dep::ExplicitConfiguredDepAttrTypeExt;
use crate::attrs::resolve::attr_type::query::ConfiguredQueryAttrExt;
use crate::attrs::resolve::attr_type::source::SourceAttrTypeExt;
use crate::attrs::resolve::attr_type::split_transition_dep::SplitTransitionDepAttrTypeExt;
use crate::attrs::resolve::ctx::AttrResolutionContext;

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

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

    fn starlark_type(&self) -> anyhow::Result<&'static str>;

    fn to_value<'v>(&self, pkg: PackageLabel, heap: &'v Heap) -> anyhow::Result<Value<'v>>;
}

impl ConfiguredAttrExt for ConfiguredAttr {
    /// "Resolves" the configured value to the resolved value provided to the rule implementation.
    ///
    /// `resolve` may return multiple values. It is up to the caller to fail if
    /// an inappropriate number of elements is returned. e.g. `attrs.list()` might
    /// accept and merge multiple returned values from `attrs.source()`, but
    /// `attrs.optional()` might only accept a single value, and fail otherwise.
    fn resolve<'v>(
        &self,
        pkg: PackageLabel,
        ctx: &dyn AttrResolutionContext<'v>,
    ) -> anyhow::Result<Vec<Value<'v>>> {
        match self {
            // SourceLabel is special since it is the only type that can be expand to many
            ConfiguredAttr::SourceLabel(src) => SourceAttrType::resolve_label(ctx, src),
            _ => Ok(vec![self.resolve_single(pkg, ctx)?]),
        }
    }

    /// Resolving a single value is common, so `resolve_single` will validate
    /// this function's output, and return a single value or an error.
    fn resolve_single<'v>(
        &self,
        pkg: PackageLabel,
        ctx: &dyn AttrResolutionContext<'v>,
    ) -> anyhow::Result<Value<'v>> {
        match self {
            ConfiguredAttr::Bool(v) => Ok(Value::new_bool(v.0)),
            ConfiguredAttr::Int(v) => Ok(ctx.heap().alloc(*v)),
            ConfiguredAttr::String(v) | ConfiguredAttr::EnumVariant(v) => {
                Ok(ctx.heap().alloc(v.as_str()))
            }
            ConfiguredAttr::List(list) => {
                let mut values = Vec::with_capacity(list.len());
                for v in list.iter() {
                    values.append(&mut v.resolve(pkg.dupe(), ctx)?);
                }
                Ok(ctx.heap().alloc(values))
            }
            ConfiguredAttr::Tuple(list) => {
                let mut values = Vec::with_capacity(list.len());
                for v in list.iter() {
                    values.append(&mut v.resolve(pkg.dupe(), ctx)?);
                }
                Ok(ctx.heap().alloc(AllocTuple(values)))
            }
            ConfiguredAttr::Dict(dict) => {
                let mut res = SmallMap::with_capacity(dict.len());
                for (k, v) in dict.iter() {
                    res.insert_hashed(
                        k.resolve_single(pkg.dupe(), ctx)?.get_hashed()?,
                        v.resolve_single(pkg.dupe(), ctx)?,
                    );
                }
                Ok(ctx.heap().alloc(Dict::new(res)))
            }
            ConfiguredAttr::None => Ok(Value::new_none()),
            ConfiguredAttr::OneOf(box l, _) => l.resolve_single(pkg, ctx),
            a @ (ConfiguredAttr::Visibility(_) | ConfiguredAttr::WithinView(_)) => {
                // TODO(nga): rule implementations should not need visibility attribute.
                //   But adding it here to preserve existing behavior.
                a.to_value(pkg, ctx.heap())
            }
            ConfiguredAttr::ExplicitConfiguredDep(d) => {
                ExplicitConfiguredDepAttrType::resolve_single(ctx, d.as_ref())
            }
            ConfiguredAttr::SplitTransitionDep(d) => {
                SplitTransitionDepAttrType::resolve_single(ctx, d.as_ref())
            }
            ConfiguredAttr::ConfigurationDep(d) => ConfigurationDepAttrType::resolve_single(ctx, d),
            ConfiguredAttr::Dep(d) => DepAttrType::resolve_single(ctx, d),
            ConfiguredAttr::SourceLabel(s) => SourceAttrType::resolve_single_label(ctx, s),
            ConfiguredAttr::Label(label) => {
                let label = Label::new(*label.clone());
                Ok(ctx.heap().alloc(label))
            }
            ConfiguredAttr::Arg(arg) => arg.resolve(ctx),
            ConfiguredAttr::Query(query) => query.resolve(ctx),
            ConfiguredAttr::SourceFile(s) => Ok(SourceAttrType::resolve_single_file(
                ctx,
                BuckPath::new(pkg.dupe(), s.path().dupe()),
            )),
        }
    }

    /// Returns the starlark type of this attr without resolving
    fn starlark_type(&self) -> anyhow::Result<&'static str> {
        match self {
            ConfiguredAttr::Bool(_) => Ok(starlark::values::bool::BOOL_TYPE),
            ConfiguredAttr::Int(_) => Ok(starlark::values::int::INT_TYPE),
            ConfiguredAttr::String(_) | ConfiguredAttr::EnumVariant(_) => {
                Ok(starlark::values::string::STRING_TYPE)
            }
            ConfiguredAttr::List(_) => Ok(starlark::values::list::ListRef::TYPE),
            ConfiguredAttr::Tuple(_) => Ok(starlark::values::tuple::TupleRef::TYPE),
            ConfiguredAttr::Dict(_) => Ok(Dict::TYPE),
            ConfiguredAttr::None => Ok(NoneType::TYPE),
            ConfiguredAttr::OneOf(box l, _) => l.starlark_type(),
            ConfiguredAttr::Visibility(..) => Ok(ListRef::TYPE),
            ConfiguredAttr::WithinView(..) => Ok(ListRef::TYPE),
            ConfiguredAttr::ExplicitConfiguredDep(_) => {
                Ok(DependencyGen::<FrozenValue>::get_type_value_static().as_str())
            }
            ConfiguredAttr::SplitTransitionDep(_) => Ok(Dict::TYPE),
            ConfiguredAttr::ConfigurationDep(_) => Ok(starlark::values::string::STRING_TYPE),
            ConfiguredAttr::Dep(_) => Ok(Label::get_type_value_static().as_str()),
            ConfiguredAttr::SourceLabel(_) => Ok(Label::get_type_value_static().as_str()),
            ConfiguredAttr::Label(_) => Ok(Label::get_type_value_static().as_str()),
            ConfiguredAttr::Arg(_) => Ok(starlark::values::string::STRING_TYPE),
            ConfiguredAttr::Query(_) => Ok(starlark::values::string::STRING_TYPE),
            ConfiguredAttr::SourceFile(_) => Ok(StarlarkArtifact::get_type_value_static().as_str()),
        }
    }

    /// Converts the configured attr to a starlark value without fully resolving
    fn to_value<'v>(&self, pkg: PackageLabel, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(match &self {
            ConfiguredAttr::Bool(v) => heap.alloc(v.0),
            ConfiguredAttr::Int(v) => heap.alloc(*v),
            ConfiguredAttr::String(s) | ConfiguredAttr::EnumVariant(s) => heap.alloc(s.as_str()),
            ConfiguredAttr::List(list) => {
                heap.alloc(list.try_map(|v| v.to_value(pkg.dupe(), heap))?)
            }
            ConfiguredAttr::Tuple(v) => {
                heap.alloc(AllocTuple(v.try_map(|v| v.to_value(pkg.dupe(), heap))?))
            }
            ConfiguredAttr::Dict(map) => {
                let mut res = SmallMap::with_capacity(map.len());

                for (k, v) in map.iter() {
                    res.insert_hashed(
                        k.to_value(pkg.dupe(), heap)?.get_hashed()?,
                        v.to_value(pkg.dupe(), heap)?,
                    );
                }

                heap.alloc(Dict::new(res))
            }
            ConfiguredAttr::None => Value::new_none(),
            ConfiguredAttr::OneOf(box l, _) => l.to_value(pkg, heap)?,
            ConfiguredAttr::Visibility(VisibilitySpecification(specs))
            | ConfiguredAttr::WithinView(WithinViewSpecification(specs)) => match specs {
                VisibilityPatternList::Public => heap.alloc(AllocList(["PUBLIC"])),
                VisibilityPatternList::List(specs) => {
                    heap.alloc(AllocList(specs.iter().map(|s| s.to_string())))
                }
            },
            ConfiguredAttr::ExplicitConfiguredDep(d) => {
                heap.alloc(Label::new(d.as_ref().label.clone()))
            }
            ConfiguredAttr::SplitTransitionDep(t) => {
                let mut map = SmallMap::with_capacity(t.deps.len());

                for (trans, p) in t.deps.iter() {
                    map.insert_hashed(
                        heap.alloc(trans).get_hashed()?,
                        heap.alloc(Label::new(p.clone())),
                    );
                }

                heap.alloc(Dict::new(map))
            }
            ConfiguredAttr::ConfigurationDep(c) => {
                heap.alloc(StarlarkTargetLabel::new(c.as_ref().dupe()))
            }
            ConfiguredAttr::Dep(d) => heap.alloc(Label::new(d.label.clone())),
            ConfiguredAttr::SourceLabel(s) => heap.alloc(Label::new(*s.clone())),
            ConfiguredAttr::Label(l) => heap.alloc(Label::new(*l.clone())),
            ConfiguredAttr::Arg(arg) => heap.alloc(arg.to_string()),
            ConfiguredAttr::Query(query) => heap.alloc(query.query.query()),
            ConfiguredAttr::SourceFile(f) => heap.alloc(StarlarkArtifact::new(Artifact::from(
                SourceArtifact::new(BuckPath::new(pkg.to_owned(), f.path().dupe())),
            ))),
        })
    }
}


/*
 * 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_build_api::interpreter::rule_defs::cmd_args::value::FrozenCommandLineArg;
use buck2_build_api::interpreter::rule_defs::provider::collection::FrozenProviderCollectionValue;
use buck2_common::result::SharedResult;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use starlark::environment::Module;
use starlark::values::Heap;

pub type AnalysisQueryResult = Vec<(ConfiguredTargetLabel, FrozenProviderCollectionValue)>;

/// The context for attribute resolution. Provides access to the providers from
/// dependents.
pub trait AttrResolutionContext<'v> {
    fn starlark_module(&self) -> &'v Module;

    fn heap(&self) -> &'v Heap {
        self.starlark_module().heap()
    }

    /// Get the `ProviderCollection` for this label. This is converted to a `Dependency`
    /// by the `resolve()` method in `attrs::label`
    fn get_dep(
        &self,
        target: &ConfiguredProvidersLabel,
    ) -> anyhow::Result<FrozenProviderCollectionValue>;

    fn resolve_unkeyed_placeholder(
        &self,
        name: &str,
    ) -> anyhow::Result<Option<FrozenCommandLineArg>>;

    /// Provides the result of the query. This will only provide results for queries that are reported during the configured attr traversal.
    // TODO(cjhopman): Ideally, we wouldn't need to split query attr resolution in this way, but processing queries is an async operation and the starlark Heap cannot be used in async code.
    fn resolve_query(&self, query: &str) -> SharedResult<Arc<AnalysisQueryResult>>;

    fn execution_platform_resolution(&self) -> &ExecutionPlatformResolution;
}


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

pub mod attr_type;
pub mod configured_attr;
pub mod ctx;
pub(crate) mod node_to_attrs_struct;


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

use buck2_node::attrs::inspect_options::AttrInspectOptions;
use buck2_node::nodes::configured::ConfiguredTargetNode;
use starlark::values::structs::AllocStruct;
use starlark::values::Value;

use crate::attrs::resolve::configured_attr::ConfiguredAttrExt;
use crate::attrs::resolve::ctx::AttrResolutionContext;

/// Prepare `ctx.attrs` for rule impl.
pub(crate) fn node_to_attrs_struct<'v>(
    node: &ConfiguredTargetNode,
    ctx: &dyn AttrResolutionContext<'v>,
) -> anyhow::Result<Value<'v>> {
    let attrs_iter = node.attrs(AttrInspectOptions::All);
    let mut resolved_attrs = Vec::with_capacity(attrs_iter.size_hint().0);
    for a in attrs_iter {
        resolved_attrs.push((a.name, a.value.resolve_single(node.label().pkg(), ctx)?));
    }
    Ok(ctx.heap().alloc(AllocStruct(resolved_attrs)))
}


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

use buck2_core::target::label::TargetLabel;
use buck2_node::attrs::attr_type::configuration_dep::ConfigurationDepAttrType;
use starlark::values::Value;

use crate::attrs::resolve::ctx::AttrResolutionContext;

pub(crate) trait ConfigurationDepAttrTypeExt {
    fn resolve_single<'v>(
        ctx: &dyn AttrResolutionContext<'v>,
        label: &TargetLabel,
    ) -> anyhow::Result<Value<'v>> {
        Ok(ctx.heap().alloc(label.to_string()))
    }
}

impl ConfigurationDepAttrTypeExt for ConfigurationDepAttrType {}


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

use buck2_build_api::interpreter::rule_defs::provider::collection::FrozenProviderCollection;
use buck2_build_api::interpreter::rule_defs::provider::collection::FrozenProviderCollectionValue;
use buck2_build_api::interpreter::rule_defs::provider::dependency::Dependency;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_node::attrs::attr_type::configured_dep::ConfiguredExplicitConfiguredDep;
use buck2_node::attrs::attr_type::configured_dep::ExplicitConfiguredDepAttrType;
use buck2_node::attrs::attr_type::dep::DepAttr;
use buck2_node::attrs::attr_type::dep::DepAttrTransition;
use buck2_node::attrs::attr_type::dep::DepAttrType;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use buck2_node::provider_id_set::ProviderIdSet;
use starlark::environment::Module;
use starlark::values::Value;
use thiserror::Error;

use crate::attrs::resolve::ctx::AttrResolutionContext;

#[derive(Error, Debug)]
enum ResolutionError {
    #[error("required provider `{0}` was not found on `{1}`. Found these providers: {}", .2.join(", "))]
    MissingRequiredProvider(String, ConfiguredProvidersLabel, Vec<String>),
}

pub trait DepAttrTypeExt {
    fn check_providers(
        required_providers: &ProviderIdSet,
        providers: &FrozenProviderCollection,
        target: &ConfiguredProvidersLabel,
    ) -> anyhow::Result<()>;

    fn alloc_dependency<'v>(
        env: &'v Module,
        target: &ConfiguredProvidersLabel,
        v: &FrozenProviderCollectionValue,
        execution_platform_resolution: Option<&ExecutionPlatformResolution>,
    ) -> Value<'v>;

    fn resolve_single_impl<'v>(
        ctx: &dyn AttrResolutionContext<'v>,
        target: &ConfiguredProvidersLabel,
        required_providers: &ProviderIdSet,
        is_exec: bool,
    ) -> anyhow::Result<Value<'v>>;

    fn resolve_single<'v>(
        ctx: &dyn AttrResolutionContext<'v>,
        dep_attr: &DepAttr<ConfiguredProvidersLabel>,
    ) -> anyhow::Result<Value<'v>>;
}

impl DepAttrTypeExt for DepAttrType {
    fn check_providers(
        required_providers: &ProviderIdSet,
        providers: &FrozenProviderCollection,
        target: &ConfiguredProvidersLabel,
    ) -> anyhow::Result<()> {
        for provider_id in required_providers {
            if !providers.contains_provider(provider_id) {
                return Err(ResolutionError::MissingRequiredProvider(
                    provider_id.name().to_owned(),
                    target.clone(),
                    providers.provider_names(),
                )
                .into());
            }
        }
        Ok(())
    }

    fn alloc_dependency<'v>(
        env: &'v Module,
        target: &ConfiguredProvidersLabel,
        v: &FrozenProviderCollectionValue,
        execution_platform_resolution: Option<&ExecutionPlatformResolution>,
    ) -> Value<'v> {
        env.heap().alloc(Dependency::new(
            env.heap(),
            target.clone(),
            v.value().owned_value(env.frozen_heap()),
            execution_platform_resolution,
        ))
    }

    fn resolve_single_impl<'v>(
        ctx: &dyn AttrResolutionContext<'v>,
        target: &ConfiguredProvidersLabel,
        required_providers: &ProviderIdSet,
        is_exec_dep: bool,
    ) -> anyhow::Result<Value<'v>> {
        let v = ctx.get_dep(target)?;
        let provider_collection = v.provider_collection();
        Self::check_providers(required_providers, provider_collection, target)?;
        let execution_platform_resolution = if is_exec_dep {
            Some(ctx.execution_platform_resolution())
        } else {
            None
        };

        Ok(Self::alloc_dependency(
            ctx.starlark_module(),
            target,
            &v,
            execution_platform_resolution,
        ))
    }

    fn resolve_single<'v>(
        ctx: &dyn AttrResolutionContext<'v>,
        dep_attr: &DepAttr<ConfiguredProvidersLabel>,
    ) -> anyhow::Result<Value<'v>> {
        let is_exec = dep_attr.attr_type.transition == DepAttrTransition::Exec;
        Self::resolve_single_impl(
            ctx,
            &dep_attr.label,
            &dep_attr.attr_type.required_providers,
            is_exec,
        )
    }
}

pub(crate) trait ExplicitConfiguredDepAttrTypeExt {
    fn resolve_single<'v>(
        ctx: &dyn AttrResolutionContext<'v>,
        dep_attr: &ConfiguredExplicitConfiguredDep,
    ) -> anyhow::Result<Value<'v>> {
        DepAttrType::resolve_single_impl(
            ctx,
            &dep_attr.label,
            &dep_attr.attr_type.required_providers,
            false,
        )
    }
}

impl ExplicitConfiguredDepAttrTypeExt for ExplicitConfiguredDepAttrType {}


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

pub mod arg;
pub(crate) mod configuration_dep;
pub mod dep;
pub(crate) mod query;
pub(crate) mod source;
pub(crate) mod split_transition_dep;


/*
 * 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_common::result::SharedResult;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersName;
use buck2_node::attrs::attr_type::dep::DepAttrType;
use buck2_node::attrs::attr_type::query::QueryAttr;
use buck2_node::attrs::attr_type::query::QueryAttrBase;
use dupe::Dupe;
use starlark::values::Value;

use crate::attrs::resolve::attr_type::dep::DepAttrTypeExt;
use crate::attrs::resolve::ctx::AnalysisQueryResult;
use crate::attrs::resolve::ctx::AttrResolutionContext;

pub(crate) trait ConfiguredQueryAttrBaseExt {
    fn resolve(&self, ctx: &dyn AttrResolutionContext) -> SharedResult<Arc<AnalysisQueryResult>>;
}

impl ConfiguredQueryAttrBaseExt for QueryAttrBase<ConfiguredProvidersLabel> {
    fn resolve(&self, ctx: &dyn AttrResolutionContext) -> SharedResult<Arc<AnalysisQueryResult>> {
        ctx.resolve_query(&self.query)
    }
}

pub(crate) trait ConfiguredQueryAttrExt {
    fn resolve<'v>(&self, ctx: &dyn AttrResolutionContext<'v>) -> anyhow::Result<Value<'v>>;
}

impl ConfiguredQueryAttrExt for QueryAttr<ConfiguredProvidersLabel> {
    fn resolve<'v>(&self, ctx: &dyn AttrResolutionContext<'v>) -> anyhow::Result<Value<'v>> {
        let query_results = self.query.resolve(ctx)?;
        let mut dependencies = Vec::new();

        for (target, providers) in &*query_results {
            let providers_label =
                ConfiguredProvidersLabel::new(target.dupe(), ProvidersName::Default);
            if !self.providers.is_empty() {
                let provider_collection = providers.provider_collection();

                DepAttrType::check_providers(
                    &self.providers,
                    provider_collection,
                    &providers_label,
                )?;
            }
            dependencies.push(DepAttrType::alloc_dependency(
                ctx.starlark_module(),
                &providers_label,
                providers,
                None,
            ));
        }
        Ok(ctx.heap().alloc(dependencies))
    }
}


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

use buck2_artifact::artifact::source_artifact::SourceArtifact;
use buck2_build_api::interpreter::rule_defs::artifact::StarlarkArtifact;
use buck2_core::buck_path::path::BuckPath;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_node::attrs::attr_type::source::SourceAttrType;
use starlark::values::list::ListRef;
use starlark::values::Value;

use crate::attrs::resolve::ctx::AttrResolutionContext;

#[derive(thiserror::Error, Debug)]
enum SourceLabelResolutionError {
    #[error("Expected a single artifact from {0}, but it returned {1} artifacts")]
    ExpectedSingleValue(String, usize),
}

pub(crate) trait SourceAttrTypeExt {
    fn resolve_single_file<'v>(ctx: &dyn AttrResolutionContext<'v>, path: BuckPath) -> Value<'v> {
        ctx.heap()
            .alloc(StarlarkArtifact::new(SourceArtifact::new(path).into()))
    }

    fn resolve_label<'v>(
        ctx: &dyn AttrResolutionContext<'v>,
        label: &ConfiguredProvidersLabel,
    ) -> anyhow::Result<Vec<Value<'v>>> {
        let dep = ctx.get_dep(label)?;
        let default_outputs = dep
            .provider_collection()
            .default_info()
            .default_outputs_raw();
        let res = ListRef::from_frozen_value(default_outputs)
            .unwrap()
            .iter()
            .collect();
        Ok(res)
    }

    fn resolve_single_label<'v>(
        ctx: &dyn AttrResolutionContext<'v>,
        value: &ConfiguredProvidersLabel,
    ) -> anyhow::Result<Value<'v>> {
        let mut resolved = Self::resolve_label(ctx, value)?;
        if resolved.len() == 1 {
            Ok(resolved.pop().unwrap())
        } else {
            Err(
                SourceLabelResolutionError::ExpectedSingleValue(value.to_string(), resolved.len())
                    .into(),
            )
        }
    }
}

impl SourceAttrTypeExt for SourceAttrType {}


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

use buck2_node::attrs::attr_type::dep::DepAttrType;
use buck2_node::attrs::attr_type::split_transition_dep::ConfiguredSplitTransitionDep;
use buck2_node::attrs::attr_type::split_transition_dep::SplitTransitionDepAttrType;
use starlark::values::dict::Dict;
use starlark::values::Value;
use starlark_map::small_map::SmallMap;
use starlark_map::Hashed;

use crate::attrs::resolve::attr_type::dep::DepAttrTypeExt;
use crate::attrs::resolve::ctx::AttrResolutionContext;

pub(crate) trait SplitTransitionDepAttrTypeExt {
    fn resolve_single<'v>(
        ctx: &dyn AttrResolutionContext<'v>,
        deps: &ConfiguredSplitTransitionDep,
    ) -> anyhow::Result<Value<'v>>;
}

impl SplitTransitionDepAttrTypeExt for SplitTransitionDepAttrType {
    fn resolve_single<'v>(
        ctx: &dyn AttrResolutionContext<'v>,
        deps: &ConfiguredSplitTransitionDep,
    ) -> anyhow::Result<Value<'v>> {
        let mut res = SmallMap::with_capacity(deps.deps.len());
        for (label, target) in &deps.deps {
            let label_hashed = ctx.heap().alloc_str(label).get_hashed();
            res.insert_hashed(
                Hashed::new_unchecked(label_hashed.hash(), label_hashed.key().to_value()),
                DepAttrType::resolve_single_impl(ctx, target, &deps.required_providers, false)?,
            );
        }
        Ok(ctx.heap().alloc(Dict::new(res)))
    }
}


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

use anyhow::Context;
use buck2_build_api::interpreter::rule_defs::cmd_args::value::FrozenCommandLineArg;
use buck2_build_api::interpreter::rule_defs::provider::builtin::run_info::RunInfoCallable;
use buck2_build_api::interpreter::rule_defs::provider::builtin::template_placeholder_info::FrozenTemplatePlaceholderInfo;
use buck2_build_api::interpreter::rule_defs::resolved_macro::ResolvedMacro;
use buck2_build_api::interpreter::rule_defs::resolved_macro::ResolvedStringWithMacros;
use buck2_build_api::interpreter::rule_defs::resolved_macro::ResolvedStringWithMacrosPart;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_node::attrs::attr_type::arg::ConfiguredMacro;
use buck2_node::attrs::attr_type::arg::ConfiguredStringWithMacros;
use buck2_node::attrs::attr_type::arg::ConfiguredStringWithMacrosPart;
use buck2_node::attrs::attr_type::arg::StringWithMacros;
use buck2_node::attrs::attr_type::arg::UnrecognizedMacro;
use dupe::Dupe;
use either::Either;
use starlark::values::Value;

use crate::attrs::resolve::attr_type::arg::query::ConfiguredQueryMacroBaseExt;
use crate::attrs::resolve::ctx::AttrResolutionContext;

pub mod query;

#[derive(Debug, thiserror::Error)]
enum ResolveMacroError {
    #[error(
        "The mapping for {0} in the TemplatePlaceholderInfo for {1} was not a dictionary (required because requested arg `{2}`)."
    )]
    KeyedPlaceholderMappingNotADict(String, ConfiguredProvidersLabel, String),
    #[error(
        "The mapping for {0} in the TemplatePlaceholderInfo for {1} had no mapping for arg `{2}`."
    )]
    KeyedPlaceholderArgMissing(String, ConfiguredProvidersLabel, String),
    #[error("There was no mapping for {0} in the TemplatePlaceholderInfo for {1}.")]
    KeyedPlaceholderMappingMissing(String, ConfiguredProvidersLabel),
    #[error("There was no TemplatePlaceholderInfo for {0}.")]
    KeyedPlaceholderInfoMissing(ConfiguredProvidersLabel),
    #[error("There was no mapping for {0}.")]
    UnkeyedPlaceholderUnresolved(String),
    #[error("Expected a RunInfo provider from target `{0}`.")]
    ExpectedRunInfo(String),
    #[error("Can't expand unrecognized macros (`{0}`).")]
    UnrecognizedMacroUnimplemented(String),
}

pub trait ConfiguredStringWithMacrosExt {
    fn resolve<'v>(&self, ctx: &dyn AttrResolutionContext<'v>) -> anyhow::Result<Value<'v>>;
}

impl ConfiguredStringWithMacrosExt for ConfiguredStringWithMacros {
    fn resolve<'v>(&self, ctx: &dyn AttrResolutionContext<'v>) -> anyhow::Result<Value<'v>> {
        let resolved_parts = match &self.string_with_macros {
            StringWithMacros::StringPart(s) => {
                vec![ResolvedStringWithMacrosPart::String(s.dupe())]
            }
            StringWithMacros::ManyParts(ref parts) => {
                let mut resolved_parts = Vec::with_capacity(parts.len());
                for part in parts.iter() {
                    match part {
                        ConfiguredStringWithMacrosPart::String(s) => {
                            resolved_parts.push(ResolvedStringWithMacrosPart::String(s.dupe()));
                        }
                        ConfiguredStringWithMacrosPart::Macro(write_to_file, m) => {
                            resolved_parts.push(ResolvedStringWithMacrosPart::Macro(
                                *write_to_file,
                                resolve_configured_macro(m, ctx)
                                    .with_context(|| format!("Error resolving `{}`.", part))?,
                            ));
                        }
                    }
                }
                resolved_parts
            }
        };

        let configured_macros = if self.anon_target_compatible {
            Some(self)
        } else {
            None
        };

        Ok(ctx.heap().alloc(ResolvedStringWithMacros::new(
            resolved_parts,
            configured_macros,
        )))
    }
}

fn resolve_configured_macro(
    configured_macro: &ConfiguredMacro,
    ctx: &dyn AttrResolutionContext,
) -> anyhow::Result<ResolvedMacro> {
    match configured_macro {
        ConfiguredMacro::Location(target) => {
            let providers_value = ctx.get_dep(target)?;
            let providers = providers_value.provider_collection();
            Ok(ResolvedMacro::Location(providers.default_info()))
        }
        ConfiguredMacro::Exe { label, .. } => {
            // Don't need to consider exec_dep as it already was applied when configuring the label.
            let providers_value = ctx.get_dep(label)?;
            let providers = providers_value.provider_collection();
            let run_info = match providers.get_provider_raw(RunInfoCallable::provider_id()) {
                Some(value) => *value,
                None => {
                    return Err(ResolveMacroError::ExpectedRunInfo(label.to_string()).into());
                }
            };
            // A RunInfo is an arg-like value.
            Ok(ResolvedMacro::ArgLike(FrozenCommandLineArg::new(run_info)?))
        }
        ConfiguredMacro::UserUnkeyedPlaceholder(name) => {
            let provider = ctx.resolve_unkeyed_placeholder(name)?.ok_or_else(|| {
                ResolveMacroError::UnkeyedPlaceholderUnresolved((**name).to_owned())
            })?;
            Ok(ResolvedMacro::ArgLike(provider))
        }
        ConfiguredMacro::UserKeyedPlaceholder(box (name, label, arg)) => {
            let providers = ctx.get_dep(label)?;
            let placeholder_info =
                FrozenTemplatePlaceholderInfo::from_providers(providers.provider_collection())
                    .ok_or_else(|| ResolveMacroError::KeyedPlaceholderInfoMissing(label.clone()))?;
            let keyed_variables = placeholder_info.keyed_variables();
            let either_cmd_or_mapping = keyed_variables.get(&**name).ok_or_else(|| {
                ResolveMacroError::KeyedPlaceholderMappingMissing(
                    (**name).to_owned(),
                    label.to_owned(),
                )
            })?;

            let value: FrozenCommandLineArg = match (arg, either_cmd_or_mapping) {
                (None, Either::Left(mapping)) => *mapping,
                (Some(arg), Either::Left(_)) => {
                    return Err(ResolveMacroError::KeyedPlaceholderMappingNotADict(
                        (**name).to_owned(),
                        label.clone(),
                        (**arg).to_owned(),
                    )
                    .into());
                }
                (arg, Either::Right(mapping)) => {
                    let arg = arg.as_deref().unwrap_or("DEFAULT");
                    mapping.get(arg).copied().ok_or_else(|| {
                        ResolveMacroError::KeyedPlaceholderArgMissing(
                            (**name).to_owned(),
                            label.clone(),
                            arg.to_owned(),
                        )
                    })?
                }
            };

            Ok(ResolvedMacro::ArgLike(value))
        }
        ConfiguredMacro::Query(query) => Ok(ResolvedMacro::Query(query.resolve(ctx)?)),
        ConfiguredMacro::UnrecognizedMacro(box UnrecognizedMacro {
            macro_type,
            args: _,
        }) => Err(anyhow::anyhow!(
            ResolveMacroError::UnrecognizedMacroUnimplemented((**macro_type).to_owned())
        )),
    }
}


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

use std::sync::Arc;

use buck2_build_api::interpreter::rule_defs::resolve_query_macro::ResolvedQueryMacro;
use buck2_build_api::interpreter::rule_defs::resolve_query_macro::ResolvedQueryMacroTargetAndOutputs;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_node::attrs::attr_type::arg::QueryExpansion;
use buck2_node::attrs::attr_type::query::QueryMacroBase;
use dupe::Dupe;

use crate::attrs::resolve::attr_type::query::ConfiguredQueryAttrBaseExt;
use crate::attrs::resolve::ctx::AnalysisQueryResult;
use crate::attrs::resolve::ctx::AttrResolutionContext;

pub(crate) trait ConfiguredQueryMacroBaseExt {
    fn resolve(&self, ctx: &dyn AttrResolutionContext) -> anyhow::Result<ResolvedQueryMacro>;
}

impl ConfiguredQueryMacroBaseExt for QueryMacroBase<ConfiguredProvidersLabel> {
    fn resolve(&self, ctx: &dyn AttrResolutionContext) -> anyhow::Result<ResolvedQueryMacro> {
        let query_result: Arc<AnalysisQueryResult> = self.query.resolve(ctx)?;

        match &self.expansion_type {
            QueryExpansion::Output => Ok(ResolvedQueryMacro::Outputs(
                query_result
                    .iter()
                    .map(|(_, providers)| {
                        providers
                            .provider_collection()
                            .default_info()
                            .default_outputs()
                            .into_boxed_slice()
                    })
                    .collect(),
            )),
            QueryExpansion::Target => Ok(ResolvedQueryMacro::Targets(
                query_result
                    .iter()
                    .map(|(target, _)| target.dupe())
                    .collect(),
            )),
            QueryExpansion::TargetAndOutput(separator) => {
                Ok(ResolvedQueryMacro::TargetsAndOutputs(Box::new(
                    ResolvedQueryMacroTargetAndOutputs {
                        sep: match separator {
                            Some(separator) => separator.to_owned().into_boxed_str(),
                            None => " ".to_owned().into_boxed_str(),
                        },
                        list: query_result
                            .iter()
                            .map(|(target, providers)| {
                                (
                                    target.dupe(),
                                    providers
                                        .provider_collection()
                                        .default_info()
                                        .default_outputs()
                                        .into_boxed_slice(),
                                )
                            })
                            .collect(),
                    },
                )))
            }
        }
    }
}


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

use allocative::Allocative;
use async_trait::async_trait;
use buck2_build_api::analysis::anon_promises_dyn::AnonPromisesDyn;
use buck2_interpreter::dice::starlark_provider::with_starlark_eval_provider;
use buck2_interpreter::starlark_profiler::StarlarkProfilerOrInstrumentation;
use buck2_interpreter::starlark_promise::StarlarkPromise;
use dice::DiceComputations;
use either::Either;
use futures::future;
use starlark::eval::Evaluator;
use starlark::values::list::AllocList;
use starlark::values::Trace;
use starlark::values::ValueTyped;

use crate::anon_targets::AnonTargetKey;

#[derive(Default, Debug, Trace, Allocative)]
pub(crate) struct AnonPromises<'v> {
    // The actual data
    entries: Vec<(
        ValueTyped<'v, StarlarkPromise<'v>>,
        // Either a single entry, or a list that becomes a list of providers
        Either<AnonTargetKey, Vec<AnonTargetKey>>,
    )>,
}

impl<'v> AnonPromises<'v> {
    pub(crate) fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    pub(crate) fn push_one(
        &mut self,
        promise: ValueTyped<'v, StarlarkPromise<'v>>,
        one: AnonTargetKey,
    ) {
        self.entries.push((promise, Either::Left(one)));
    }

    pub(crate) fn push_list(
        &mut self,
        promise: ValueTyped<'v, StarlarkPromise<'v>>,
        many: Vec<AnonTargetKey>,
    ) {
        self.entries.push((promise, Either::Right(many)));
    }
}

#[async_trait(?Send)]
impl<'v> AnonPromisesDyn<'v> for AnonPromises<'v> {
    async fn run_promises(
        self: Box<Self>,
        dice: &DiceComputations,
        eval: &mut Evaluator<'v, '_>,
        description: String,
    ) -> anyhow::Result<()> {
        // Resolve all the targets in parallel
        // We have vectors of vectors, so we create a "shape" which has the same shape but with indices
        let mut shape = Vec::new();
        let mut targets = Vec::new();
        for (promise, xs) in self.entries {
            match xs {
                Either::Left(x) => {
                    shape.push((promise, Either::Left(shape.len())));
                    targets.push(x);
                }
                Either::Right(xs) => {
                    shape.push((promise, Either::Right(shape.len()..shape.len() + xs.len())));
                    targets.extend(xs);
                }
            }
        }

        let values =
            future::try_join_all(targets.iter().map(|target| target.resolve(dice))).await?;
        with_starlark_eval_provider(
            dice,
            &mut StarlarkProfilerOrInstrumentation::disabled(),
            description,
            |_provider| {
                // But must bind the promises sequentially
                for (promise, xs) in shape {
                    match xs {
                        Either::Left(i) => {
                            let val = values[i]
                                .provider_collection
                                .value()
                                .owned_value(eval.frozen_heap());
                            promise.resolve(val, eval)?
                        }
                        Either::Right(is) => {
                            let xs: Vec<_> = is
                                .map(|i| {
                                    values[i]
                                        .provider_collection
                                        .value()
                                        .owned_value(eval.frozen_heap())
                                })
                                .collect();
                            let list = eval.heap().alloc(AllocList(xs));
                            promise.resolve(list, eval)?
                        }
                    }
                }
                Ok(())
            },
        )
        .await
    }
}


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

use std::collections::HashMap;
use std::fmt::Debug;
use std::mem;
use std::sync::Arc;

use allocative::Allocative;
use anyhow::Context as _;
use async_trait::async_trait;
use buck2_analysis::analysis::calculation::get_rule_impl;
use buck2_analysis::analysis::env::RuleAnalysisAttrResolutionContext;
use buck2_analysis::analysis::env::RuleImplFunction;
use buck2_build_api::analysis::anon_promises_dyn::AnonPromisesDyn;
use buck2_build_api::analysis::anon_targets_registry::AnonTargetsRegistryDyn;
use buck2_build_api::analysis::anon_targets_registry::ANON_TARGET_REGISTRY_NEW;
use buck2_build_api::analysis::calculation::RuleAnalysisCalculation;
use buck2_build_api::analysis::registry::AnalysisRegistry;
use buck2_build_api::analysis::AnalysisResult;
use buck2_build_api::deferred::calculation::EVAL_ANON_TARGET;
use buck2_build_api::deferred::types::DeferredTable;
use buck2_build_api::interpreter::rule_defs::context::AnalysisContext;
use buck2_build_api::interpreter::rule_defs::provider::collection::FrozenProviderCollectionValue;
use buck2_build_api::interpreter::rule_defs::provider::collection::ProviderCollection;
use buck2_build_api::keep_going;
use buck2_common::result::SharedResult;
use buck2_configured::nodes::calculation::find_execution_platform_by_configuration;
use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_core::base_deferred_key::BaseDeferredKeyDyn;
use buck2_core::cells::name::CellName;
use buck2_core::cells::paths::CellRelativePath;
use buck2_core::configuration::config_setting::ConfigSettingData;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::configuration::pair::ConfigurationNoExec;
use buck2_core::configuration::pair::ConfigurationWithExec;
use buck2_core::configuration::transition::applied::TransitionApplied;
use buck2_core::configuration::transition::id::TransitionId;
use buck2_core::package::PackageLabel;
use buck2_core::pattern::lex_target_pattern;
use buck2_core::pattern::pattern_type::TargetPatternExtra;
use buck2_core::pattern::ParsedPattern;
use buck2_core::pattern::PatternData;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::provider::label::ProvidersName;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_core::target::label::TargetLabel;
use buck2_core::target::name::TargetNameRef;
use buck2_core::unsafe_send_future::UnsafeSendFuture;
use buck2_events::dispatch::get_dispatcher;
use buck2_events::dispatch::span_async;
use buck2_execute::digest_config::HasDigestConfig;
use buck2_interpreter::dice::starlark_provider::with_starlark_eval_provider;
use buck2_interpreter::print_handler::EventDispatcherPrintHandler;
use buck2_interpreter::starlark_profiler::StarlarkProfilerOrInstrumentation;
use buck2_interpreter::starlark_promise::StarlarkPromise;
use buck2_interpreter::types::label::Label;
use buck2_interpreter_for_build::rule::FrozenRuleCallable;
use buck2_node::attrs::attr_type::query::ResolvedQueryLiterals;
use buck2_node::attrs::attr_type::AttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::coerced_path::CoercedPath;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use buck2_node::attrs::configuration_context::AttrConfigurationContext;
use buck2_node::attrs::configured_traversal::ConfiguredAttrTraversal;
use buck2_node::attrs::internal::internal_attrs;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use buck2_util::arc_str::ArcSlice;
use buck2_util::arc_str::ArcStr;
use buck2_util::collections::ordered_map::OrderedMap;
use derive_more::Display;
use dice::DiceComputations;
use dice::Key;
use dupe::Dupe;
use futures::stream::FuturesUnordered;
use futures::Future;
use futures::FutureExt;
use gazebo::prelude::*;
use more_futures::cancellation::CancellationContext;
use starlark::any::AnyLifetime;
use starlark::any::ProvidesStaticType;
use starlark::environment::Module;
use starlark::values::dict::DictOf;
use starlark::values::structs::AllocStruct;
use starlark::values::Trace;
use starlark::values::Value;
use starlark::values::ValueTyped;
use thiserror::Error;

use crate::anon_promises::AnonPromises;
use crate::anon_target_attr::AnonTargetAttr;
use crate::anon_target_attr_coerce::AnonTargetAttrTypeCoerce;
use crate::anon_target_attr_resolve::AnonTargetAttrExt;
use crate::anon_target_node::AnonTarget;

#[derive(Debug, Trace, Allocative, ProvidesStaticType)]
pub struct AnonTargetsRegistry<'v> {
    // We inherit the execution platform of our parent
    execution_platform: ExecutionPlatformResolution,
    promises: AnonPromises<'v>,
}

#[derive(Debug, Error)]
pub enum AnonTargetsError {
    #[error("Not allowed to call `anon_targets` in this context")]
    AssertNoPromisesFailed,
    #[error(
        "Invalid `name` attribute, must be a label or a string, got `{value}` of type `{typ}`"
    )]
    InvalidNameType { typ: String, value: String },
    #[error("`name` attribute must be a valid target label, got `{0}`")]
    NotTargetLabel(String),
    #[error("can't parse strings during `anon_targets` coercion, got `{0}`")]
    CantParseDuringCoerce(String),
    #[error("Unknown attribute `{0}`")]
    UnknownAttribute(String),
    #[error("Internal attribute `{0}` not allowed as argument to `anon_targets`")]
    InternalAttribute(String),
    #[error("Missing attribute `{0}`")]
    MissingAttribute(String),
    #[error("Query macros are not supported")]
    QueryMacroNotSupported,
}

#[derive(Hash, Eq, PartialEq, Clone, Dupe, Debug, Display, Trace, Allocative)]
pub(crate) struct AnonTargetKey(Arc<AnonTarget>);

impl AnonTargetKey {
    fn downcast(key: Arc<dyn BaseDeferredKeyDyn>) -> anyhow::Result<Self> {
        Ok(AnonTargetKey(
            key.into_any()
                .downcast()
                .ok()
                .context("Expecting AnonTarget (internal error)")?,
        ))
    }

    pub(crate) fn new<'v>(
        execution_platform: &ExecutionPlatformResolution,
        rule: ValueTyped<'v, FrozenRuleCallable>,
        attributes: DictOf<'v, &'v str, Value<'v>>,
    ) -> anyhow::Result<Self> {
        let mut name = None;
        let internal_attrs = internal_attrs();

        let entries = attributes.collect_entries();
        let attrs_spec = rule.attributes();
        let mut attrs = OrderedMap::with_capacity(attrs_spec.len());

        let anon_attr_ctx = AnonAttrCtx::new(execution_platform);

        for (k, v) in entries {
            if k == "name" {
                name = Some(Self::coerce_name(v)?);
            } else if internal_attrs.contains_key(k) {
                return Err(AnonTargetsError::InternalAttribute(k.to_owned()).into());
            } else {
                let attr = attrs_spec
                    .attribute(k)
                    .ok_or_else(|| AnonTargetsError::UnknownAttribute(k.to_owned()))?;
                attrs.insert(
                    k.to_owned(),
                    Self::coerce_to_anon_target_attr(attr.coercer(), v, &anon_attr_ctx)
                        .with_context(|| format!("Error coercing attribute `{}`", k))?,
                );
            }
        }
        for (k, _, a) in attrs_spec.attr_specs() {
            if !attrs.contains_key(k) && !internal_attrs.contains_key(k) {
                if let Some(x) = a.default() {
                    attrs.insert(
                        k.to_owned(),
                        Self::coerced_to_anon_target_attr(x, a.coercer(), &anon_attr_ctx)?,
                    );
                } else {
                    return Err(AnonTargetsError::MissingAttribute(k.to_owned()).into());
                }
            }
        }

        // We need to ensure there is a "name" attribute which corresponds to something we can turn in to a label.
        // If there isn't a good one, make something up
        let name = match name {
            None => Self::create_name(&rule.rule_type().name)?,
            Some(name) => name,
        };

        Ok(Self(Arc::new(AnonTarget::new(
            rule.rule_type().dupe(),
            name,
            attrs.into(),
            execution_platform.cfg().dupe(),
        ))))
    }

    /// We need to parse a TargetLabel from a String, but it doesn't matter if the pieces aren't
    /// valid targets in the context of this build (e.g. if the package really exists),
    /// just that it is syntactically valid.
    fn parse_target_label(x: &str) -> anyhow::Result<TargetLabel> {
        let err = || AnonTargetsError::NotTargetLabel(x.to_owned());
        let lex = lex_target_pattern::<TargetPatternExtra>(x, false).with_context(err)?;
        // TODO(nga): `CellName` contract requires it refers to declared cell name.
        //   This `unchecked_new` violates it.
        let cell =
            CellName::unchecked_new(lex.cell_alias.filter(|a| !a.is_empty()).unwrap_or("anon"))?;
        match lex.pattern.reject_ambiguity()? {
            PatternData::TargetInPackage {
                package,
                target_name,
                extra: TargetPatternExtra,
            } => Ok(TargetLabel::new(
                PackageLabel::new(cell, CellRelativePath::new(package)),
                target_name.as_ref(),
            )),
            _ => Err(err().into()),
        }
    }

    fn create_name(rule_name: &str) -> anyhow::Result<TargetLabel> {
        // TODO(nga): this creates non-existing cell reference.
        let cell_name = CellName::unchecked_new("anon")?;
        let pkg = PackageLabel::new(cell_name, CellRelativePath::empty());
        Ok(TargetLabel::new(pkg, TargetNameRef::new(rule_name)?))
    }

    fn coerce_name(x: Value) -> anyhow::Result<TargetLabel> {
        if let Some(x) = Label::from_value(x) {
            Ok(x.label().target().unconfigured().dupe())
        } else if let Some(x) = x.unpack_str() {
            Self::parse_target_label(x)
        } else {
            Err(AnonTargetsError::InvalidNameType {
                typ: x.get_type().to_owned(),
                value: x.to_string(),
            }
            .into())
        }
    }

    fn coerce_to_anon_target_attr(
        attr: &AttrType,
        x: Value,
        ctx: &AnonAttrCtx,
    ) -> anyhow::Result<AnonTargetAttr> {
        attr.coerce_item(AttrIsConfigurable::No, &ctx, x)
    }

    fn coerced_to_anon_target_attr(
        x: &CoercedAttr,
        ty: &AttrType,
        ctx: &AnonAttrCtx,
    ) -> anyhow::Result<AnonTargetAttr> {
        AnonTargetAttr::from_coerced_attr(x, ty, ctx)
    }

    pub(crate) async fn resolve(&self, dice: &DiceComputations) -> anyhow::Result<AnalysisResult> {
        #[async_trait]
        impl Key for AnonTargetKey {
            type Value = SharedResult<AnalysisResult>;

            async fn compute(
                &self,
                ctx: &DiceComputations,
                _cancellation: &CancellationContext,
            ) -> Self::Value {
                Ok(self.run_analysis(ctx).await?)
            }

            fn equality(_: &Self::Value, _: &Self::Value) -> bool {
                false
            }
        }

        Ok(dice.compute(self).await??)
    }

    fn run_analysis<'a>(
        &'a self,
        dice: &'a DiceComputations,
    ) -> impl Future<Output = anyhow::Result<AnalysisResult>> + Send + 'a {
        let fut = async move { self.run_analysis_impl(dice).await };
        unsafe { UnsafeSendFuture::new_encapsulates_starlark(fut) }
    }

    fn deps(&self) -> anyhow::Result<Vec<ConfiguredTargetLabel>> {
        struct Traversal(Vec<ConfiguredTargetLabel>);

        impl ConfiguredAttrTraversal for Traversal {
            fn dep(&mut self, dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
                self.0.push(dep.target().dupe());
                Ok(())
            }

            fn query_macro(
                &mut self,
                _query: &str,
                _resolved_literals: &ResolvedQueryLiterals<ConfiguredProvidersLabel>,
            ) -> anyhow::Result<()> {
                Err(AnonTargetsError::QueryMacroNotSupported.into())
            }
        }

        let mut traversal = Traversal(Vec::new());
        for x in self.0.attrs().values() {
            x.traverse(self.0.name().pkg(), &mut traversal)?;
        }
        Ok(traversal.0)
    }
    async fn run_analysis_impl(&self, dice: &DiceComputations) -> anyhow::Result<AnalysisResult> {
        let deps = self.deps()?;
        let dep_analysis_results: HashMap<_, _> = keep_going::try_join_all(
            dice,
            deps.iter()
                .map(async move |dep| {
                    let res = dice
                        .get_analysis_result(dep)
                        .await
                        .and_then(|v| v.require_compatible());
                    res.map(|x| (dep, x.providers().dupe()))
                })
                .collect::<FuturesUnordered<_>>(),
        )
        .await?;

        let exec_resolution = ExecutionPlatformResolution::new(
            Some(
                find_execution_platform_by_configuration(
                    dice,
                    self.0.exec_cfg().cfg(),
                    self.0.exec_cfg().cfg(),
                )
                .await?,
            ),
            Vec::new(),
        );

        let rule_impl = get_rule_impl(dice, self.0.rule_type()).await?;
        let env = Module::new();
        let print = EventDispatcherPrintHandler(get_dispatcher());

        span_async(
            buck2_data::AnalysisStart {
                target: Some(self.0.as_proto().into()),
                rule: self.0.rule_type().to_string(),
            },
            async move {
                let (mut eval, ctx, list_res) = with_starlark_eval_provider(
                    dice,
                    &mut StarlarkProfilerOrInstrumentation::disabled(),
                    format!("anon_analysis:{}", self),
                    |provider| {
                        let mut eval = provider.make(&env)?;
                        eval.set_print_handler(&print);

                        // No attributes are allowed to contain macros or other stuff, so an empty resolution context works
                        let resolution_ctx = RuleAnalysisAttrResolutionContext {
                            module: &env,
                            dep_analysis_results,
                            query_results: HashMap::new(),
                            execution_platform_resolution: exec_resolution.clone(),
                        };

                        let mut resolved_attrs = Vec::with_capacity(self.0.attrs().len());
                        for (name, attr) in self.0.attrs().iter() {
                            resolved_attrs.push((
                                name,
                                attr.resolve_single(self.0.name().pkg(), &resolution_ctx)?,
                            ));
                        }
                        let attributes = env.heap().alloc(AllocStruct(resolved_attrs));

                        let registry = AnalysisRegistry::new_from_owner(
                            BaseDeferredKey::AnonTarget(self.0.dupe()),
                            exec_resolution,
                        )?;

                        let ctx = env.heap().alloc_typed(AnalysisContext::new(
                            eval.heap(),
                            attributes,
                            Some(eval.heap().alloc_typed(Label::new(
                                ConfiguredProvidersLabel::new(
                                    self.0.configured_label(),
                                    ProvidersName::Default,
                                ),
                            ))),
                            registry,
                            dice.global_data().get_digest_config(),
                        ));

                        let list_res = rule_impl.invoke(&mut eval, ctx)?;
                        Ok((eval, ctx, list_res))
                    },
                )
                .await?;

                ctx.actions
                    .run_promises(dice, &mut eval, format!("anon_analysis$promises:{}", self))
                    .await?;
                let res_typed = ProviderCollection::try_from_value(list_res)?;
                let res = env.heap().alloc(res_typed);
                env.set("", res);

                // Pull the ctx object back out, and steal ctx.action's state back
                let analysis_registry = ctx.take_state();
                std::mem::drop(eval);

                let (frozen_env, deferreds) = analysis_registry.finalize(&env)?(env)?;

                let res = frozen_env.get("").unwrap();
                let provider_collection = FrozenProviderCollectionValue::try_from_value(res)
                    .expect("just created this, this shouldn't happen");

                // this could look nicer if we had the entire analysis be a deferred
                let deferred = DeferredTable::new(deferreds.take_result()?);
                Ok(AnalysisResult::new(provider_collection, deferred, None))
            }
            .map(|res| {
                (
                    res,
                    buck2_data::AnalysisEnd {
                        target: Some(self.0.as_proto().into()),
                        rule: self.0.rule_type().to_string(),
                        profile: None, // Not implemented for anon targets
                    },
                )
            }),
        )
        .await
    }
}

/// Several attribute functions need a context, make one that is mostly useless.
pub struct AnonAttrCtx {
    cfg: ConfigurationData,
    transitions: OrderedMap<Arc<TransitionId>, Arc<TransitionApplied>>,
    pub execution_platform_resolution: ExecutionPlatformResolution,
}

impl AnonAttrCtx {
    fn new(execution_platform_resolution: &ExecutionPlatformResolution) -> Self {
        Self {
            cfg: ConfigurationData::unspecified(),
            transitions: OrderedMap::new(),
            execution_platform_resolution: execution_platform_resolution.clone(),
        }
    }
}

impl AttrCoercionContext for AnonAttrCtx {
    fn coerce_label(&self, value: &str) -> anyhow::Result<ProvidersLabel> {
        Err(AnonTargetsError::CantParseDuringCoerce(value.to_owned()).into())
    }

    fn intern_str(&self, value: &str) -> ArcStr {
        // TODO(scottcao): do intern.
        ArcStr::from(value)
    }

    fn intern_list(&self, value: Vec<CoercedAttr>) -> ArcSlice<CoercedAttr> {
        // TODO(scottcao): do intern.
        value.into()
    }

    fn intern_dict(
        &self,
        value: Vec<(CoercedAttr, CoercedAttr)>,
    ) -> ArcSlice<(CoercedAttr, CoercedAttr)> {
        // TODO(scottcao): do intern.
        value.into()
    }

    fn intern_select(
        &self,
        value: Vec<(TargetLabel, CoercedAttr)>,
    ) -> ArcSlice<(TargetLabel, CoercedAttr)> {
        // TODO(scottcao): do intern.
        value.into()
    }

    fn coerce_path(&self, value: &str, _allow_directory: bool) -> anyhow::Result<CoercedPath> {
        Err(AnonTargetsError::CantParseDuringCoerce(value.to_owned()).into())
    }

    fn coerce_target_pattern(
        &self,
        pattern: &str,
    ) -> anyhow::Result<ParsedPattern<TargetPatternExtra>> {
        Err(AnonTargetsError::CantParseDuringCoerce(pattern.to_owned()).into())
    }

    fn visit_query_function_literals(
        &self,
        _visitor: &mut dyn buck2_query::query::syntax::simple::functions::QueryLiteralVisitor,
        _expr: &buck2_query_parser::spanned::Spanned<buck2_query_parser::Expr>,
        query: &str,
    ) -> anyhow::Result<()> {
        Err(AnonTargetsError::CantParseDuringCoerce(query.to_owned()).into())
    }
}

impl AttrConfigurationContext for AnonAttrCtx {
    fn matches<'a>(&'a self, _label: &TargetLabel) -> Option<&'a ConfigSettingData> {
        None
    }

    fn cfg(&self) -> ConfigurationNoExec {
        ConfigurationNoExec::new(self.cfg.dupe())
    }

    fn exec_cfg(&self) -> ConfigurationNoExec {
        ConfigurationNoExec::new(self.cfg.dupe())
    }

    fn toolchain_cfg(&self) -> ConfigurationWithExec {
        ConfigurationWithExec::new(self.cfg.dupe(), self.cfg.dupe())
    }

    fn platform_cfg(&self, _label: &TargetLabel) -> anyhow::Result<ConfigurationData> {
        Ok(self.cfg.dupe())
    }

    fn resolved_transitions(&self) -> &OrderedMap<Arc<TransitionId>, Arc<TransitionApplied>> {
        &self.transitions
    }
}

pub(crate) fn init_eval_anon_target() {
    EVAL_ANON_TARGET
        .init(|ctx, key| Box::pin(async move { AnonTargetKey::downcast(key)?.resolve(ctx).await }));
}

pub(crate) fn init_anon_target_registry_new() {
    ANON_TARGET_REGISTRY_NEW.init(|_phantom, execution_platform| {
        Box::new(AnonTargetsRegistry {
            execution_platform,
            promises: AnonPromises::default(),
        })
    });
}

impl<'v> AnonTargetsRegistry<'v> {
    pub(crate) fn downcast_mut(
        registry: &mut dyn AnonTargetsRegistryDyn<'v>,
    ) -> anyhow::Result<&'v mut AnonTargetsRegistry<'v>> {
        let registry: &mut AnonTargetsRegistry = registry
            .as_any_mut()
            .downcast_mut::<AnonTargetsRegistry>()
            .context("AnonTargetsRegistryDyn is not an AnonTargetsRegistry (internal error)")?;
        unsafe {
            // It is hard or impossible to express this safely with the borrow checker.
            // Has something to do with 'v being invariant.
            Ok(mem::transmute::<
                &mut AnonTargetsRegistry,
                &mut AnonTargetsRegistry,
            >(registry))
        }
    }

    pub(crate) fn register_one(
        &mut self,
        promise: ValueTyped<'v, StarlarkPromise<'v>>,
        rule: ValueTyped<'v, FrozenRuleCallable>,
        attributes: DictOf<'v, &'v str, Value<'v>>,
    ) -> anyhow::Result<()> {
        self.promises.push_one(
            promise,
            AnonTargetKey::new(&self.execution_platform, rule, attributes)?,
        );
        Ok(())
    }

    pub(crate) fn register_many(
        &mut self,
        promise: ValueTyped<'v, StarlarkPromise<'v>>,
        rules: Vec<(
            ValueTyped<'v, FrozenRuleCallable>,
            DictOf<'v, &'v str, Value<'v>>,
        )>,
    ) -> anyhow::Result<()> {
        let keys = rules.into_try_map(|(rule, attributes)| {
            AnonTargetKey::new(&self.execution_platform, rule, attributes)
        })?;
        self.promises.push_list(promise, keys);
        Ok(())
    }
}

impl<'v> AnonTargetsRegistryDyn<'v> for AnonTargetsRegistry<'v> {
    fn as_any_mut(&mut self) -> &mut dyn AnyLifetime<'v> {
        self
    }

    fn take_promises(&mut self) -> Option<Box<dyn AnonPromisesDyn<'v>>> {
        // We swap it out, so we can still collect new promises
        Some(mem::take(&mut self.promises))
            .filter(|p| !p.is_empty())
            .map(|p| Box::new(p) as Box<dyn AnonPromisesDyn>)
    }

    /*
    pub(crate) fn get_promises(&mut self) -> Option<AnonTargetsRegistry<'v>> {
        if self.entries.is_empty() {
            None
        } else {
            // We swap it out, so we can still collect new promises
            let mut new = AnonTargetsRegistry::new(self.execution_platform.dupe());
            mem::swap(&mut new, self);
            Some(new)
        }
    }
    */

    fn assert_no_promises(&self) -> anyhow::Result<()> {
        if self.promises.is_empty() {
            Ok(())
        } else {
            Err(AnonTargetsError::AssertNoPromisesFailed.into())
        }
    }
}

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

    #[test]
    fn anon_target_name() {
        assert_eq!(
            AnonTargetKey::parse_target_label("//foo:bar")
                .unwrap()
                .to_string(),
            "anon//foo:bar"
        );
        assert_eq!(
            AnonTargetKey::parse_target_label("cell//foo/bar:baz")
                .unwrap()
                .to_string(),
            "cell//foo/bar:baz"
        );
        assert!(AnonTargetKey::parse_target_label("foo").is_err());
        assert!(AnonTargetKey::parse_target_label("//foo:").is_err());
    }
}