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

use allocative::Allocative;
use buck2_build_api::query::bxl::BxlUqueryFunctions;
use buck2_build_api::query::bxl::NEW_BXL_UQUERY_FUNCTIONS;
use buck2_build_api::query::oneshot::QUERY_FRONTEND;
use buck2_node::nodes::unconfigured::TargetNode;
use buck2_query::query::syntax::simple::eval::set::TargetSetExt;
use buck2_query::query::syntax::simple::functions::helpers::CapturedExpr;
use derivative::Derivative;
use derive_more::Display;
use dupe::Dupe;
use gazebo::prelude::OptionExt;
use gazebo::prelude::SliceExt;
use starlark::any::ProvidesStaticType;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::list::ListRef;
use starlark::values::none::NoneOr;
use starlark::values::starlark_value;
use starlark::values::AllocValue;
use starlark::values::Heap;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::UnpackValue;
use starlark::values::Value;
use starlark::values::ValueError;
use starlark::StarlarkDocs;

use super::file_set::StarlarkFileSet;
use super::target_expr::TargetExpr;
use crate::bxl::starlark_defs::context::BxlContext;
use crate::bxl::starlark_defs::file_set::FileSetExpr;
use crate::bxl::starlark_defs::query_util::parse_query_evaluation_result;
use crate::bxl::starlark_defs::targetset::StarlarkTargetSet;

#[derive(
    ProvidesStaticType,
    Derivative,
    Display,
    Trace,
    NoSerialize,
    Allocative,
    StarlarkDocs
)]
#[starlark_docs(directory = "bxl")]
#[derivative(Debug)]
#[display(fmt = "{:?}", self)]
#[allocative(skip)]
pub struct StarlarkUQueryCtx<'v> {
    #[trace(unsafe_ignore)]
    #[derivative(Debug = "ignore")]
    ctx: &'v BxlContext<'v>,
}

#[starlark_value(type = "uqueryctx", StarlarkTypeRepr, UnpackValue)]
impl<'v> StarlarkValue<'v> for StarlarkUQueryCtx<'v> {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(register_uquery)
    }
}

pub(crate) async fn get_uquery_env<'v>(
    ctx: &'v BxlContext<'v>,
) -> anyhow::Result<Box<dyn BxlUqueryFunctions<'v> + 'v>> {
    (NEW_BXL_UQUERY_FUNCTIONS.get()?)(ctx.async_ctx.0, ctx.project_root().dupe(), ctx.cell_name)
        .await
}

impl<'v> AllocValue<'v> for StarlarkUQueryCtx<'v> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_complex_no_freeze(self)
    }
}

impl<'v> StarlarkUQueryCtx<'v> {
    pub fn new(ctx: &'v BxlContext<'v>) -> anyhow::Result<Self> {
        Ok(Self { ctx })
    }
}

/// The context for performing `uquery` operations in bxl. The functions offered on this ctx are
/// the same behaviour as the query functions available within uquery command.
#[starlark_module]
fn register_uquery(builder: &mut MethodsBuilder) {
    /// The `allpaths` query for computing all dependency paths.
    fn allpaths<'v>(
        this: &StarlarkUQueryCtx<'v>,
        from: Value<'v>,
        to: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkTargetSet<TargetNode>> {
        this.ctx.async_ctx.via_dice(|ctx| async {
            get_uquery_env(this.ctx)
                .await?
                .allpaths(
                    &*TargetExpr::<'v, TargetNode>::unpack(from, this.ctx, eval)
                        .await?
                        .get(ctx)
                        .await?,
                    &*TargetExpr::<'v, TargetNode>::unpack(to, this.ctx, eval)
                        .await?
                        .get(ctx)
                        .await?,
                )
                .await
                .map(StarlarkTargetSet::from)
        })
    }

    /// The somepaths query, which returns the graph of nodes on some arbitrary path from a start to destination target.
    fn somepath<'v>(
        this: &StarlarkUQueryCtx<'v>,
        from: Value<'v>,
        to: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkTargetSet<TargetNode>> {
        this.ctx.async_ctx.via_dice(|ctx| async {
            get_uquery_env(this.ctx)
                .await?
                .somepath(
                    &*TargetExpr::<'v, TargetNode>::unpack(from, this.ctx, eval)
                        .await?
                        .get(ctx)
                        .await?,
                    &*TargetExpr::<'v, TargetNode>::unpack(to, this.ctx, eval)
                        .await?
                        .get(ctx)
                        .await?,
                )
                .await
                .map(StarlarkTargetSet::from)
        })
    }

    /// The attrfilter query for rule attribute filtering.
    fn attrfilter<'v>(
        this: &StarlarkUQueryCtx<'v>,
        attr: &str,
        value: &str,
        targets: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkTargetSet<TargetNode>> {
        this.ctx.async_ctx.via_dice(|ctx| async {
            let targets = TargetExpr::<'v, TargetNode>::unpack(targets, this.ctx, eval)
                .await?
                .get(ctx)
                .await?;
            targets
                .attrfilter(attr, &|v| Ok(v == value))
                .map(StarlarkTargetSet::from)
        })
    }

    /// The inputs query for finding input files.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_inputs(ctx):
    ///     result = ctx.uquery().inputs("root//bin:the_binary")
    ///     ctx.output.print(result)
    /// ```
    fn inputs<'v>(
        this: &StarlarkUQueryCtx<'v>,
        targets: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkFileSet> {
        this.ctx
            .async_ctx
            .via_dice(|ctx| async {
                let targets = TargetExpr::<'v, TargetNode>::unpack(targets, this.ctx, eval)
                    .await?
                    .get(ctx)
                    .await?;
                targets.inputs()
            })
            .map(StarlarkFileSet::from)
    }

    /// The kind query for filtering targets by rule type.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_kind(ctx):
    ///     kind = ctx.uquery().kind(".*1", "bin/kind/...")
    ///     ctx.output.print(kind)
    /// ```
    fn kind<'v>(
        this: &StarlarkUQueryCtx<'v>,
        regex: &str,
        targets: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkTargetSet<TargetNode>> {
        this.ctx.async_ctx.via_dice(|ctx| async {
            let targets = TargetExpr::<'v, TargetNode>::unpack(targets, this.ctx, eval)
                .await?
                .get(ctx)
                .await?;
            targets.kind(regex).map(StarlarkTargetSet::from)
        })
    }

    /// The deps query for finding the transitive closure of dependencies.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_deps(ctx):
    ///     result = ctx.uquery().deps("root//bin:the_binary", 1)
    ///     ctx.output.print(result)
    /// ```
    fn deps<'v>(
        this: &StarlarkUQueryCtx<'v>,
        universe: Value<'v>,
        #[starlark(default = NoneOr::None)] depth: NoneOr<i32>,
        #[starlark(default = NoneOr::None)] filter: NoneOr<&'v str>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkTargetSet<TargetNode>> {
        this.ctx
            .async_ctx
            .via_dice(|ctx| async {
                let filter = filter
                    .into_option()
                    .try_map(buck2_query_parser::parse_expr)?;

                get_uquery_env(this.ctx)
                    .await?
                    .deps(
                        &*TargetExpr::<'v, TargetNode>::unpack(universe, this.ctx, eval)
                            .await?
                            .get(ctx)
                            .await?,
                        depth.into_option(),
                        filter
                            .as_ref()
                            .map(|span| CapturedExpr { expr: span })
                            .as_ref(),
                    )
                    .await
            })
            .map(StarlarkTargetSet::from)
    }

    /// The rdeps query for finding the transitive closure of reverse dependencies.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_rdeps(ctx):
    ///     result = ctx.uquery().rdeps("root//bin:the_binary", "//lib:file1", 100)
    ///     ctx.output.print(result)
    /// ```
    fn rdeps<'v>(
        this: &StarlarkUQueryCtx<'v>,
        universe: Value<'v>,
        from: Value<'v>,
        depth: Option<i32>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkTargetSet<TargetNode>> {
        this.ctx
            .async_ctx
            .via_dice(|ctx| async {
                get_uquery_env(this.ctx)
                    .await?
                    .rdeps(
                        &*TargetExpr::<'v, TargetNode>::unpack(universe, this.ctx, eval)
                            .await?
                            .get(ctx)
                            .await?,
                        &*TargetExpr::<'v, TargetNode>::unpack(from, this.ctx, eval)
                            .await?
                            .get(ctx)
                            .await?,
                        depth,
                    )
                    .await
            })
            .map(StarlarkTargetSet::from)
    }

    /// The filter query for filtering targets by name.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_filter(ctx):
    ///     result = ctx.uquery().filter(".*the_binary", "root//...")
    ///     ctx.output.print(result)
    /// ```
    fn filter<'v>(
        this: &StarlarkUQueryCtx<'v>,
        regex: &str,
        targets: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkTargetSet<TargetNode>> {
        this.ctx
            .async_ctx
            .via_dice(|ctx| async {
                let targets = TargetExpr::<'v, TargetNode>::unpack(targets, this.ctx, eval)
                    .await?
                    .get(ctx)
                    .await?;
                targets.filter_name(regex)
            })
            .map(StarlarkTargetSet::from)
    }

    /// The testsof query for listing the tests of the specified targets.
    ///
    /// Sample usage:
    /// ```text
    /// def _testsof_impl(ctx):
    ///     result = ctx.uquery().testsof("//:foo_lib")
    ///     ctx.output.print(result)
    /// ```
    fn testsof<'v>(
        this: &StarlarkUQueryCtx<'v>,
        targets: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkTargetSet<TargetNode>> {
        this.ctx
            .async_ctx
            .via_dice(|ctx| async {
                get_uquery_env(this.ctx)
                    .await?
                    .testsof(
                        &*TargetExpr::<'v, TargetNode>::unpack(targets, this.ctx, eval)
                            .await?
                            .get(ctx)
                            .await?,
                    )
                    .await
            })
            .map(StarlarkTargetSet::from)
    }

    /// Find the build file(s) that defines a target or a target set.
    ///
    /// Sample usage:
    /// ```text
    /// def _buildfile_impl(ctx):
    ///     owner = ctx.uquery().owner(["bin/TARGET", "bin/kind"])
    ///     result = ctx.uquery().buildfile(owner)
    ///     ctx.output.print(result)
    /// ```
    fn buildfile<'v>(
        this: &StarlarkUQueryCtx<'v>,
        targets: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkFileSet> {
        this.ctx
            .async_ctx
            .via_dice(|ctx| async {
                let targets = &*TargetExpr::<'v, TargetNode>::unpack(targets, this.ctx, eval)
                    .await?
                    .get(ctx)
                    .await?;

                Ok(targets.buildfile())
            })
            .map(StarlarkFileSet::from)
    }

    /// The owner query for finding targets that own specified files.
    ///
    /// Sample usage:
    /// ```text
    /// def _owner_impl(ctx):
    ///     owner = ctx.uquery().owner("bin/TARGETS.fixture")
    ///     ctx.output.print(owner)
    /// ```
    fn owner<'v>(
        this: &StarlarkUQueryCtx,
        files: FileSetExpr,
    ) -> anyhow::Result<StarlarkTargetSet<TargetNode>> {
        this.ctx
            .async_ctx
            .via(|| async {
                get_uquery_env(this.ctx)
                    .await?
                    .owner((files.get(this.ctx).await?).as_ref())
                    .await
            })
            .map(StarlarkTargetSet::from)
    }

    /// The attrregexfilter query for rule attribute filtering with regex.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_attrregexfilter(ctx):
    ///     filtered = ctx.uquery().attrregexfilter("foo", "he.lo", "bin/kind/...")
    ///     ctx.output.print(filtered)
    /// ```
    fn attrregexfilter<'v>(
        this: &StarlarkUQueryCtx<'v>,
        attribute: &str,
        value: &str,
        targets: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkTargetSet<TargetNode>> {
        this.ctx.async_ctx.via_dice(|ctx| async {
            let targets = TargetExpr::<'v, TargetNode>::unpack(targets, this.ctx, eval)
                .await?
                .get(ctx)
                .await?;
            targets
                .attrregexfilter(attribute, value)
                .map(StarlarkTargetSet::from)
        })
    }

    /// Evaluates some general query string, `query_args` can be a target_set of unconfigured nodes, or
    /// a list of strings.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_eval(ctx):
    ///     result1 = ctx.uquery().eval("inputs(cell//path/to/file:target)")
    ///     ctx.output.print(result1)
    ///
    ///     result2 = ctx.uquery().eval("inputs(%s)", query_args = ["cell//path/to/file:target"])
    ///     ctx.output.print(result2)
    /// ```
    fn eval<'v>(
        this: &StarlarkUQueryCtx<'v>,
        query: &'v str,
        #[starlark(default = NoneOr::None)] query_args: NoneOr<Value<'v>>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        let query_args = if query_args.is_none() {
            Vec::new()
        } else {
            let unwrapped_query_args = query_args.into_option().unwrap();
            if let Some(query_args) = unpack_unconfigured_query_args(unwrapped_query_args)? {
                query_args
            } else {
                return Err(ValueError::IncorrectParameterTypeWithExpected(
                    "list of strings, or a target_set with unconfigured nodes".to_owned(),
                    unwrapped_query_args.get_type().to_owned(),
                )
                .into());
            }
        };

        this.ctx.async_ctx.via_dice(|ctx| async {
            parse_query_evaluation_result(
                QUERY_FRONTEND
                    .get()?
                    .eval_uquery(ctx, &this.ctx.working_dir()?, query, &query_args, None)
                    .await?,
                eval,
            )
        })
    }
}

pub(crate) fn unpack_unconfigured_query_args<'v>(
    query_args: Value<'v>,
) -> anyhow::Result<Option<Vec<String>>> {
    if let Some(list) = <&ListRef>::unpack_value(query_args) {
        Ok(Some(list.content().try_map(|e| match e.unpack_str() {
            Some(arg) => Ok(arg.to_owned()),
            None => Err(ValueError::IncorrectParameterTypeWithExpected(
                "list of strings, or a target_set of unconfigured nodes".to_owned(),
                query_args.get_type().to_owned(),
            )),
        })?))
    } else if let Some(set) = <&StarlarkTargetSet<TargetNode>>::unpack_value(query_args) {
        // TODO - we really should change eval_query() to handle this, but escaping the unconfigured target label for now
        // as a quick solution.
        Ok(Some(
            set.0.iter_names().map(|e| format!("\"{}\"", e)).collect(),
        ))
    } else {
        Ok(None)
    }
}


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

//! Starlark Actions API for bxl functions
//!

use std::collections::HashMap;

use allocative::Allocative;
use buck2_build_api::analysis::calculation::RuleAnalysisCalculation;
use buck2_build_api::analysis::registry::AnalysisRegistry;
use buck2_build_api::interpreter::rule_defs::context::AnalysisActions;
use buck2_build_api::interpreter::rule_defs::provider::dependency::Dependency;
use buck2_configured::configuration::calculation::ConfigurationCalculation;
use buck2_configured::nodes::calculation::ExecutionPlatformConstraints;
use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_core::cells::name::CellName;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::configuration::pair::ConfigurationNoExec;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::target::label::TargetLabel;
use buck2_node::attrs::configuration_context::AttrConfigurationContext;
use buck2_node::attrs::configuration_context::AttrConfigurationContextImpl;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use buck2_util::collections::ordered_map::OrderedMap;
use derivative::Derivative;
use derive_more::Display;
use dice::DiceComputations;
use dupe::Dupe;
use starlark::any::ProvidesStaticType;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::environment::Module;
use starlark::starlark_module;
use starlark::values::dict::DictRef;
use starlark::values::starlark_value;
use starlark::values::AllocValue;
use starlark::values::Heap;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::Value;
use starlark::values::ValueOfUnchecked;
use starlark::values::ValueTyped;
use starlark::StarlarkDocs;
use thiserror::Error;

use crate::bxl::starlark_defs::context::BxlContext;

#[derive(Debug, Error)]
enum BxlActionsError {
    #[error(
        "An action registry was already requested via `actions_factory`. Only one action registry is allowed"
    )]
    RegistryAlreadyCreated,
}

#[allow(unused)]
pub(crate) async fn resolve_bxl_execution_platform<'v>(
    ctx: &'v DiceComputations,
    cell: CellName,
    exec_deps: Vec<ProvidersLabel>,
    toolchain_deps: Vec<ProvidersLabel>,
    target_platform: Option<TargetLabel>,
    exec_compatible_with: Vec<TargetLabel>,
    module: &'v Module,
) -> anyhow::Result<BxlExecutionResolution<'v>> {
    // bxl has on transitions
    let resolved_transitions = OrderedMap::new();

    let platform_configuration = match target_platform.as_ref() {
        Some(global_target_platform) => {
            ctx.get_platform_configuration(global_target_platform)
                .await?
        }
        None => ConfigurationData::unspecified(),
    };
    let resolved_configuration = {
        ctx.get_resolved_configuration(&platform_configuration, cell, &exec_compatible_with)
            .await?
    };

    // there is not explicit configured deps, so platforms is empty
    let platform_cfgs = OrderedMap::new();

    let configuration_ctx = AttrConfigurationContextImpl::new(
        &resolved_configuration,
        ConfigurationNoExec::unbound_exec(),
        // We don't really need `resolved_transitions` here:
        // `Traversal` declared above ignores transitioned dependencies.
        // But we pass `resolved_transitions` here to prevent breakages in the future
        // if something here changes.
        &resolved_transitions,
        &platform_cfgs,
    );
    let mut toolchain_deps_configured = HashMap::new();

    for dep in toolchain_deps.into_iter() {
        let configured = configuration_ctx.configure_toolchain_target(&dep);

        let dependency = get_dependency_for_label(configured, ctx, module).await?;

        toolchain_deps_configured.insert(dep, dependency);
    }

    let execution_constraints = ExecutionPlatformConstraints::new_constraints(
        exec_deps
            .iter()
            .map(|label| label.target().dupe())
            .collect(),
        toolchain_deps_configured
            .values()
            .map(|dep| dep.label().inner().target().clone())
            .collect(),
        exec_compatible_with,
    );

    let resolved_execution = execution_constraints.one_for_cell(ctx, cell).await?;

    let mut exec_deps_configured = HashMap::new();

    for exec_dep in exec_deps.into_iter() {
        let configured = exec_dep
            .configure_pair_no_exec(resolved_execution.platform()?.cfg_pair_no_exec().dupe());

        let dependency = get_dependency_for_label(configured, ctx, module).await?;

        exec_deps_configured.insert(exec_dep, dependency);
    }

    Ok(BxlExecutionResolution {
        resolved_execution,
        exec_deps_configured,
        toolchain_deps_configured,
    })
}

async fn get_dependency_for_label<'v>(
    configured: ConfiguredProvidersLabel,
    ctx: &'v DiceComputations,
    module: &'v Module,
) -> anyhow::Result<Dependency<'v>> {
    let analysis_result = ctx
        .get_analysis_result(configured.target())
        .await?
        .require_compatible()?;

    let v = analysis_result.lookup_inner(&configured)?;

    let dependency = Dependency::new(
        module.heap(),
        configured,
        v.value().owned_value(module.frozen_heap()),
        None,
    );

    Ok(dependency)
}

pub(crate) struct BxlExecutionResolution<'v> {
    pub(crate) resolved_execution: ExecutionPlatformResolution,
    pub(crate) exec_deps_configured: HashMap<ProvidersLabel, Dependency<'v>>,
    pub(crate) toolchain_deps_configured: HashMap<ProvidersLabel, Dependency<'v>>,
}

pub(crate) fn validate_action_instantiation<'v>(
    this: &BxlContext<'v>,
    execution_platform: ExecutionPlatformResolution,
) -> anyhow::Result<()> {
    let mut registry = this.state.state.borrow_mut();

    if (*registry).is_some() {
        return Err(anyhow::anyhow!(BxlActionsError::RegistryAlreadyCreated));
    } else {
        let analysis_registry = AnalysisRegistry::new_from_owner(
            BaseDeferredKey::BxlLabel(this.current_bxl.dupe().into_base_deferred_key_dyn_impl()),
            execution_platform,
        )?;

        *registry = Some(analysis_registry);
    }

    Ok(())
}

#[derive(
    ProvidesStaticType,
    Derivative,
    Display,
    Trace,
    NoSerialize,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(directory = "bxl")]
#[derivative(Debug)]
#[display(fmt = "{:?}", self)]
pub(crate) struct BxlActions<'v> {
    actions: ValueTyped<'v, AnalysisActions<'v>>,
    exec_deps: ValueOfUnchecked<'v, DictRef<'v>>,
    toolchains: ValueOfUnchecked<'v, DictRef<'v>>,
}

impl<'v> BxlActions<'v> {
    pub(crate) fn new(
        actions: ValueTyped<'v, AnalysisActions<'v>>,
        exec_deps: ValueOfUnchecked<'v, DictRef<'v>>,
        toolchains: ValueOfUnchecked<'v, DictRef<'v>>,
    ) -> Self {
        Self {
            actions,
            exec_deps,
            toolchains,
        }
    }
}

#[starlark_value(type = "bxl_actions", StarlarkTypeRepr, UnpackValue)]
impl<'v> StarlarkValue<'v> for BxlActions<'v> {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(bxl_actions_methods)
    }
}

impl<'v> AllocValue<'v> for BxlActions<'v> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_complex_no_freeze(self)
    }
}

/// The bxl action context is the context for creating actions. This context is obtained after
/// performing execution platform resolution based on a set of given dependencies and toolchains.
///
/// You can access the analysis actions to create actions, and the resolved dependencies and
/// toolchains from this context
#[starlark_module]
fn bxl_actions_methods(builder: &mut MethodsBuilder) {
    /// Gets the analysis action context to create and register actions on the execution platform
    /// corresponding to this bxl action's execution platform resolution.
    #[starlark(attribute)]
    fn actions<'v>(this: &'v BxlActions) -> anyhow::Result<ValueTyped<'v, AnalysisActions<'v>>> {
        Ok(this.actions)
    }

    /// Gets the execution deps requested correctly configured for the current execution platform
    #[starlark(attribute)]
    fn exec_deps<'v>(this: &'v BxlActions) -> anyhow::Result<ValueOfUnchecked<'v, DictRef<'v>>> {
        Ok(this.exec_deps)
    }

    /// Gets the toolchains requested configured for the current execution platform
    #[starlark(attribute)]
    fn toolchains<'v>(this: &'v BxlActions) -> anyhow::Result<ValueOfUnchecked<'v, DictRef<'v>>> {
        Ok(this.toolchains)
    }
}


/*
 * 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::analysis::calculation::RuleAnalysisCalculation;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use dice::DiceComputations;
use either::Either;
use gazebo::prelude::*;

use crate::bxl::starlark_defs::analysis_result::StarlarkAnalysisResult;
use crate::bxl::starlark_defs::providers_expr::ProvidersExpr;

pub(crate) async fn analysis(
    ctx: &DiceComputations,
    expr: ProvidersExpr<ConfiguredProvidersLabel>,
    skip_incompatible: bool,
) -> anyhow::Result<
    Either<StarlarkAnalysisResult, Vec<(ConfiguredProvidersLabel, StarlarkAnalysisResult)>>,
> {
    let analysis = futures::future::join_all(expr.labels().map(async move |label| {
        let maybe_result = ctx
            .get_analysis_result(label.target())
            .await?
            .require_compatible();

        if skip_incompatible && maybe_result.is_err() {
            Ok(None)
        } else {
            Ok(Some((
                label.clone(),
                StarlarkAnalysisResult::new(maybe_result?, label.clone()),
            )))
        }
    }))
    .await
    .into_iter()
    .filter_map(|r| match r {
        Ok(r) => r.map(Ok),
        Err(e) => Some(Err(e)),
    })
    .collect::<anyhow::Result<Vec<_>>>()?;

    match expr {
        ProvidersExpr::Literal(_) => {
            if analysis.len() != 1 {
                Err(anyhow::anyhow!(
                    "Expected exactly 1 analysis result when requesting a single target"
                ))
            } else {
                Ok(Either::Left(
                    analysis.into_iter().into_singleton().unwrap().1,
                ))
            }
        }
        ProvidersExpr::Iterable(_) => Ok(Either::Right(analysis)),
    }
}


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

//!
//! Implements the ability for bxl to build targets
use allocative::Allocative;
use buck2_build_api::build::build_configured_label;
use buck2_build_api::build::BuildTargetResult;
use buck2_build_api::build::ConvertMaterializationContext;
use buck2_build_api::build::ProvidersToBuild;
use buck2_build_api::bxl::build_result::BxlBuildResult;
use buck2_build_api::interpreter::rule_defs::artifact::StarlarkArtifact;
use buck2_cli_proto::build_request::Materializations;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_interpreter::types::label::Label;
use derive_more::Display;
use dupe::Dupe;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use itertools::Itertools;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::eval::Evaluator;
use starlark::starlark_complex_value;
use starlark::values::starlark_value;
use starlark::values::Freeze;
use starlark::values::Heap;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::Value;
use starlark::values::ValueLike;
use starlark_map::small_map::SmallMap;

use crate::bxl::starlark_defs::build_result::StarlarkBxlBuildResult;
use crate::bxl::starlark_defs::context::BxlContext;
use crate::bxl::starlark_defs::providers_expr::ProvidersExpr;

#[derive(
    Debug,
    Clone,
    Trace,
    Coerce,
    Freeze,
    Display,
    ProvidesStaticType,
    NoSerialize,
    Allocative
)]
#[repr(C)]
pub(crate) struct StarlarkProvidersArtifactIterableGen<V>(pub(crate) V);

starlark_complex_value!(pub(crate) StarlarkProvidersArtifactIterable);

#[starlark_value(type = "bxl_built_artifacts_iterable")]
impl<'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for StarlarkProvidersArtifactIterableGen<V>
where
    Self: ProvidesStaticType<'v>,
{
    fn iterate_collect(&self, heap: &'v Heap) -> anyhow::Result<Vec<Value<'v>>> {
        Ok(self
            .0
            .downcast_ref::<StarlarkBxlBuildResult>()
            .unwrap()
            .0
            .unpack_built()
            .unwrap()
            .outputs
            .iter()
            .flat_map(|built| match built {
                Ok(built) => itertools::Either::Left(Box::new(
                    built
                        .values
                        .iter()
                        .map(|(artifact, _)| heap.alloc(StarlarkArtifact::new(artifact.dupe()))),
                )),
                Err(_) => itertools::Either::Right(std::iter::empty()),
            })
            .collect())
    }
}

#[derive(
    Debug,
    Clone,
    Trace,
    Coerce,
    Freeze,
    Display,
    ProvidesStaticType,
    NoSerialize,
    Allocative
)]
#[repr(C)]
pub(crate) struct StarlarkFailedArtifactIterableGen<V>(pub(crate) V);

starlark_complex_value!(pub(crate) StarlarkFailedArtifactIterable);

#[starlark_value(type = "bxl_failed_artifacts_iterable")]
impl<'v, V: ValueLike<'v> + 'v> StarlarkValue<'v> for StarlarkFailedArtifactIterableGen<V>
where
    Self: ProvidesStaticType<'v>,
{
    fn iterate_collect(&self, heap: &'v Heap) -> anyhow::Result<Vec<Value<'v>>> {
        Ok(self
            .0
            .downcast_ref::<StarlarkBxlBuildResult>()
            .unwrap()
            .0
            .unpack_built()
            .unwrap()
            .outputs
            .iter()
            .filter_map(|built| match built {
                Ok(_) => None,
                Err(e) => Some(heap.alloc(format!("{}", e))),
            })
            .collect())
    }
}

pub(crate) fn build<'v>(
    ctx: &'v BxlContext<'v>,
    spec: Value<'v>,
    target_platform: Value<'v>,
    materializations: Materializations,
    eval: &Evaluator<'v, '_>,
) -> anyhow::Result<SmallMap<Value<'v>, Value<'v>>> {
    let build_spec =
        ProvidersExpr::<ConfiguredProvidersLabel>::unpack(spec, target_platform, ctx, eval)?;

    let materializations =
        ConvertMaterializationContext::with_existing_map(materializations, &ctx.materializations);

    let build_result = ctx.async_ctx.via_dice(async move |dice| {
        let materializations = &materializations;

        let stream = build_spec
            .labels()
            .unique()
            .map(|target| async move {
                let res = build_configured_label(
                    dice,
                    materializations,
                    target.clone(),
                    &ProvidersToBuild {
                        default: true,
                        default_other: true,
                        run: true,
                        tests: true,
                    }, // TODO support skipping/configuring?
                    false,
                )
                .await;

                match res {
                    Ok(stream) => stream.map(Ok).left_stream(),
                    Err(e) => futures::stream::once(futures::future::ready(Err(e))).right_stream(),
                }
            })
            .collect::<FuturesUnordered<_>>()
            .flatten_unordered(None);

        // TODO (torozco): support --fail-fast in BXL.
        BuildTargetResult::collect_stream(stream, false).await
    })?;

    build_result
        .into_iter()
        .map(|(target, result)| {
            Ok((
                eval.heap().alloc(Label::new(target)).get_hashed().unwrap(),
                eval.heap()
                    .alloc(StarlarkBxlBuildResult(BxlBuildResult::new(result))),
            ))
        })
        .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 buck2_artifact::artifact::source_artifact::SourceArtifact;
use buck2_build_api::interpreter::rule_defs::artifact::StarlarkArtifact;
use buck2_common::dice::file_ops::HasFileOps;
use buck2_common::file_ops::FileOps;
use buck2_common::package_listing::dice::HasPackageListingResolver;
use buck2_common::package_listing::resolver::PackageListingResolver;
use buck2_core::buck_path::path::BuckPath;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::instance::CellInstance;
use buck2_core::fs::artifact_path_resolver::ArtifactFs;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::project::ProjectRoot;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_core::package::package_relative_path::PackageRelativePath;
use buck2_core::package::PackageLabel;
use buck2_node::nodes::unconfigured::TargetNode;
use derivative::Derivative;
use derive_more::Display;
use starlark::any::ProvidesStaticType;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::none::NoneType;
use starlark::values::starlark_value;
use starlark::values::AllocValue;
use starlark::values::Heap;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::StringValue;
use starlark::values::Trace;
use starlark::values::Value;
use starlark::StarlarkDocs;
use thiserror::Error;

use super::BxlContext;
use crate::bxl::starlark_defs::context::starlark_async::BxlSafeDiceComputations;
use crate::bxl::starlark_defs::file_expr::FileExpr;
use crate::bxl::starlark_defs::file_set::StarlarkReadDirSet;
use crate::bxl::starlark_defs::target_expr::TargetExpr;

#[derive(
    ProvidesStaticType,
    Derivative,
    Display,
    Trace,
    NoSerialize,
    StarlarkDocs,
    Allocative
)]
#[derivative(Debug)]
#[starlark_docs(directory = "bxl")]
#[display(fmt = "{:?}", self)]
#[allocative(skip)]
pub struct BxlFilesystem<'v> {
    #[trace(unsafe_ignore)]
    #[derivative(Debug = "ignore")]
    ctx: &'v BxlContext<'v>,
}

impl<'v> BxlFilesystem<'v> {
    pub(crate) fn new(ctx: &'v BxlContext<'v>) -> Self {
        Self { ctx }
    }

    fn artifact_fs(&self) -> &ArtifactFs {
        &self.ctx.output_stream.artifact_fs
    }

    fn project_fs(&self) -> &ProjectRoot {
        &self.ctx.output_stream.project_fs
    }

    fn cell(&self) -> anyhow::Result<&CellInstance> {
        self.ctx.cell_resolver.get(self.ctx.cell_name)
    }

    fn dice(&self) -> &'v BxlSafeDiceComputations {
        &self.ctx.async_ctx
    }
}

#[starlark_value(type = "fs", StarlarkTypeRepr, UnpackValue)]
impl<'v> StarlarkValue<'v> for BxlFilesystem<'v> {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(fs_operations)
    }
}

impl<'v> AllocValue<'v> for BxlFilesystem<'v> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_complex_no_freeze(self)
    }
}

#[derive(Debug, Error)]
pub enum BxlFilesystemError {
    #[error("Inferred package path `{0}` is not a valid package within the given file path `{1}`")]
    PackageMismatch(PackageLabel, CellPath),
    #[error("Expected a single target hint, not an iterable: `{0}`")]
    MultipleTargetHintsNotSupported(String),
}

impl<'v> BxlFilesystem<'v> {
    /// Returns the absolute path for a FileExpr.
    fn resolve(&'v self, expr: FileExpr<'v>) -> anyhow::Result<AbsNormPathBuf> {
        let project_rel_path = self.project_relative_path(expr)?;
        Ok(self.project_fs().resolve(&project_rel_path))
    }

    /// Returns the project relative path for a cellpath.
    fn project_relative_path(
        &'v self,
        expr: FileExpr<'v>,
    ) -> anyhow::Result<ProjectRelativePathBuf> {
        let cell_path = expr.get(self.dice(), self.cell()?)?;
        self.artifact_fs().resolve_cell_path(cell_path.as_ref())
    }
}

/// Provides some basic tracked filesystem access for bxl functions so that they can meaningfully
/// detect simple properties of artifacts, and source directories.
#[starlark_module]
fn fs_operations(builder: &mut MethodsBuilder) {
    /// Check if a path exists on disk, taking advantage of Buck's cached filesystem.
    /// Takes in a literal, a source artifact (via `[StarlarkArtifact]`), or a `[StarlarkFileNode]`.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_exists(ctx):
    ///     ctx.output.print(ctx.fs.exists("bin"))
    /// ```
    fn exists<'v>(this: &'v BxlFilesystem<'v>, expr: FileExpr<'v>) -> anyhow::Result<bool> {
        let path = expr.get(this.dice(), this.cell()?);

        match path {
            Ok(p) => this.dice().via_dice(async move |ctx| {
                <dyn FileOps>::try_exists(&ctx.file_ops(), p.as_ref()).await
            }),
            Err(e) => Err(e),
        }
    }

    /// Returns all the contents of the given input that points to a directory.
    /// Errors if the given path is a file. Takes an optional boolean `dirs_only` to only return directories, defaults to false.
    ///
    /// The input is a either a literal, a source artifact (via `[StarlarkArtifact]`), or a `[StarlarkFileNode]`.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_list(ctx):
    ///     list_results = ctx.fs.list("bin")
    ///     for result in list_results:
    ///         ctx.output.print(result)
    /// ```
    fn list<'v>(
        this: &'v BxlFilesystem<'v>,
        expr: FileExpr<'v>,
        #[starlark(require = named, default = false)] dirs_only: bool,
    ) -> anyhow::Result<StarlarkReadDirSet> {
        let path = expr.get(this.dice(), this.cell()?);

        match path {
            Ok(path) => this.dice().via_dice(async move |ctx| {
                let read_dir_output = ctx.file_ops().read_dir(path.as_ref()).await?;
                Ok(StarlarkReadDirSet {
                    cell_path: path,
                    included: read_dir_output.included,
                    dirs_only,
                })
            }),
            Err(e) => Err(e),
        }
    }

    /// Returns whether the provided path is a dir. Returns false is the dir does not exist.
    /// The input is a either a literal, a source artifact (via `[StarlarkArtifact]`), or a `[StarlarkFileNode]`.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_is_dir(ctx):
    ///     ctx.output.print(ctx.fs.is_dir("bin"))
    /// ```
    fn is_dir<'v>(this: &'v BxlFilesystem<'v>, expr: FileExpr<'v>) -> anyhow::Result<bool> {
        Ok(std::path::Path::is_dir(this.resolve(expr)?.as_ref()))
    }

    /// Returns whether the provided path is a file. Returns false is the file does not exist.
    /// The input is a either a literal, a source artifact (via `[StarlarkArtifact]`), or a `[StarlarkFileNode]`.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_is_file(ctx):
    ///     ctx.output.print(ctx.fs.is_dir("bin"))
    /// ```
    fn is_file<'v>(this: &'v BxlFilesystem<'v>, expr: FileExpr<'v>) -> anyhow::Result<bool> {
        Ok(std::path::Path::is_file(this.resolve(expr)?.as_ref()))
    }

    /// Returns the relative path to the project root, given the file expression.
    ///
    /// Sample usage:
    /// ```text
    /// def project_rel_path(ctx):
    ///     ctx.output.print(ctx.fs.project_rel_path("bin"))
    /// ```
    fn project_rel_path<'v>(
        this: &'v BxlFilesystem<'v>,
        expr: FileExpr<'v>,
        heap: &'v Heap,
    ) -> anyhow::Result<StringValue<'v>> {
        Ok(heap.alloc_str(this.project_relative_path(expr)?.as_str()))
    }

    /// Returns the absolute path, given the file expression. Use at your own risk, as the current working directory
    /// may have been changed when this function is called. In addition, passing the absolute path into actions that
    /// are run remotely will most likely result in failures since the absolute path most likely differs locally vs remotely.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_abs_path_unsafe(ctx):
    ///     ctx.output.print(ctx.fs.abs_path_unsafe("bin"))
    /// ```
    fn abs_path_unsafe<'v>(
        this: &'v BxlFilesystem<'v>,
        expr: FileExpr<'v>,
        heap: &'v Heap,
    ) -> anyhow::Result<StringValue<'v>> {
        let abs_norm_path = this.resolve(expr)?;
        Ok(heap.alloc_str(abs_norm_path.as_abs_path().to_str()?))
    }

    /// Returns the source artifact for a path and an optional target hint (unconfigured target label or node)
    /// which points to the owning package. If no target hint is given, the nearest package will be used to
    /// guess the desired artifact. The path should be either an absolute path, or a project relative path.
    fn source<'v>(
        this: &'v BxlFilesystem<'v>,
        expr: FileExpr<'v>,
        #[starlark(default = NoneType)] target_hint: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        let file_path_as_cell_path = expr.get(this.dice(), this.cell()?)?;
        let file_path = this
            .artifact_fs()
            .resolve_cell_path(file_path_as_cell_path.as_ref())?;

        let package_label = this.dice().via_dice(|dice| async {
            if target_hint.is_none() {
                dice.get_package_listing_resolver()
                    .get_enclosing_package(file_path_as_cell_path.as_ref())
                    .await
            } else {
                let target_expr =
                    TargetExpr::<'v, TargetNode>::unpack(target_hint, this.ctx, eval).await?;
                match target_expr {
                    TargetExpr::Node(node) => Ok(node.label().pkg()),
                    TargetExpr::Label(label) => Ok(label.as_ref().pkg()),
                    _ => Err(anyhow::anyhow!(
                        BxlFilesystemError::MultipleTargetHintsNotSupported(target_hint.to_repr())
                    )),
                }
            }
        })?;

        let package_path = this
            .artifact_fs()
            .resolve_cell_path(package_label.as_cell_path())?;

        let forward_relative_path = match file_path.strip_prefix(&package_path) {
            Ok(path) => path,
            Err(_) => {
                return Err(anyhow::anyhow!(BxlFilesystemError::PackageMismatch(
                    package_label,
                    file_path_as_cell_path,
                )));
            }
        };

        let package_relative_path = PackageRelativePath::new(forward_relative_path.as_path())?;
        let buck_path = BuckPath::new(package_label, package_relative_path.into());
        Ok(eval
            .heap()
            .alloc(StarlarkArtifact::new(SourceArtifact::new(buck_path).into())))
    }
}


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

use std::cell::RefCell;
use std::fmt::Display;
use std::io::Write;
use std::ops::DerefMut;

use allocative::Allocative;
use anyhow::Context;
use buck2_build_api::artifact_groups::ArtifactGroup;
use buck2_build_api::bxl::build_result::BxlBuildResult;
use buck2_build_api::interpreter::rule_defs::artifact::StarlarkArtifact;
use buck2_build_api::interpreter::rule_defs::cmd_args::value_as::ValueAsCommandLineLike;
use buck2_build_api::interpreter::rule_defs::cmd_args::CommandLineArgLike;
use buck2_build_api::interpreter::rule_defs::cmd_args::SimpleCommandLineArtifactVisitor;
use buck2_build_api::interpreter::rule_defs::cmd_args::StarlarkCommandLineInputs;
use buck2_core::fs::artifact_path_resolver::ArtifactFs;
use buck2_core::fs::project::ProjectRoot;
use buck2_execute::path::artifact_path::ArtifactPath;
use derivative::Derivative;
use derive_more::Display;
use dupe::Dupe;
use gazebo::prelude::SliceExt;
use serde::ser::SerializeSeq;
use serde::Serialize;
use serde::Serializer;
use starlark::any::ProvidesStaticType;
use starlark::collections::SmallSet;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::starlark_module;
use starlark::values::dict::Dict;
use starlark::values::dict::DictRef;
use starlark::values::list::ListRef;
use starlark::values::none::NoneType;
use starlark::values::record::Record;
use starlark::values::starlark_value;
use starlark::values::structs::StructRef;
use starlark::values::tuple::TupleRef;
use starlark::values::AllocValue;
use starlark::values::Heap;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::UnpackValue;
use starlark::values::Value;
use starlark::values::ValueError;
use starlark::values::ValueLike;
use starlark::StarlarkDocs;

use super::starlark_async::BxlSafeDiceComputations;
use crate::bxl::starlark_defs::artifacts::EnsuredArtifact;
use crate::bxl::starlark_defs::artifacts::EnsuredArtifactGroup;
use crate::bxl::starlark_defs::build_result::StarlarkBxlBuildResult;
use crate::bxl::starlark_defs::context::build::StarlarkProvidersArtifactIterable;

#[derive(
    ProvidesStaticType,
    Derivative,
    Display,
    Trace,
    NoSerialize,
    StarlarkDocs,
    Allocative
)]
#[display(fmt = "{:?}", self)]
#[starlark_docs(directory = "bxl")]
#[derivative(Debug)]
pub struct OutputStream<'v> {
    #[derivative(Debug = "ignore")]
    #[trace(unsafe_ignore)]
    #[allocative(skip)]
    pub(crate) sink: RefCell<Box<dyn Write>>,
    #[trace(unsafe_ignore)]
    artifacts_to_ensure: RefCell<Option<SmallSet<EnsuredArtifactOrGroup>>>,
    #[derivative(Debug = "ignore")]
    pub(crate) project_fs: ProjectRoot,
    #[derivative(Debug = "ignore")]
    pub(crate) artifact_fs: ArtifactFs,
    #[trace(unsafe_ignore)]
    #[derivative(Debug = "ignore")]
    #[allocative(skip)]
    pub(crate) async_ctx: BxlSafeDiceComputations<'v>,
}

/// We can ensure either an `Artifact` or an `ArtifactGroup`. When we want to ensure a `CommandLineArgLike` object,
/// the result of visiting its artifacts is a list of `ArtifactGroup`s. It's convenient to preserve the group rather
/// than extract the individual `Artifact`s from it, for perf/memory optimizations.
#[derive(Display, Debug, Allocative, Hash, Eq, PartialEq)]
pub enum EnsuredArtifactOrGroup {
    Artifact(EnsuredArtifact),
    ArtifactGroup(ArtifactGroup),
}

impl<'v> OutputStream<'v> {
    pub fn new(
        project_fs: ProjectRoot,
        artifact_fs: ArtifactFs,
        sink: RefCell<Box<dyn Write>>,
        async_ctx: BxlSafeDiceComputations<'v>,
    ) -> Self {
        Self {
            sink,
            artifacts_to_ensure: RefCell::new(Some(Default::default())),
            project_fs,
            artifact_fs,
            async_ctx,
        }
    }

    pub fn take_artifacts(&self) -> SmallSet<EnsuredArtifactOrGroup> {
        self.artifacts_to_ensure.borrow_mut().take().unwrap()
    }
}

#[starlark_value(type = "bxl_output_stream", StarlarkTypeRepr, UnpackValue)]
impl<'v> StarlarkValue<'v> for OutputStream<'v> {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(register_output_stream)
    }
}

impl<'v> AllocValue<'v> for OutputStream<'v> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_complex_no_freeze(self)
    }
}

/// The output stream for bxl to print values to the console as their result
#[starlark_module]
fn register_output_stream(builder: &mut MethodsBuilder) {
    /// Outputs results to the console via stdout. These outputs are considered to be the results
    /// of a bxl script, which will be displayed to stdout by buck2 even when the script is cached.
    /// Accepts an optional separator that defaults to " ".
    ///
    /// Prints that are not result of the bxl should be printed via stderr via the stdlib `print`
    /// and `pprint`. Note that `ctx.output.print()` is intended for simple outputs. For more complex
    /// outputs, the recommendation would be to write them to a file.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_print(ctx):
    ///     ctx.output.print("test")
    /// ```
    fn print<'v>(
        this: &'v OutputStream<'v>,
        #[starlark(args)] args: Vec<Value<'v>>,
        #[starlark(default = " ")] sep: &'v str,
    ) -> anyhow::Result<NoneType> {
        let mut first = true;
        let mut write = |d: &dyn Display| -> anyhow::Result<()> {
            if !first {
                write!(this.sink.borrow_mut(), "{}{}", sep, d)?;
            } else {
                write!(this.sink.borrow_mut(), "{}", d)?;
                first = false;
            }
            Ok(())
        };

        for arg in args {
            if let Some(ensured) = <&EnsuredArtifact>::unpack_value(arg) {
                let path = get_artifact_path_display(
                    ensured.get_artifact_path(),
                    ensured.abs(),
                    &this.project_fs,
                    &this.artifact_fs,
                )?;
                write(&path)?;
            } else if let Some(ensured) = <&EnsuredArtifactGroup>::unpack_value(arg) {
                this.async_ctx.via_dice(|ctx| {
                    ensured.visit_artifact_path_without_associated_deduped(
                        |artifact_path, abs| {
                            let path = get_artifact_path_display(
                                artifact_path,
                                abs,
                                &this.project_fs,
                                &this.artifact_fs,
                            )?;
                            write(&path)
                        },
                        ctx,
                    )
                })?;
            } else {
                write(&arg.to_str())?;
            }
        }

        writeln!(this.sink.borrow_mut())?;

        Ok(NoneType)
    }

    /// Outputs results to the console via stdout as pretty-printed json. Pretty
    /// printing can be turned off by the `pretty` keyword-only parameter.
    /// These outputs are considered to be the results of a bxl script, which will be displayed to
    /// stdout by buck2 even when the script is cached.
    ///
    /// Prints that are not result of the bxl should be printed via stderr via the stdlib `print`
    /// and `pprint`.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_print_json(ctx):
    ///     outputs = {}
    ///     outputs.update({"foo": bar})
    ///     ctx.output.print_json("test")
    /// ```
    fn print_json<'v>(
        this: &'v OutputStream<'v>,
        value: Value<'v>,
        #[starlark(require=named, default=true)] pretty: bool,
    ) -> anyhow::Result<NoneType> {
        /// A wrapper with a Serialize instance so we can pass down the necessary context.
        struct SerializeValue<'a, 'v> {
            value: Value<'v>,
            artifact_fs: &'a ArtifactFs,
            project_fs: &'a ProjectRoot,
            async_ctx: &'v BxlSafeDiceComputations<'v>,
        }

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

        impl<'a, 'v> Serialize for SerializeValue<'a, 'v> {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                if let Some(ensured) = <&EnsuredArtifact>::unpack_value(self.value) {
                    let path = get_artifact_path_display(
                        ensured.get_artifact_path(),
                        ensured.abs(),
                        self.project_fs,
                        self.artifact_fs,
                    )
                    .map_err(|err| serde::ser::Error::custom(format!("{:#}", err)))?;
                    serializer.serialize_str(&path)
                } else if let Some(ensured) = <&EnsuredArtifactGroup>::unpack_value(self.value) {
                    let mut seq_ser = serializer.serialize_seq(None)?;

                    self.async_ctx
                        .via_dice(|ctx| {
                            ensured.visit_artifact_path_without_associated_deduped(
                                |artifact_path, abs| {
                                    let path = get_artifact_path_display(
                                        artifact_path,
                                        abs,
                                        self.project_fs,
                                        self.artifact_fs,
                                    )?;
                                    seq_ser
                                        .serialize_element(&path)
                                        .map_err(|err| anyhow::anyhow!(format!("{:#}", err)))?;
                                    Ok(())
                                },
                                ctx,
                            )
                        })
                        .map_err(|err| serde::ser::Error::custom(format!("{:#}", err)))?;
                    seq_ser.end()
                } else if let Some(x) = ListRef::from_value(self.value) {
                    serializer.collect_seq(x.iter().map(|v| self.with_value(v)))
                } else if let Some(x) = TupleRef::from_value(self.value) {
                    serializer.collect_seq(x.iter().map(|v| self.with_value(v)))
                } else if let Some(x) = DictRef::from_value(self.value) {
                    serializer.collect_map(
                        x.iter()
                            .map(|(k, v)| (self.with_value(k), self.with_value(v))),
                    )
                } else if let Some(x) = StructRef::from_value(self.value) {
                    serializer.collect_map(x.iter().map(|(k, v)| (k, self.with_value(v))))
                } else if let Some(x) = Record::from_value(self.value) {
                    serializer.collect_map(x.iter().map(|(k, v)| (k, self.with_value(v))))
                } else {
                    self.value.serialize(serializer)
                }
            }
        }

        let writer = if pretty {
            serde_json::to_writer_pretty
        } else {
            serde_json::to_writer
        };
        writer(
            this.sink.borrow_mut().deref_mut(),
            &SerializeValue {
                value,
                artifact_fs: &this.artifact_fs,
                project_fs: &this.project_fs,
                async_ctx: &this.async_ctx,
            },
        )
        .context("Error writing to JSON for `write_json`")?;
        writeln!(this.sink.borrow_mut())?;

        Ok(NoneType)
    }

    /// Marks the artifact as an artifact that should be available to the users at the end of
    /// the bxl invocation. Any artifacts that do not get registered via this call is not
    /// accessible by users at the end of bxl script.
    ///
    /// This function returns an `ensured_artifact` type that can be printed via `ctx.output.print()`
    /// to print its actual path on disk.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_ensure(ctx):
    ///     actions = ctx.bxl_actions().actions
    ///     output = actions.write("my_output", "my_content")
    ///     ensured = ctx.output.ensure(output)
    ///     ctx.output.print(ensured)
    /// ```
    fn ensure<'v>(this: &OutputStream, artifact: Value<'v>) -> anyhow::Result<EnsuredArtifact> {
        let artifact = EnsuredArtifact::new(artifact)?;
        populate_ensured_artifacts(this, EnsuredArtifactOrGroup::Artifact(artifact.clone()))?;

        Ok(artifact)
    }

    /// Same as `ensure`, but for multiple artifacts. Will preserve the shape of the inputs (i.e. if the resulting
    /// `Dict` of a `ctx.build()` is passed in, the output will be a `Dict` where the key is preserved,
    /// and the values are converted to `EnsuredArtifact`s).
    ///
    /// Note that is slower to loop through objects and ensure them one by one than it is to call `ensure_multiple()`
    /// on all the objects at once (if possible).
    /// So, it is suggested to use this method when you are only ensuring a few individual artifacts that are not stored in an iterable.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_ensure_multiple(ctx):
    ///     outputs = {}
    ///     for target, value in ctx.build(ctx.cli_args.target).items():
    ///     outputs.update({target.raw_target(): ctx.output.ensure_multiple(value.artifacts())})
    ///     ctx.output.print_json(outputs)
    /// ```
    fn ensure_multiple<'v>(
        this: &'v OutputStream<'v>,
        artifacts: Value<'v>,
        heap: &'v Heap,
    ) -> anyhow::Result<Value<'v>> {
        if artifacts.is_none() {
            Ok(heap.alloc(Vec::<EnsuredArtifact>::new()))
        } else if let Some(list) = <&ListRef>::unpack_value(artifacts) {
            let artifacts: Vec<EnsuredArtifact> = list.content().try_map(|artifact| {
                let artifact = EnsuredArtifact::new(*artifact)?;

                populate_ensured_artifacts(
                    this,
                    EnsuredArtifactOrGroup::Artifact(artifact.clone()),
                )?;

                Ok::<EnsuredArtifact, anyhow::Error>(artifact)
            })?;

            Ok(heap.alloc(artifacts))
        } else if let Some(artifact_gen) =
            <&StarlarkProvidersArtifactIterable>::unpack_value(artifacts)
        {
            Ok(heap.alloc(get_artifacts_from_bxl_build_result(
                artifact_gen
                    .0
                    .downcast_ref::<StarlarkBxlBuildResult>()
                    .unwrap(),
                this,
            )?))
        } else if let Some(bxl_build_result) = <&StarlarkBxlBuildResult>::unpack_value(artifacts) {
            Ok(heap.alloc(get_artifacts_from_bxl_build_result(bxl_build_result, this)?))
        } else if let Some(build_result_dict) = <DictRef>::unpack_value(artifacts) {
            Ok(heap.alloc(Dict::new(
                build_result_dict
                    .iter()
                    .map(|(label, value)| {
                        if let Some(bxl_build_result) =
                            <&StarlarkBxlBuildResult>::unpack_value(value)
                        {
                            Ok((
                                label.get_hashed()?,
                                heap.alloc(get_artifacts_from_bxl_build_result(
                                    bxl_build_result,
                                    this,
                                )?),
                            ))
                        } else {
                            Err(anyhow::anyhow!(incorrect_parameter_type_error(artifacts)))
                        }
                    })
                    .collect::<anyhow::Result<_>>()?,
            )))
        } else if let Some(cmd_line) = artifacts.as_command_line() {
            let inputs = get_cmd_line_inputs(cmd_line)?;
            let mut result = Vec::new();

            for artifact_group in &inputs.inputs {
                populate_ensured_artifacts(
                    this,
                    EnsuredArtifactOrGroup::ArtifactGroup(artifact_group.dupe()),
                )?;
                result.push(artifact_group.dupe());
            }

            Ok(heap.alloc(EnsuredArtifactGroup::new(result, false, heap)))
        } else {
            Err(anyhow::anyhow!(incorrect_parameter_type_error(artifacts)))
        }
    }
}

pub(crate) fn get_cmd_line_inputs<'v>(
    cmd_line: &'v dyn CommandLineArgLike,
) -> anyhow::Result<StarlarkCommandLineInputs> {
    let mut visitor = SimpleCommandLineArtifactVisitor::new();
    cmd_line.visit_artifacts(&mut visitor)?;
    let inputs = StarlarkCommandLineInputs {
        inputs: visitor.inputs,
    };
    Ok(inputs)
}

pub(crate) fn get_artifact_path_display(
    artifact_path: ArtifactPath,
    abs: bool,
    project_fs: &ProjectRoot,
    artifact_fs: &ArtifactFs,
) -> anyhow::Result<String> {
    let resolved = artifact_path.resolve(artifact_fs)?;
    Ok(if abs {
        project_fs.resolve(&resolved).to_string()
    } else {
        resolved.to_string()
    })
}

fn incorrect_parameter_type_error(artifacts: Value) -> ValueError {
    ValueError::IncorrectParameterTypeWithExpected(
        "list of artifacts, bxl_built_artifacts_iterable, or command-line-arg-like".to_owned(),
        artifacts.get_type().to_owned(),
    )
}

fn populate_ensured_artifacts(
    output_stream: &OutputStream,
    ensured: EnsuredArtifactOrGroup,
) -> anyhow::Result<()> {
    output_stream
        .artifacts_to_ensure
        .borrow_mut()
        .as_mut()
        .expect("should not have been taken")
        .insert(ensured);
    Ok(())
}

fn get_artifacts_from_bxl_build_result(
    bxl_build_result: &StarlarkBxlBuildResult,
    output_stream: &OutputStream,
) -> anyhow::Result<Vec<EnsuredArtifact>> {
    match &bxl_build_result.0 {
        BxlBuildResult::None => Ok(Vec::new()),
        BxlBuildResult::Built(result) => result
            .outputs
            .iter()
            .filter_map(|built| {
                built.as_ref().ok().map(|artifacts| {
                    artifacts
                        .values
                        .iter()
                        .map(|(artifact, _)| EnsuredArtifact::Artifact {
                            artifact: StarlarkArtifact::new(artifact.dupe()),
                            abs: false,
                        })
                })
            })
            .flatten()
            .map(|artifact| try {
                populate_ensured_artifacts(
                    output_stream,
                    EnsuredArtifactOrGroup::Artifact(artifact.clone()),
                )?;
                artifact
            })
            .collect::<anyhow::Result<_>>(),
    }
}


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

use std::future::Future;

use buck2_common::events::HasEvents;
use buck2_data::BxlDiceInvocationEnd;
use buck2_data::BxlDiceInvocationStart;
use buck2_events::dispatch::with_dispatcher_async;
use dice::DiceComputations;
use dupe::Dupe;
use futures::future::select;
use futures::future::Either;
use more_futures::cancellable_future::CancellationObserver;
use thiserror::Error;

#[derive(Error, Debug)]
enum ViaError {
    #[error("The owning DICE evaluation has been cancelled")]
    Cancelled,
}

/// Provides a safe blocking calls to async functions for starlark that requires operations to
/// be not async.
///
/// This is not exposed to starlark but rather, used by operations exposed to starlark to run
/// code.
/// This also provides a handle for dice.
#[derive(Clone, Dupe)]
pub struct BxlSafeDiceComputations<'a>(pub(crate) &'a DiceComputations, CancellationObserver);

impl<'a> BxlSafeDiceComputations<'a> {
    pub fn new(dice: &'a DiceComputations, cancellation: CancellationObserver) -> Self {
        Self(dice, cancellation)
    }

    /// runs the async computation over dice as sync
    pub fn via_dice<Fut, T>(&self, f: impl FnOnce(&'a DiceComputations) -> Fut) -> anyhow::Result<T>
    where
        Fut: Future<Output = anyhow::Result<T>>,
    {
        self.via(|| f(self.0))
    }

    /// runs any async computation
    pub fn via<Fut, T>(&self, f: impl FnOnce() -> Fut) -> anyhow::Result<T>
    where
        Fut: Future<Output = anyhow::Result<T>>,
    {
        let dispatcher = self.0.per_transaction_data().get_dispatcher().dupe();

        dispatcher.span(BxlDiceInvocationStart {}, || {
            let fut = with_dispatcher_async(dispatcher.clone(), f());
            let fut = async move {
                futures::pin_mut!(fut);

                match select(fut, self.1.dupe()).await {
                    Either::Left((res, _)) => res,
                    Either::Right(((), _)) => Err(ViaError::Cancelled.into()),
                }
            };

            (
                tokio::runtime::Handle::current().block_on(fut),
                BxlDiceInvocationEnd {},
            )
        })
    }
}


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

use std::sync::Arc;

use allocative::Allocative;
use buck2_build_api::actions::RegisteredAction;
use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_interpreter::types::target_label::StarlarkConfiguredTargetLabel;
use derive_more::Display;
use dupe::Dupe;
use starlark::any::ProvidesStaticType;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::starlark_module;
use starlark::starlark_simple_value;
use starlark::values::starlark_value;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::UnpackValue;
use starlark::values::ValueLike;
use starlark::StarlarkDocs;

#[derive(Debug, Display, ProvidesStaticType, Allocative, StarlarkDocs)]
#[derive(NoSerialize)]
#[display(fmt = "{}", "self.0")]
#[starlark_docs(directory = "bxl")]
pub struct StarlarkAction(pub Arc<RegisteredAction>);

starlark_simple_value!(StarlarkAction);

#[starlark_value(type = "action")]
impl<'v> StarlarkValue<'v> for StarlarkAction {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(action_methods)
    }
}

impl<'a> UnpackValue<'a> for StarlarkAction {
    fn expected() -> String {
        "action".to_owned()
    }

    fn unpack_value(value: starlark::values::Value<'a>) -> Option<Self> {
        value
            .downcast_ref::<Self>()
            .map(|value| Self(value.0.dupe()))
    }
}

/// Methods for an action.
#[starlark_module]
fn action_methods(builder: &mut MethodsBuilder) {
    /// Gets the owning configured target label for an action.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_action(ctx):
    ///     action = ctx.audit().output("buck-out/path/to/__target__/artifact", "your_target_platform")
    ///     ctx.output.print(action.owner())
    /// ```
    fn owner<'v>(this: StarlarkAction) -> anyhow::Result<StarlarkConfiguredTargetLabel> {
        match this.0.owner() {
            BaseDeferredKey::TargetLabel(label) => {
                Ok(StarlarkConfiguredTargetLabel::new(label.dupe()))
            }
            _ => Err(anyhow::anyhow!("BXL and anon targets not supported.")),
        }
    }
}


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

use std::borrow::Cow;
use std::fmt;
use std::fmt::Display;
use std::path::Path;

use allocative::Allocative;
use anyhow::Context;
use buck2_analysis::analysis::calculation::get_dep_analysis;
use buck2_analysis::analysis::calculation::resolve_queries;
use buck2_analysis::analysis::env::get_deps_from_analysis_results;
use buck2_analysis::analysis::env::RuleAnalysisAttrResolutionContext;
use buck2_analysis::attrs::resolve::configured_attr::ConfiguredAttrExt;
use buck2_artifact::artifact::artifact_type::Artifact;
use buck2_artifact::artifact::source_artifact::SourceArtifact;
use buck2_build_api::analysis::AnalysisResult;
use buck2_build_api::interpreter::rule_defs::artifact::StarlarkArtifact;
use buck2_common::dice::cells::HasCellResolver;
use buck2_common::dice::data::HasIoProvider;
use buck2_core::buck_path::path::BuckPathRef;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::fs::paths::abs_path::AbsPath;
use buck2_core::fs::project_rel_path::ProjectRelativePath;
use buck2_core::package::PackageLabel;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_interpreter::types::target_label::StarlarkConfiguredTargetLabel;
use buck2_node::attrs::configured_attr::ConfiguredAttr;
use buck2_node::attrs::configured_traversal::ConfiguredAttrTraversal;
use buck2_node::attrs::display::AttrDisplayWithContext;
use buck2_node::attrs::fmt_context::AttrFmtContext;
use buck2_node::attrs::inspect_options::AttrInspectOptions;
use buck2_node::attrs::serialize::AttrSerializeWithContext;
use buck2_node::nodes::configured::ConfiguredTargetNode;
use derivative::Derivative;
use derive_more::Display;
use dupe::Dupe;
use once_cell::sync::OnceCell;
use serde::Serialize;
use serde::Serializer;
use starlark::any::ProvidesStaticType;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::environment::Module;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::starlark_simple_value;
use starlark::values::starlark_value;
use starlark::values::structs::AllocStruct;
use starlark::values::AllocValue;
use starlark::values::Heap;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::UnpackValue;
use starlark::values::Value;
use starlark::values::ValueLike;
use starlark::StarlarkDocs;

use crate::bxl::starlark_defs::context::BxlContext;
use crate::bxl::starlark_defs::nodes::configured::attr_resolution_ctx::LazyAttrResolutionContext;

mod attr_resolution_ctx;

#[derive(Debug, Display, ProvidesStaticType, StarlarkDocs, Allocative)]
#[derive(NoSerialize)] // TODO probably should be serializable the same as how queries serialize
#[display(fmt = "configured_target_node(name = {}, ...)", "self.0.label()")]
#[starlark_docs(directory = "bxl")]
pub struct StarlarkConfiguredTargetNode(pub ConfiguredTargetNode);

starlark_simple_value!(StarlarkConfiguredTargetNode);

#[starlark_value(type = "target_node")]
impl<'v> StarlarkValue<'v> for StarlarkConfiguredTargetNode {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(configured_target_node_value_methods)
    }
}

impl<'a> UnpackValue<'a> for StarlarkConfiguredTargetNode {
    fn expected() -> String {
        "target node".to_owned()
    }

    fn unpack_value(value: starlark::values::Value<'a>) -> Option<Self> {
        value
            .downcast_ref::<Self>()
            .map(|value| Self(value.0.dupe()))
    }
}

#[starlark_module]
fn configured_target_node_value_methods(builder: &mut MethodsBuilder) {
    /// Gets the configured target label of this target node.
    ///
    /// Note that you cannot get a non-configured label from a configured target node because the
    /// configured target node is not uniquely identified a non-configured label, only by the configured target label.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_label(ctx):
    ///     node = ctx.configured_targets("my_cell//bin:the_binary")
    ///     ctx.output.print(node.label)
    /// ```
    #[starlark(attribute)]
    fn label(this: &StarlarkConfiguredTargetNode) -> anyhow::Result<StarlarkConfiguredTargetLabel> {
        Ok(StarlarkConfiguredTargetLabel::new(this.0.label().dupe()))
    }

    /// Returns a struct of all the attributes of this target node. The structs fields are the
    /// attributes names, and the values are [`StarlarkConfiguredValue`].
    ///
    /// If you need to access many or all attrs on the same node, then this is the preferred way. Otherwise,
    /// using `attrs_lazy()` would be a better option for only accessing only a few attrs, although this really
    /// depends on what kind of attrs are on the node. Benchmarking performance will give you the best
    /// indication on which method to use.
    ///
    /// You should store the result of this function call for further usage in the code rather than calling
    /// `attrs_eager()` each time you need to access the attrs.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_attrs_eager(ctx):
    ///     node = ctx.cquery().owner("cell//path/to/TARGETS")[0]
    ///     attrs = node.attrs_eager() # cache once
    ///     ctx.output.print(attrs)
    ///     # do more stuff with attrs
    /// ```
    fn attrs_eager<'v>(
        this: &StarlarkConfiguredTargetNode,
        heap: &'v Heap,
    ) -> anyhow::Result<Value<'v>> {
        let attrs_iter = this.0.attrs(AttrInspectOptions::All);
        let attrs = attrs_iter.map(|a| {
            (
                a.name,
                StarlarkConfiguredValue(a.value, this.0.label().pkg().dupe()),
            )
        });

        Ok(heap.alloc(AllocStruct(attrs)))
    }

    /// Gets a `StarlarkLazyAttrs` for getting attrs lazily. Returns a `StarlarkLazyAttrs` object
    /// that you can call `get()` on that gets an attr one at a time.
    ///
    /// If you need to access only few attrs on the same node, then this is the preferred way. Otherwise,
    /// using `attrs_eager()` would be a better option for accessing many or all attrs, although this really
    /// depends on what kind of attrs are on the node. Benchmarking performance will give you the best
    /// indication on which method to use.
    ///
    /// You should store the result of this function call for further usage in the code rather than calling
    /// `attrs_lazy()` each time to get the `StarlarkLazyAttrs` object. Note that if the `get()` is `NoneType`,
    /// then any methods called on `NoneType` will result in an error.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_attrs_lazy(ctx):
    ///     node = ctx.cquery().owner("cell//path/to/TARGETS")[0]
    ///     attrs = node.attrs_lazy() # cache once
    ///     ctx.output.print(attrs.get("some_attributes").value())
    ///     ctx.output.print(attrs.get("some_attribute").label)
    /// ```
    fn attrs_lazy<'v>(
        this: &'v StarlarkConfiguredTargetNode,
    ) -> anyhow::Result<StarlarkLazyAttrs<'v>> {
        Ok(StarlarkLazyAttrs::new(this))
    }

    /// Gets a `StarlarkLazyResolvedAttrs` for getting resolved attrs lazily. Returns a `StarlarkLazyResolvedAttrs` object
    /// that you can call `get()` on that gets a resolved attr one at a time.
    ///
    /// If you need to access only few resolved attrs on the same node, then this is the preferred way. Otherwise,
    /// using `resolved_attrs_eager()` would be a better option for accessing many or all resolved attrs, although this really
    /// depends on what kind of resolved attrs are on the node. Benchmarking performance will give you the best
    /// indication on which method to use.
    ///
    /// You should store the result of this function call for further usage in the code rather than calling
    /// `resolved_attrs_lazy()` each time to get the `StarlarkResolvedLazyAttrs` object. Note that if the `get()` is `NoneType`,
    /// then any methods called on `NoneType` will result in an error.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_resolved_attrs_lazy(ctx):
    ///     node = ctx.cquery().owner("cell//path/to/TARGETS")[0]
    ///     attrs = node.resolved_attrs_lazy() # cache once
    ///     ctx.output.print(attrs.get("some_attributes").value())
    ///     ctx.output.print(attrs.get("some_attribute").label)
    /// ```
    fn resolved_attrs_lazy<'v>(
        this: &'v StarlarkConfiguredTargetNode,
        ctx: &'v BxlContext<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<StarlarkLazyResolvedAttrs<'v>> {
        Ok(StarlarkLazyResolvedAttrs::new(this, ctx, eval.module()))
    }

    /// Returns a struct of all the resolved attributes of this target node. The structs fields are the
    /// attributes names, and the values are Starlark `[Value]`.
    ///
    /// If you need to access many or all resolved attrs on the same node, then this is the preferred way. Otherwise,
    /// using `resolved_attrs_lazy()` would be a better option for accessing only a few resolved attrs, although this really
    /// depends on what kind of resolved attrs are on the node. Benchmarking performance will give you the best
    /// indication on which method to use.
    ///
    /// You should store the result of this function call for further usage in the code rather than calling
    /// `resolved_attrs_eager()` each time you need all the resolved attrs.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_resolved_attrs_eager(ctx):
    ///     node = ctx.cquery().owner("cell//path/to/TARGETS")[0]
    ///     attrs = node.resolved_attrs_eager() # cache once
    ///     ctx.output.print(attrs)
    ///     # do more stuff with attrs
    /// ```
    fn resolved_attrs_eager<'v>(
        this: &'v StarlarkConfiguredTargetNode,
        ctx: &'v BxlContext<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        let configured_node = &this.0;

        let dep_analysis: anyhow::Result<Vec<(&ConfiguredTargetLabel, AnalysisResult)>, _> = ctx
            .async_ctx
            .via_dice(|dice_ctx| get_dep_analysis(configured_node, dice_ctx));

        let query_results = ctx
            .async_ctx
            .via_dice(|dice_ctx| resolve_queries(dice_ctx, configured_node))?;

        let resolution_ctx = RuleAnalysisAttrResolutionContext {
            module: eval.module(),
            dep_analysis_results: get_deps_from_analysis_results(dep_analysis?)?,
            query_results,
            execution_platform_resolution: configured_node.execution_platform_resolution().clone(),
        };

        let attrs_iter = this.0.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(this.0.label().pkg(), &resolution_ctx)?,
            ));
        }

        Ok(eval.heap().alloc(AllocStruct(resolved_attrs)))
    }

    /// Gets the targets' corresponding rule's name. This is the fully qualified rule name including
    /// the import path.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_rule_type(ctx):
    ///     node = ctx.configured_targets("my_cell//bin:the_binary")
    ///     ctx.output.print(node.rule_type)
    /// ```
    #[starlark(attribute)]
    fn rule_type(this: &StarlarkConfiguredTargetNode) -> anyhow::Result<String> {
        Ok(this.0.rule_type().to_string())
    }

    /// Returns a List of all the sources used by this node.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_sources(ctx):
    ///     node = ctx.configured_targets("my_cell//bin:the_binary")
    ///     ctx.output.print(node.sources())
    /// ```
    fn sources(this: &StarlarkConfiguredTargetNode) -> anyhow::Result<Vec<StarlarkArtifact>> {
        struct InputsCollector {
            inputs: Vec<StarlarkArtifact>,
        }
        impl ConfiguredAttrTraversal for InputsCollector {
            fn dep(&mut self, _dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn input(&mut self, path: BuckPathRef) -> anyhow::Result<()> {
                self.inputs
                    .push(StarlarkArtifact::new(Artifact::from(SourceArtifact::new(
                        path.to_buck_path(),
                    ))));
                Ok(())
            }
        }
        let mut traversal = InputsCollector { inputs: Vec::new() };
        for a in this.0.attrs(AttrInspectOptions::All) {
            a.traverse(this.0.label().pkg(), &mut traversal)?;
        }
        Ok(traversal.inputs)
    }

    /// Gets the `SourceArtifact` that corresponds to the given `path` given a context. The path should be the
    /// project relative path to the file, or an absolute path.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_get_source(ctx):
    ///     owner = ctx.cquery().owner("project/relative/path/to/file")[0]
    ///     artifact = owner.sources()[0]
    ///     ctx.output.print(artifact)
    /// ```
    fn get_source(
        this: &StarlarkConfiguredTargetNode,
        path: &str,
        ctx: &BxlContext,
    ) -> anyhow::Result<Option<StarlarkArtifact>> {
        let path = Path::new(path);
        let fs = ctx
            .async_ctx
            .0
            .global_data()
            .get_io_provider()
            .project_root()
            .dupe();
        let path = if path.is_absolute() {
            Cow::Owned(
                fs.relativize_any(AbsPath::new(path)?)
                    .context("Given path does not belong to the project root")?,
            )
        } else {
            Cow::Borrowed(ProjectRelativePath::new(path).context(
                "Given path should either be absolute or a forward pointing project relative path",
            )?)
        };

        let cell_path = ctx
            .async_ctx
            .via_dice(async move |ctx| ctx.get_cell_resolver().await?.get_cell_path(&path))?;

        struct SourceFinder {
            found: Option<StarlarkArtifact>,
            target: CellPath,
        }
        impl ConfiguredAttrTraversal for SourceFinder {
            fn dep(&mut self, _dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn input(&mut self, path: BuckPathRef) -> anyhow::Result<()> {
                if path.to_cell_path() == self.target {
                    self.found = Some(StarlarkArtifact::new(Artifact::from(SourceArtifact::new(
                        path.to_buck_path(),
                    ))));
                }
                Ok(())
            }
        }
        let mut traversal = SourceFinder {
            found: None,
            target: cell_path,
        };
        for a in this.0.attrs(AttrInspectOptions::All) {
            a.traverse(this.0.label().pkg(), &mut traversal)?;

            if let Some(found) = traversal.found {
                return Ok(Some(found));
            }
        }
        Ok(None)
    }
}

#[derive(Debug, Clone, ProvidesStaticType, StarlarkDocs, Allocative)]
#[repr(C)]
#[starlark_docs(directory = "bxl")]
pub struct StarlarkConfiguredValue(ConfiguredAttr, PackageLabel);

impl Display for StarlarkConfiguredValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(
            &AttrFmtContext {
                package: Some(self.1.dupe()),
            },
            f,
        )
    }
}

impl Serialize for StarlarkConfiguredValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.0.serialize_with_ctx(
            &AttrFmtContext {
                package: Some(self.1.dupe()),
            },
            serializer,
        )
    }
}

starlark_simple_value!(StarlarkConfiguredValue);

#[starlark_value(type = "configured_attr_val")]
impl<'v> StarlarkValue<'v> for StarlarkConfiguredValue {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(configured_value_methods)
    }
}

/// Methods on configured target node's attributes.
#[starlark_module]
fn configured_value_methods(builder: &mut MethodsBuilder) {
    /// Returns the type name of the attribute
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_type(ctx):
    ///     node = ctx.cquery().owner("bin/TARGETS")[0]
    ///     attrs = node.attrs_eager()
    ///     ctx.output.print(attrs.name.type)
    /// ```
    #[starlark(attribute)]
    fn r#type<'v>(this: &StarlarkConfiguredValue) -> anyhow::Result<&'v str> {
        this.0.starlark_type()
    }

    /// Returns the value of this attribute. The value here is not fully resolved like in rules.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_value(ctx):
    ///     node = ctx.cquery().owner("bin/TARGETS")[0]
    ///     attrs = node.attrs_eager()
    ///     ctx.output.print(attrs.name.value())
    /// ```
    fn value<'v>(this: &StarlarkConfiguredValue, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        this.0.to_value(this.1.dupe(), heap)
    }
}

/// The context for getting attrs lazily on a `StarlarkConfiguredTargetNode`.
#[derive(
    ProvidesStaticType,
    Derivative,
    Display,
    Trace,
    NoSerialize,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(directory = "bxl")]
#[derivative(Debug)]
#[display(fmt = "{:?}", self)]
pub struct StarlarkLazyAttrs<'v> {
    #[trace(unsafe_ignore)]
    #[derivative(Debug = "ignore")]
    #[allocative(skip)]
    configured_target_node: &'v StarlarkConfiguredTargetNode,
}

#[starlark_value(type = "lazy_attrs", StarlarkTypeRepr, UnpackValue)]
impl<'v> StarlarkValue<'v> for StarlarkLazyAttrs<'v> {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(lazy_attrs_methods)
    }
}

impl<'v> AllocValue<'v> for StarlarkLazyAttrs<'v> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_complex_no_freeze(self)
    }
}

impl<'v> StarlarkLazyAttrs<'v> {
    pub fn new(configured_target_node: &'v StarlarkConfiguredTargetNode) -> StarlarkLazyAttrs<'v> {
        Self {
            configured_target_node,
        }
    }
}

/// The context for getting attrs lazily on a `StarlarkConfiguredTargetNode`.
#[starlark_module]
fn lazy_attrs_methods(builder: &mut MethodsBuilder) {
    /// Gets a single attribute. Returns an optional `[StarlarkConfiguredValue]`.
    ///
    /// def _impl_attrs_lazy(ctx):
    ///     node = ctx.cquery().owner("cell//path/to/TARGETS")[0]
    ///     attrs = node.attrs_lazy() # cache once
    ///     ctx.output.print(attrs.get("some_attributes").value())
    ///     ctx.output.print(attrs.get("some_attribute").label)
    /// ```
    fn get<'v>(
        this: &StarlarkLazyAttrs<'v>,
        attr: &str,
    ) -> anyhow::Result<Option<StarlarkConfiguredValue>> {
        Ok(this
            .configured_target_node
            .0
            .get(attr, AttrInspectOptions::All)
            .map(|a| {
                StarlarkConfiguredValue(a.value, this.configured_target_node.0.label().pkg().dupe())
            }))
    }
}

#[derive(
    ProvidesStaticType,
    Derivative,
    Display,
    Trace,
    NoSerialize,
    StarlarkDocs,
    Allocative
)]
#[starlark_docs(directory = "bxl")]
#[derivative(Debug)]
#[display(fmt = "{:?}", self)]
pub struct StarlarkLazyResolvedAttrs<'v> {
    #[trace(unsafe_ignore)]
    #[derivative(Debug = "ignore")]
    #[allocative(skip)]
    configured_node: &'v ConfiguredTargetNode,
    #[trace(unsafe_ignore)]
    #[derivative(Debug = "ignore")]
    #[allocative(skip)]
    resolution_ctx: LazyAttrResolutionContext<'v>,
}

#[starlark_value(type = "lazy_resolved_attrs", StarlarkTypeRepr, UnpackValue)]
impl<'v> StarlarkValue<'v> for StarlarkLazyResolvedAttrs<'v> {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(lazy_resolved_attrs_methods)
    }
}

impl<'v> AllocValue<'v> for StarlarkLazyResolvedAttrs<'v> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc_complex_no_freeze(self)
    }
}

impl<'v> StarlarkLazyResolvedAttrs<'v> {
    pub fn new(
        configured_node: &'v StarlarkConfiguredTargetNode,
        ctx: &'v BxlContext<'v>,
        module: &'v Module,
    ) -> StarlarkLazyResolvedAttrs<'v> {
        let configured_node = &configured_node.0;
        let resolution_ctx = LazyAttrResolutionContext {
            module,
            configured_node,
            ctx,
            dep_analysis_results: OnceCell::new(),
            query_results: OnceCell::new(),
        };

        Self {
            configured_node,
            resolution_ctx,
        }
    }
}

/// The context for getting resolved attrs lazily on a `StarlarkConfiguredTargetNode`.
#[starlark_module]
fn lazy_resolved_attrs_methods(builder: &mut MethodsBuilder) {
    /// Gets a single resolved attribute. Returns an optional configured attribute.
    ///
    /// Gets a single attribute.
    ///
    /// def _impl_resolved_attrs_lazy(ctx):
    ///     node = ctx.cquery().owner("cell//path/to/TARGETS")[0]
    ///     attrs = node.resolved_attrs_lazy() # cache once
    ///     ctx.output.print(attrs.get("some_attribute").value())
    ///     ctx.output.print(attrs.get("some_attribute").label)
    /// ```
    fn get<'v>(
        this: &StarlarkLazyResolvedAttrs<'v>,
        attr: &str,
    ) -> anyhow::Result<Option<Value<'v>>> {
        Ok(
            match this.configured_node.get(attr, AttrInspectOptions::All) {
                Some(attr) => Some(
                    attr.value
                        .resolve_single(this.configured_node.label().pkg(), &this.resolution_ctx)?,
                ),
                None => None,
            },
        )
    }
}


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

use allocative::Allocative;
use buck2_interpreter::types::target_label::StarlarkTargetLabel;
use buck2_node::attrs::inspect_options::AttrInspectOptions;
use buck2_node::nodes::unconfigured::TargetNode;
use derive_more::Display;
use dupe::Dupe;
use starlark::any::ProvidesStaticType;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::starlark_module;
use starlark::starlark_simple_value;
use starlark::values::starlark_value;
use starlark::values::structs::AllocStruct;
use starlark::values::Heap;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::UnpackValue;
use starlark::values::Value;
use starlark::values::ValueLike;
use starlark::StarlarkDocs;

use crate::bxl::starlark_defs::file_set::StarlarkFileNode;
use crate::bxl::starlark_defs::nodes::unconfigured::attribute::StarlarkCoercedAttr;
use crate::bxl::starlark_defs::nodes::unconfigured::attribute::StarlarkTargetNodeCoercedAttributes;

pub mod attribute;

#[derive(Debug, Display, ProvidesStaticType, Allocative, StarlarkDocs)]
#[derive(NoSerialize)] // TODO probably should be serializable the same as how queries serialize
#[display(fmt = "{:?}", self)]
#[starlark_docs(directory = "bxl")]
pub struct StarlarkTargetNode(pub TargetNode);

starlark_simple_value!(StarlarkTargetNode);

#[starlark_value(type = "unconfigured_target_node")]
impl<'v> StarlarkValue<'v> for StarlarkTargetNode {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(target_node_value_methods)
    }
}

impl<'a> UnpackValue<'a> for StarlarkTargetNode {
    fn expected() -> String {
        "target node".to_owned()
    }

    fn unpack_value(value: starlark::values::Value<'a>) -> Option<Self> {
        value
            .downcast_ref::<Self>()
            .map(|value| Self(value.0.dupe()))
    }
}

/// Methods for unconfigured target node.
#[starlark_module]
fn target_node_value_methods(builder: &mut MethodsBuilder) {
    /// DO NOT USE. Will be deprecated.
    ///
    /// Gets the coerced attributes from the unconfigured target node. Returns an iterable `starlark_attributes`
    /// object.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_attributes(ctx):
    ///     target_node = ctx.uquery().eval("owner('path/to/file')")[0]
    ///     for attr in target_node.attributes:
    ///         ctx.output.print(attr)
    /// ```
    #[starlark(attribute)]
    fn attributes<'v>(this: StarlarkTargetNode, heap: &Heap) -> anyhow::Result<Value<'v>> {
        Ok(heap.alloc(StarlarkTargetNodeCoercedAttributes {
            inner: heap.alloc(this),
        }))
    }

    /// Gets the coerced attributes from the unconfigured target node. Returns a struct.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_attributes(ctx):
    ///     target_node = ctx.uquery().eval("owner('path/to/file')")[0]
    ///     ctx.output.print(target_node.attrs.my_attr)
    /// ```
    #[starlark(attribute)]
    fn attrs<'v>(this: StarlarkTargetNode, heap: &Heap) -> anyhow::Result<Value<'v>> {
        let attrs_iter = this.0.attrs(AttrInspectOptions::All);
        let attrs = attrs_iter.map(|a| {
            (
                a.name,
                StarlarkCoercedAttr(a.value.clone(), this.0.label().pkg().dupe()),
            )
        });

        Ok(heap.alloc(AllocStruct(attrs)))
    }

    /// Gets the label from the unconfigured target node.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_label(ctx):
    ///     target_node = ctx.uquery().eval("owner('path/to/file')")[0]
    ///     ctx.output.print(target_node.label)
    /// ```
    #[starlark(attribute)]
    fn label(this: &StarlarkTargetNode) -> anyhow::Result<StarlarkTargetLabel> {
        Ok(this.0.label().dupe().into())
    }

    /// Gets the buildfile path from the unconfigured target node.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_label(ctx):
    ///     target_node = ctx.uquery().eval("owner('path/to/file')")[0]
    ///     ctx.output.print(target_node.buildfile_path)
    /// ```
    #[starlark(attribute)]
    fn buildfile_path(this: &StarlarkTargetNode) -> anyhow::Result<StarlarkFileNode> {
        Ok(StarlarkFileNode(this.0.buildfile_path().path()))
    }

    /// Gets the fully qualified name of the rule for this unconfigured target node as a
    /// string. This includes the import path as well.
    ///
    /// Sample usage:
    /// ```text
    /// def _impl_rule_type(ctx):
    ///     target_node = ctx.uquery().owner('path/to/file')[0]
    ///     ctx.output.print(target_node.rule_type)
    /// ```
    #[starlark(attribute)]
    fn rule_type(this: &StarlarkTargetNode) -> anyhow::Result<String> {
        Ok(this.0.rule_type().to_string())
    }
}