/*
 * 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_common::executor_config::CommandExecutorConfig;
use buck2_core::configuration::compatibility::IncompatiblePlatformReason;
use buck2_core::configuration::compatibility::IncompatiblePlatformReasonCause;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::configuration::pair::ConfigurationNoExec;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_core::target::label::TargetLabel;
use dupe::Dupe;
use indent_write::indentable::Indentable;
use itertools::Itertools;
use thiserror::Error;

/// An execution platform is used for the execution deps of a target, those dependencies that
/// need to be invoked as part of a build action or otherwise need to be configured against the
/// platform that execution happens on (as opposed to the normal case where things are configured
/// against the target platform).
///
/// It consists of that platform `Configuration` and configuration of how to do the execution
/// (e.g. local, remote, etc.).
#[derive(Debug, Eq, PartialEq, Hash, Dupe, Clone, Allocative)]
pub struct ExecutionPlatform(Arc<ExecutionPlatformData>);

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub enum ExecutionPlatformData {
    /// A user-defined platform.
    Platform {
        target: TargetLabel,
        cfg: ConfigurationNoExec,
        executor_config: Arc<CommandExecutorConfig>,
    },
    /// When users haven't configured execution platforms, we will use old legacy behavior where
    /// execution deps inherit the target platform configuration.
    LegacyExecutionPlatform {
        executor_config: Arc<CommandExecutorConfig>,
        cfg: ConfigurationNoExec,
    },
}

impl ExecutionPlatform {
    pub fn platform(
        target: TargetLabel,
        cfg: ConfigurationData,
        executor_config: Arc<CommandExecutorConfig>,
    ) -> Self {
        Self(Arc::new(ExecutionPlatformData::Platform {
            target,
            cfg: ConfigurationNoExec::new(cfg),
            executor_config,
        }))
    }

    pub fn legacy_execution_platform(
        executor_config: Arc<CommandExecutorConfig>,
        cfg: ConfigurationNoExec,
    ) -> Self {
        Self(Arc::new(ExecutionPlatformData::LegacyExecutionPlatform {
            executor_config,
            cfg,
        }))
    }

    #[inline]
    pub fn cfg_pair_no_exec(&self) -> &ConfigurationNoExec {
        match &*self.0 {
            ExecutionPlatformData::Platform { cfg, .. } => cfg,
            ExecutionPlatformData::LegacyExecutionPlatform { cfg, .. } => cfg,
        }
    }

    #[inline]
    pub fn cfg(&self) -> &ConfigurationData {
        self.cfg_pair_no_exec().cfg()
    }

    pub fn id(&self) -> String {
        match &*self.0 {
            ExecutionPlatformData::Platform { target, .. } => target.to_string(),
            ExecutionPlatformData::LegacyExecutionPlatform { .. } => {
                "<legacy_global_exec_platform>".to_owned()
            }
        }
    }

    pub fn executor_config(&self) -> &Arc<CommandExecutorConfig> {
        match &*self.0 {
            ExecutionPlatformData::Platform {
                executor_config, ..
            } => executor_config,
            ExecutionPlatformData::LegacyExecutionPlatform {
                executor_config, ..
            } => executor_config,
        }
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub enum ExecutionPlatformIncompatibleReason {
    ConstraintNotSatisfied(TargetLabel),
    ExecutionDependencyIncompatible(Arc<IncompatiblePlatformReason>),
}

impl ExecutionPlatformIncompatibleReason {
    pub fn into_incompatible_platform_reason(
        self,
        target: ConfiguredTargetLabel,
    ) -> IncompatiblePlatformReason {
        match self {
            Self::ConstraintNotSatisfied(unsatisfied_config) => IncompatiblePlatformReason {
                target,
                cause: IncompatiblePlatformReasonCause::UnsatisfiedConfig(unsatisfied_config),
            },
            Self::ExecutionDependencyIncompatible(previous) => IncompatiblePlatformReason {
                target,
                cause: IncompatiblePlatformReasonCause::Dependency(previous),
            },
        }
    }
}

impl std::fmt::Display for ExecutionPlatformIncompatibleReason {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ExecutionPlatformIncompatibleReason::ConstraintNotSatisfied(v) => write!(
                f,
                "exec_compatible_with requires `{}` but it was not satisfied",
                v
            ),
            ExecutionPlatformIncompatibleReason::ExecutionDependencyIncompatible(v) => v.fmt(f),
        }
    }
}

#[derive(Debug, Error)]
pub enum ExecutionPlatformError {
    // .indented() losing the alternate flag that we want to use to format the reason so we need to explicitly do that.
    #[error("No compatible execution platform.\n{}", .0.iter().map(|(id, reason)| format!("  `{}` skipped because:\n{}", id, format!("{:#}", reason).indented("    "))).join("\n"))]
    NoCompatiblePlatform(Arc<Vec<(String, ExecutionPlatformIncompatibleReason)>>),
}

/// Represents the result of performing execution platform resolution. It stores both the
/// resolved platform (if there is one) and the reasons why higher priority platforms were
/// skipped.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Dupe, Allocative)]
pub struct ExecutionPlatformResolution {
    /// The platform if resolution found one. If all platforms are skipped, this will be `None`.
    platform: Option<ExecutionPlatform>,
    /// The human readable names of skipped platforms and the reason they were skipped.
    skipped_platforms: Arc<Vec<(String, ExecutionPlatformIncompatibleReason)>>,
}

impl ExecutionPlatformResolution {
    pub fn unspecified() -> Self {
        Self {
            platform: None,
            skipped_platforms: Arc::new(Vec::new()),
        }
    }

    pub fn new(
        platform: Option<ExecutionPlatform>,
        skipped: Vec<(String, ExecutionPlatformIncompatibleReason)>,
    ) -> Self {
        Self {
            platform,
            skipped_platforms: Arc::new(skipped),
        }
    }

    // TODO(cjhopman): Should this be an anyhow::Result and never return an invalid configuration?
    #[inline]
    pub fn cfg(&self) -> ConfigurationNoExec {
        match &self.platform {
            Some(v) => v.cfg_pair_no_exec().dupe(),
            None => ConfigurationNoExec::unspecified_exec().dupe(),
        }
    }

    pub fn platform(&self) -> anyhow::Result<&ExecutionPlatform> {
        match &self.platform {
            Some(v) => Ok(v),
            None => Err(ExecutionPlatformError::NoCompatiblePlatform(
                self.skipped_platforms.dupe(),
            )
            .into()),
        }
    }

    pub fn skipped(&self) -> &[(String, ExecutionPlatformIncompatibleReason)] {
        &self.skipped_platforms
    }

    pub fn executor_config(&self) -> anyhow::Result<&Arc<CommandExecutorConfig>> {
        Ok(self.platform()?.executor_config())
    }
}


/*
 * 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 crate::configuration::execution::ExecutionPlatform;

pub type ExecutionPlatforms = Arc<ExecutionPlatformsData>;

#[derive(Debug, Allocative)]
pub enum ExecutionPlatformFallback {
    Error,
    UseUnspecifiedExec,
    Platform(ExecutionPlatform),
}

#[derive(Debug, Allocative)]
pub struct ExecutionPlatformsData {
    platforms: Vec<ExecutionPlatform>,
    fallback: ExecutionPlatformFallback,
}

impl ExecutionPlatformsData {
    pub fn new(platforms: Vec<ExecutionPlatform>, fallback: ExecutionPlatformFallback) -> Self {
        Self {
            platforms,
            fallback,
        }
    }

    pub fn candidates(&self) -> impl Iterator<Item = &ExecutionPlatform> {
        self.platforms.iter()
    }

    pub fn fallback(&self) -> &ExecutionPlatformFallback {
        &self.fallback
    }
}


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

pub mod execution;
pub mod execution_platforms;
pub mod resolved;
pub mod target_platform_detector;
pub mod toolchain_constraints;


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

use std::hash::Hash;
use std::hash::Hasher;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::configuration::config_setting::ConfigSettingData;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::configuration::pair::ConfigurationNoExec;
use buck2_core::target::label::TargetLabel;
use buck2_util::collections::unordered_map::UnorderedMap;
use dupe::Dupe;
use starlark_map::Equivalent;

#[derive(Debug, Eq, Allocative)]
pub struct ConfigurationSettingKey(pub TargetLabel);

#[derive(Debug, Hash, Eq, PartialEq)]
pub struct ConfigurationSettingKeyRef<'a>(pub &'a TargetLabel);

impl Equivalent<ConfigurationSettingKey> for ConfigurationSettingKeyRef<'_> {
    fn equivalent(&self, key: &ConfigurationSettingKey) -> bool {
        self == &key.as_ref()
    }
}

impl ConfigurationSettingKey {
    fn as_ref(&self) -> ConfigurationSettingKeyRef {
        ConfigurationSettingKeyRef(&self.0)
    }
}

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

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

/// For a target, some of the configuration comes down the graph from its dependents, other
/// parts of the configuration (those not specified by the target platform or transitions)
/// comes from the global configuration state.
///
/// The ResolvedConfiguration represents the full configuration state that is available to
/// a target for analysis.
#[derive(Clone, Dupe, Debug, Eq, PartialEq, Hash, Allocative)]
pub struct ResolvedConfiguration(Arc<ResolvedConfigurationData>);

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
struct ResolvedConfigurationData {
    cfg: ConfigurationNoExec,
    settings: UnorderedMap<ConfigurationSettingKey, ConfigurationNode>,
}

impl ResolvedConfiguration {
    pub fn new(
        cfg: ConfigurationNoExec,
        settings: UnorderedMap<ConfigurationSettingKey, ConfigurationNode>,
    ) -> Self {
        Self(Arc::new(ResolvedConfigurationData { cfg, settings }))
    }

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

    pub fn setting_matches(&self, key: ConfigurationSettingKeyRef) -> Option<&ConfigSettingData> {
        let configuration_node = self.0.settings.get(&key).expect(
            "framework should've ensured all necessary configuration setting keys are present",
        );
        if configuration_node.matches() {
            Some(configuration_node.configuration_data())
        } else {
            None
        }
    }

    pub fn matches(&self, label: &TargetLabel) -> Option<&ConfigSettingData> {
        self.setting_matches(ConfigurationSettingKeyRef(label))
    }
}

/// A ConfigurationNode contains the information about a config_setting() or similar target in a certain configuration.
#[derive(Clone, Dupe, Debug, Eq, PartialEq, Hash, Allocative)]
pub struct ConfigurationNode(Arc<ConfigurationNodeData>);

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
struct ConfigurationNodeData {
    // This is stored as a split Configuration/TargetLabel (rather than a ConfiguredTargetLabel) because it's not
    // quite the same as what you would think of a ConfiguredTargetLabel. Importantly, we don't do analysis of the
    // target with this configuration, instead we interpret the results of the analysis of the target in the "unbound"
    // configuration within the context of this configuration.
    cfg: ConfigurationData,

    label: TargetLabel,

    config_setting: ConfigSettingData,

    /// Indicates whether this node "matches" the configuration.
    ///
    /// For example, a configuration node that requires a list of constraints "matches" a configuration where all of those constraints are satisfied.
    matches: bool,
}

impl ConfigurationNode {
    pub fn new(
        cfg: ConfigurationData,
        label: TargetLabel,
        config_setting: ConfigSettingData,
        matches: bool,
    ) -> Self {
        Self(Arc::new(ConfigurationNodeData {
            cfg,
            label,
            config_setting,
            matches,
        }))
    }

    pub fn matches(&self) -> bool {
        self.0.matches
    }

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

    pub fn configuration_data(&self) -> &ConfigSettingData {
        &self.0.config_setting
    }
}


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

//! The target platform detector spec is used to define a mapping of `package-prefix` to `target platform target`.
//! When performing "target platform resolution", if a target doesn't specify a `default_target_platform`, buck
//! will fallback to the target platform detector to determine the default target platform for that target.
//!
//! This is encoded in the buckconfig value `parser.target_platform_detector_spec` and has the format:
//!   `kind1:matcher1->platform1 kind2:matcher2->platform2 ...`
//!
//! The only supported _kind_ currently is "target". The _platform_ must be a target literal that defines a configuration (for example,
//! a `platform()` target).
//!
//! The "target" kind only supports a _matcher_ that is a recursive target pattern (ex. `cell//lib/...`).
//!
//! An example supported mapping is: `target://foo/...->//:tgt target:cell//bar/...->//:tgt2 target:cell//bar/foo/...->//:tgt`.
//! This would map `//foo:x` to platform `//:tgt`, `cell//bar:x` to `//:tgt2` and `cell//bar/foo:x` also to `//:tgt2` (the mapping
//! for `cell//bar/foo/...` has no effect because buck will pick the first matching spec).

use allocative::Allocative;
use anyhow::Context;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::name::CellName;
use buck2_core::cells::CellResolver;
use buck2_core::pattern::pattern_type::TargetPatternExtra;
use buck2_core::pattern::ParsedPattern;
use buck2_core::target::label::TargetLabel;
use thiserror::Error;

#[derive(Debug, Error)]
enum DetectorSpecParseError {
    #[error(
        "`target:` platform detector only supports a recursive pattern matcher (like `cell//package/...`) but got `{0}`"
    )]
    TargetKindRequiresRecursivePattern(String),
    #[error(
        "Error parsing target platform detector spec, expected format `<kind>:<matcher> <kind>:<matcher>`, got `{0}`"
    )]
    UnrecognizedFormat(String),
    #[error(
        "Unsupported target platform detector kind, supported kinds are <\"target\">, got `{0}`"
    )]
    UnsupportedKind(String),
}

#[derive(Debug, Eq, PartialEq, Allocative)]
pub struct TargetPlatformDetector {
    detectors: Vec<(CellPath, TargetLabel)>,
}

impl TargetPlatformDetector {
    pub fn empty() -> Self {
        Self {
            detectors: Vec::new(),
        }
    }
    pub fn parse_spec(
        spec: &str,
        cell_name: CellName,
        cell_resolver: &CellResolver,
    ) -> anyhow::Result<Self> {
        let mut detectors = Vec::new();
        for value in spec.split_whitespace() {
            match value.split_once(':') {
                Some(("target", value)) => match value.split_once("->") {
                    Some((matcher, target)) => {
                        let matcher_package =
                            match ParsedPattern::<TargetPatternExtra>::parse_precise(
                                matcher,
                                cell_name,
                                cell_resolver,
                            )? {
                                buck2_core::pattern::ParsedPattern::Recursive(root) => root,
                                _ => {
                                    return Err(
                                        DetectorSpecParseError::TargetKindRequiresRecursivePattern(
                                            matcher.to_owned(),
                                        )
                                        .into(),
                                    );
                                }
                            };
                        let target = ParsedPattern::<TargetPatternExtra>::parse_precise(
                            target,
                            cell_name,
                            cell_resolver,
                        )
                        .and_then(|x| x.as_target_label(target))
                        .context("Error parsing target platform detector spec")?;
                        detectors.push((matcher_package, target))
                    }
                    None => {
                        return Err(
                            DetectorSpecParseError::UnrecognizedFormat(spec.to_owned()).into()
                        );
                    }
                },
                Some((kind, _)) => {
                    return Err(DetectorSpecParseError::UnsupportedKind(kind.to_owned()).into());
                }
                None => {
                    return Err(DetectorSpecParseError::UnrecognizedFormat(spec.to_owned()).into());
                }
            }
        }
        Ok(TargetPlatformDetector { detectors })
    }

    pub fn detect(&self, target: &TargetLabel) -> Option<&TargetLabel> {
        for (root, platform) in &self.detectors {
            if target.pkg().cell_name() == root.cell()
                && target.pkg().cell_relative_path().starts_with(root.path())
            {
                return Some(platform);
            }
        }
        None
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use buck2_core::cells::alias::NonEmptyCellAlias;
    use buck2_core::cells::cell_root_path::CellRootPathBuf;
    use buck2_core::cells::name::CellName;

    use super::*;

    #[test]
    fn test_parse_errors() -> anyhow::Result<()> {
        let cell_resolver = CellResolver::testing_with_names_and_paths_with_alias(&[
            (
                CellName::testing_new("root"),
                CellRootPathBuf::testing_new(""),
                HashMap::from_iter([(
                    NonEmptyCellAlias::testing_new("alias1"),
                    CellName::testing_new("cell1"),
                )]),
            ),
            (
                CellName::testing_new("cell1"),
                CellRootPathBuf::testing_new("cell1"),
                HashMap::new(),
            ),
        ]);

        let check_fails = |spec: &str| {
            if TargetPlatformDetector::parse_spec(
                spec,
                CellName::testing_new("root"),
                &cell_resolver,
            )
            .is_ok()
            {
                panic!(
                    "Expected spec `{}` to fail parsing, but it succeeded.",
                    spec
                )
            }
        };

        let check_good = |spec: &str| {
            TargetPlatformDetector::parse_spec(spec, CellName::testing_new("root"), &cell_resolver)
                .unwrap_or_else(|_| panic!("Expected parsing `{}` to succeed.", spec))
        };

        check_good("target://...->//:tgt");
        check_good("target://...->//:tgt target:alias1//...->alias1//:tgt");

        // all bad
        check_fails("xxxx");
        // bad kind
        check_fails("xyz://...->//:tgt");
        // missing pattern to match
        check_fails("target:->//:tgt");
        // match pattern not recursive
        check_fails("target://some:->//:tgt");
        // bad cell alias
        check_fails("target://...->missing//:tgt");
        // target not a literal
        check_fails("target://...->//...");
        // first spec good, second spec bad
        check_fails("target://...->//:tgt xyz");

        Ok(())
    }

    #[test]
    fn test_detect() -> anyhow::Result<()> {
        let cell_resolver = CellResolver::testing_with_names_and_paths_with_alias(&[
            (
                CellName::testing_new("root"),
                CellRootPathBuf::testing_new(""),
                HashMap::from_iter([(
                    NonEmptyCellAlias::testing_new("alias1"),
                    CellName::testing_new("cell1"),
                )]),
            ),
            (
                CellName::testing_new("cell1"),
                CellRootPathBuf::testing_new("cell1"),
                HashMap::new(),
            ),
        ]);

        let detector = TargetPlatformDetector::parse_spec(
            "target://lib/...->//:p1 target://lib2/foo/...->//:p2 target:alias1//map/...->alias1//:alias",
            CellName::testing_new("root"),
            &cell_resolver,
        )?;

        let p1 = TargetLabel::testing_parse("root//:p1");
        let p2 = TargetLabel::testing_parse("root//:p2");
        let alias = TargetLabel::testing_parse("cell1//:alias");

        assert_eq!(
            detector.detect(&TargetLabel::testing_parse("root//lib/bar:xyz")),
            Some(&p1)
        );
        assert_eq!(
            detector.detect(&TargetLabel::testing_parse("root//lib:xyz")),
            Some(&p1)
        );

        assert_eq!(
            detector.detect(&TargetLabel::testing_parse("root//lib2/foo:xyz")),
            Some(&p2)
        );
        assert_eq!(
            detector.detect(&TargetLabel::testing_parse("root//lib2:foo")),
            None
        );

        assert_eq!(
            detector.detect(&TargetLabel::testing_parse("root//:lib")),
            None
        );

        assert_eq!(
            detector.detect(&TargetLabel::testing_parse("cell1//:xyz")),
            None
        );
        assert_eq!(
            detector.detect(&TargetLabel::testing_parse("cell1//map:xyz")),
            Some(&alias)
        );

        Ok(())
    }
}


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

use std::sync::Arc;

use allocative::Allocative;
use buck2_core::configuration::compatibility::IncompatiblePlatformReason;
use dupe::Dupe;
use starlark_map::small_map::SmallMap;

use crate::configuration::execution::ExecutionPlatform;

/// The constraint introduced on execution platform resolution by
/// a toolchain rule (reached via a toolchain_dep).
#[derive(Dupe, Clone, PartialEq, Eq, Allocative)]
pub struct ToolchainConstraints {
    // We know the set of execution platforms is fixed throughout the build,
    // so we can record just the ones we are incompatible with,
    // and assume all others we _are_ compatible with.
    incompatible: Arc<SmallMap<ExecutionPlatform, Arc<IncompatiblePlatformReason>>>,
}

impl ToolchainConstraints {
    pub fn new(incompatible: SmallMap<ExecutionPlatform, Arc<IncompatiblePlatformReason>>) -> Self {
        Self {
            incompatible: Arc::new(incompatible),
        }
    }

    pub fn allows(
        &self,
        exec_platform: &ExecutionPlatform,
    ) -> Result<(), Arc<IncompatiblePlatformReason>> {
        match self.incompatible.get(exec_platform) {
            None => Ok(()),
            Some(e) => Err(e.dupe()),
        }
    }
}


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

use std::fmt::Debug;
use std::fmt::Formatter;
use std::hash::Hash;
use std::hash::Hasher;
use std::iter;
use std::sync::Arc;

use allocative::Allocative;
use anyhow::Context;
use buck2_core::buck_path::path::BuckPathRef;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::bzl::ImportPath;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::configuration::pair::ConfigurationNoExec;
use buck2_core::configuration::transition::applied::TransitionApplied;
use buck2_core::configuration::transition::id::TransitionId;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::provider::label::ProvidersName;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_core::target::label::TargetLabel;
use buck2_util::arc_str::ArcStr;
use buck2_util::collections::ordered_map::OrderedMap;
use buck2_util::collections::unordered_map::UnorderedMap;
use dupe::Dupe;
use either::Either;
use once_cell::sync::Lazy;
use starlark_map::Hashed;

use crate::attrs::attr::Attribute;
use crate::attrs::attr_type::dep::DepAttr;
use crate::attrs::attr_type::dep::DepAttrTransition;
use crate::attrs::attr_type::dep::DepAttrType;
use crate::attrs::attr_type::query::ResolvedQueryLiterals;
use crate::attrs::attr_type::string::StringLiteral;
use crate::attrs::attr_type::AttrType;
use crate::attrs::coerced_attr::CoercedAttr;
use crate::attrs::coerced_attr_full::CoercedAttrFull;
use crate::attrs::configuration_context::AttrConfigurationContextImpl;
use crate::attrs::configured_attr::ConfiguredAttr;
use crate::attrs::configured_attr_full::ConfiguredAttrFull;
use crate::attrs::configured_traversal::ConfiguredAttrTraversal;
use crate::attrs::inspect_options::AttrInspectOptions;
use crate::attrs::internal::TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD;
use crate::attrs::internal::TESTS_ATTRIBUTE_FIELD;
use crate::configuration::execution::ExecutionPlatformResolution;
use crate::configuration::resolved::ResolvedConfiguration;
use crate::nodes::attributes::DEPS;
use crate::nodes::attributes::EXECUTION_PLATFORM;
use crate::nodes::attributes::ONCALL;
use crate::nodes::attributes::PACKAGE;
use crate::nodes::attributes::TARGET_CONFIGURATION;
use crate::nodes::attributes::TYPE;
use crate::nodes::unconfigured::RuleKind;
use crate::nodes::unconfigured::TargetNode;
use crate::provider_id_set::ProviderIdSet;
use crate::rule_type::RuleType;
use crate::rule_type::StarlarkRuleType;

/// ConfiguredTargetNode contains the information for a target in a particular configuration.
///
/// Most information (like attribute values) is constructed when requested and not stored
/// in the node, instead the node just stores the base TargetNode and a configuration for
/// resolving the attributes. This saves memory, but users should try avoid repeatedly
/// requesting the same information.
#[derive(Debug, Clone, Dupe, Eq, PartialEq, Hash, Allocative)]
pub struct ConfiguredTargetNode(Arc<Hashed<ConfiguredTargetNodeData>>);

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
enum TargetNodeOrForward {
    TargetNode(TargetNode),
    // Coerced attr is always a dependency.
    Forward(CoercedAttr, ConfiguredTargetNode),
}

const ACTUAL_ATTR_NAME: &str = "actual";

impl TargetNodeOrForward {
    fn attrs<'a>(
        &'a self,
        opts: AttrInspectOptions,
    ) -> impl Iterator<Item = CoercedAttrFull<'a>> + 'a {
        match self {
            TargetNodeOrForward::TargetNode(target_node) => Either::Left(target_node.attrs(opts)),
            TargetNodeOrForward::Forward(actual, _) => {
                let actual_attr = Some(CoercedAttrFull {
                    name: ACTUAL_ATTR_NAME,
                    attr: ConfiguredTargetNode::actual_attribute(),
                    value: actual,
                });
                Either::Right(actual_attr.into_iter())
            }
        }
    }

    fn rule_type(&self) -> &RuleType {
        match self {
            TargetNodeOrForward::TargetNode(target_node) => target_node.rule_type(),
            TargetNodeOrForward::Forward(..) => &RuleType::Forward,
        }
    }

    fn rule_kind(&self) -> RuleKind {
        match self {
            TargetNodeOrForward::TargetNode(x) => x.rule_kind(),
            TargetNodeOrForward::Forward(_, x) => x.rule_kind(),
        }
    }

    fn buildfile_path(&self) -> &BuildFilePath {
        match self {
            TargetNodeOrForward::TargetNode(target_node) => target_node.buildfile_path(),
            TargetNodeOrForward::Forward(_, x) => x.buildfile_path(),
        }
    }

    fn is_visible_to(&self, target: &TargetLabel) -> anyhow::Result<bool> {
        match self {
            TargetNodeOrForward::TargetNode(node) => node.is_visible_to(target),
            TargetNodeOrForward::Forward(_, forward) => forward.is_visible_to(target),
        }
    }

    fn oncall(&self) -> Option<&str> {
        match self {
            TargetNodeOrForward::TargetNode(node) => node.oncall(),
            TargetNodeOrForward::Forward(_, forward) => forward.oncall(),
        }
    }

    fn attr_or_none<'a>(
        &'a self,
        name: &str,
        opts: AttrInspectOptions,
    ) -> Option<CoercedAttrFull<'a>> {
        match self {
            TargetNodeOrForward::TargetNode(target_node) => target_node.attr_or_none(name, opts),
            TargetNodeOrForward::Forward(actual, _) => {
                if name == ACTUAL_ATTR_NAME {
                    Some(CoercedAttrFull {
                        name: ACTUAL_ATTR_NAME,
                        attr: ConfiguredTargetNode::actual_attribute(),
                        value: actual,
                    })
                } else {
                    None
                }
            }
        }
    }
}

// TODO(cjhopman): There's a lot of optimization opportunities here.
//  1. we iterate over and configure the attributes multiple times, that could be improved in a bunch of ways
//  2. we store the same resolvedconfiguration probably in a bunch of nodes, that could be made smaller or shared
//  3. deps could probably be approximated a diff against the targetnode's deps
#[derive(Eq, PartialEq, Hash, Allocative)]
struct ConfiguredTargetNodeData {
    label: ConfiguredTargetLabel,
    target_node: TargetNodeOrForward,
    resolved_configuration: ResolvedConfiguration,
    resolved_transition_configurations: OrderedMap<Arc<TransitionId>, Arc<TransitionApplied>>,
    execution_platform_resolution: ExecutionPlatformResolution,
    // Deps includes regular deps and transitioned deps,
    // but excludes exec deps or configuration deps.
    // TODO(cjhopman): Should this be a diff against the node's deps?
    deps: ConfiguredTargetNodeDeps,
    exec_deps: ConfiguredTargetNodeDeps,
    platform_cfgs: OrderedMap<TargetLabel, ConfigurationData>,
}

impl Debug for ConfiguredTargetNodeData {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ConfiguredTargetNodeData")
            .field("label", &self.label)
            .finish_non_exhaustive()
    }
}

/// The ConfiguredAttr and other derived information about it.
#[derive(Debug, Eq, PartialEq, Hash)]
struct ConfiguredNodeAttr {
    attr: ConfiguredAttr,
    has_queries: bool,
}

impl ConfiguredTargetNode {
    /// Creates a minimal ConfiguredTargetNode. Some operations may unexpectedly fail.
    pub fn testing_new(name: ConfiguredTargetLabel, rule_type: &str) -> Self {
        use crate::nodes::unconfigured::testing::TargetNodeExt;

        let rule_type = RuleType::Starlark(Arc::new(StarlarkRuleType {
            import_path: ImportPath::testing_new("cell//pkg:rules.bzl"),
            name: rule_type.to_owned(),
        }));
        let execution_platform_resolution = ExecutionPlatformResolution::new(None, Vec::new());

        Self::new(
            name.dupe(),
            TargetNode::testing_new(name.unconfigured().dupe(), rule_type, Vec::new()),
            ResolvedConfiguration::new(
                ConfigurationNoExec::new(name.cfg().dupe()),
                UnorderedMap::new(),
            ),
            OrderedMap::new(),
            execution_platform_resolution,
            Vec::new(),
            Vec::new(),
            OrderedMap::new(),
        )
    }

    pub fn new(
        name: ConfiguredTargetLabel,
        target_node: TargetNode,
        resolved_configuration: ResolvedConfiguration,
        resolved_tr_configurations: OrderedMap<Arc<TransitionId>, Arc<TransitionApplied>>,
        execution_platform_resolution: ExecutionPlatformResolution,
        deps: Vec<ConfiguredTargetNode>,
        exec_deps: Vec<ConfiguredTargetNode>,
        platform_cfgs: OrderedMap<TargetLabel, ConfigurationData>,
    ) -> Self {
        Self(Arc::new(Hashed::new(ConfiguredTargetNodeData {
            label: name,
            target_node: TargetNodeOrForward::TargetNode(target_node),
            resolved_configuration,
            resolved_transition_configurations: resolved_tr_configurations,
            execution_platform_resolution,
            deps: ConfiguredTargetNodeDeps(deps.into_boxed_slice()),
            exec_deps: ConfiguredTargetNodeDeps(exec_deps.into_boxed_slice()),
            platform_cfgs,
        })))
    }

    /// New `ConfiguredTargetNode` for a forward node for transitioned target.
    pub fn new_forward(
        // Forward node to create.
        name: ConfiguredTargetLabel,
        // The transitioned target node.
        transitioned_node: ConfiguredTargetNode,
    ) -> anyhow::Result<Self> {
        assert_eq!(
            name.unconfigured(),
            transitioned_node.label().unconfigured(),
            "Forward node can only be created for the same unconfigured node"
        );
        assert_ne!(
            &name,
            transitioned_node.label(),
            "Creating a forward node to identical target label would be a mistake"
        );

        // TODO(nga): how to obtain the correct providers name?
        let providers_name = ProvidersName::Default;
        let providers_label = ProvidersLabel::new(name.unconfigured().dupe(), providers_name);

        let configured_providers_label =
            providers_label.configure(transitioned_node.label().cfg().dupe());
        Ok(ConfiguredTargetNode(Arc::new(Hashed::new(
            ConfiguredTargetNodeData {
                label: name.dupe(),
                target_node: TargetNodeOrForward::Forward(
                    CoercedAttr::ConfiguredDep(Box::new(DepAttr {
                        attr_type: DepAttrType::new(
                            ProviderIdSet::EMPTY,
                            DepAttrTransition::Identity,
                        ),
                        label: configured_providers_label,
                    })),
                    transitioned_node.dupe(),
                ),
                // We have no attributes with selects, so resolved configurations is empty.
                resolved_configuration: ResolvedConfiguration::new(
                    name.cfg_pair().check_no_exec_cfg()?,
                    UnorderedMap::new(),
                ),
                // We have no attributes to transition, so empty map is fine.
                resolved_transition_configurations: OrderedMap::new(),
                // Set the execution platform equal to the transitioned node's execution platform
                // so we can call `buck2 test` on Forward.
                execution_platform_resolution: transitioned_node
                    .execution_platform_resolution()
                    .dupe(),
                deps: ConfiguredTargetNodeDeps(Box::new([transitioned_node])),
                exec_deps: ConfiguredTargetNodeDeps(Box::new([])),
                platform_cfgs: OrderedMap::new(),
            },
        ))))
    }

    pub(crate) fn actual_attribute() -> &'static Attribute {
        static ATTRIBUTE: Lazy<Attribute> =
            Lazy::new(|| Attribute::new(None, "", AttrType::configured_dep(ProviderIdSet::EMPTY)));
        &ATTRIBUTE
    }

    pub fn target_compatible_with(&self) -> impl Iterator<Item = anyhow::Result<TargetLabel>> + '_ {
        self.get(
            TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD,
            AttrInspectOptions::All,
        )
        .into_iter()
        .flat_map(|a| {
            Self::attr_as_target_compatible_with(a.value).map(|a| {
                a.with_context(|| format!("attribute `{}`", TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD))
            })
        })
    }

    pub fn attr_as_target_compatible_with(
        attr: ConfiguredAttr,
    ) -> impl Iterator<Item = anyhow::Result<TargetLabel>> {
        let list = match attr.try_into_list() {
            Ok(list) => list,
            Err(e) => return Either::Left(iter::once(Err(e))),
        };
        Either::Right(list.into_iter().map(|val| val.try_into_configuration_dep()))
    }

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

    /// Returns all deps for this node that we know about after processing the build file
    /// (it may be missing things like toolchain deps or other things that are determined
    /// later in the build process).
    // TODO(cjhopman): Should this include configuration deps? Should it include the configuration deps that were inspected resolving selects?
    pub fn deps(&self) -> impl Iterator<Item = &ConfiguredTargetNode> {
        self.0.deps.iter().chain(self.0.exec_deps.iter())
    }

    pub fn toolchain_deps(&self) -> impl Iterator<Item = &ConfiguredTargetNode> {
        // Since we validate that all toolchain dependencies are of kind Toolchain,
        // we can use that to filter the deps.
        self.0
            .deps
            .iter()
            .filter(|x| x.rule_kind() == RuleKind::Toolchain)
    }

    pub fn inputs(&self) -> impl Iterator<Item = CellPath> + '_ {
        struct InputsCollector {
            inputs: Vec<CellPath>,
        }
        impl ConfiguredAttrTraversal for InputsCollector {
            fn dep(&mut self, _dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn input(&mut self, path: BuckPathRef) -> anyhow::Result<()> {
                self.inputs.push(path.to_cell_path());
                Ok(())
            }
        }
        let mut traversal = InputsCollector { inputs: Vec::new() };
        for a in self.attrs(AttrInspectOptions::All) {
            a.traverse(self.label().pkg(), &mut traversal)
                .expect("inputs collector shouldn't return errors");
        }
        traversal.inputs.into_iter()
    }

    // TODO(cjhopman): switch to for_each_query?
    pub fn queries(
        &self,
    ) -> impl Iterator<Item = (String, ResolvedQueryLiterals<ConfiguredProvidersLabel>)> {
        struct Traversal {
            queries: Vec<(String, ResolvedQueryLiterals<ConfiguredProvidersLabel>)>,
        }
        let mut traversal = Traversal {
            queries: Vec::new(),
        };
        impl ConfiguredAttrTraversal for Traversal {
            fn dep(&mut self, _dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
                // ignored.
                Ok(())
            }

            fn query_macro(
                &mut self,
                query: &str,
                resolved_literals: &ResolvedQueryLiterals<ConfiguredProvidersLabel>,
            ) -> anyhow::Result<()> {
                self.queries
                    .push((query.to_owned(), resolved_literals.clone()));
                Ok(())
            }
        }
        // TODO(cjhopman): optimize for non-query attrs
        for a in self.attrs(AttrInspectOptions::All) {
            a.traverse(self.label().pkg(), &mut traversal).unwrap();
        }
        traversal.queries.into_iter()
    }

    pub fn target_deps(&self) -> impl Iterator<Item = &ConfiguredTargetNode> {
        self.0.deps.iter()
    }

    pub fn exec_deps(&self) -> impl Iterator<Item = &ConfiguredTargetNode> {
        self.0.exec_deps.iter()
    }

    /// Return the `tests` declared for this target configured in same target platform as this target.
    pub fn tests(&self) -> impl Iterator<Item = ConfiguredProvidersLabel> {
        #[derive(Default)]
        struct TestCollector {
            labels: Vec<ConfiguredProvidersLabel>,
        }

        impl ConfiguredAttrTraversal for TestCollector {
            fn dep(&mut self, _dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
                // ignored.
                Ok(())
            }

            fn label(&mut self, label: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
                self.labels.push(label.clone());
                Ok(())
            }
        }

        let mut traversal = TestCollector::default();
        if let Some(tests) = self.get(TESTS_ATTRIBUTE_FIELD, AttrInspectOptions::All) {
            tests.traverse(self.label().pkg(), &mut traversal).unwrap();
        }
        traversal.labels.into_iter()
    }

    pub fn label(&self) -> &ConfiguredTargetLabel {
        &self.0.label
    }

    pub fn rule_type(&self) -> &RuleType {
        self.0.target_node.rule_type()
    }

    pub fn rule_kind(&self) -> RuleKind {
        self.0.target_node.rule_kind()
    }

    pub fn buildfile_path(&self) -> &BuildFilePath {
        self.0.target_node.buildfile_path()
    }

    pub fn is_visible_to(&self, target: &TargetLabel) -> anyhow::Result<bool> {
        self.0.target_node.is_visible_to(target)
    }

    pub fn special_attrs(&self) -> impl Iterator<Item = (&str, ConfiguredAttr)> {
        let typ_attr = ConfiguredAttr::String(StringLiteral(self.rule_type().name().into()));
        let deps_attr = ConfiguredAttr::List(
            self.deps()
                .map(|t| {
                    ConfiguredAttr::Label(Box::new(ConfiguredProvidersLabel::new(
                        t.label().dupe(),
                        ProvidersName::Default,
                    )))
                })
                .collect(),
        );
        let package_attr = ConfiguredAttr::String(StringLiteral(ArcStr::from(
            self.buildfile_path().to_string(),
        )));
        vec![
            (TYPE, typ_attr),
            (DEPS, deps_attr),
            (PACKAGE, package_attr),
            (
                ONCALL,
                match self.oncall() {
                    None => ConfiguredAttr::None,
                    Some(x) => ConfiguredAttr::String(StringLiteral(ArcStr::from(x))),
                },
            ),
            (
                TARGET_CONFIGURATION,
                ConfiguredAttr::String(StringLiteral(ArcStr::from(self.0.label.cfg().to_string()))),
            ),
            (
                EXECUTION_PLATFORM,
                ConfiguredAttr::String(StringLiteral(
                    self.0
                        .execution_platform_resolution
                        .platform()
                        .map_or_else(|_| ArcStr::from("<NONE>"), |v| ArcStr::from(v.id())),
                )),
            ),
        ]
        .into_iter()
    }

    pub fn oncall(&self) -> Option<&str> {
        self.0.target_node.oncall()
    }

    fn attr_configuration_context(&self) -> AttrConfigurationContextImpl {
        AttrConfigurationContextImpl::new(
            &self.0.resolved_configuration,
            self.0.execution_platform_resolution.cfg(),
            &self.0.resolved_transition_configurations,
            &self.0.platform_cfgs,
        )
    }

    pub fn attrs<'a>(
        &'a self,
        opts: AttrInspectOptions,
    ) -> impl Iterator<Item = ConfiguredAttrFull<'a>> + 'a {
        self.0.target_node.attrs(opts).map(move |a| {
            a.configure(&self.attr_configuration_context())
                .expect("checked attr configuration in constructor")
        })
    }

    pub fn get<'a>(
        &'a self,
        attr: &str,
        opts: AttrInspectOptions,
    ) -> Option<ConfiguredAttrFull<'a>> {
        self.0.target_node.attr_or_none(attr, opts).map(|v| {
            v.configure(&self.attr_configuration_context())
                .expect("checked attr configuration in constructor")
        })
    }

    pub fn call_stack(&self) -> Option<String> {
        match &self.0.target_node {
            TargetNodeOrForward::TargetNode(n) => n.call_stack(),
            TargetNodeOrForward::Forward(_, n) => n.call_stack(),
        }
    }

    /// Hash the fields that impact how this target is built.
    /// Don't do any recursive hashing of the dependencies.
    /// Hashes the attributes _after_ configuration, so changing unconfigured branches that
    /// are not taken will not change the hash.
    pub fn target_hash<H: Hasher>(&self, state: &mut H) {
        self.label().hash(state);
        self.rule_type().hash(state);
        self.attrs(AttrInspectOptions::All).for_each(|x| {
            // We deliberately don't hash the attribute, as if the value being passed to analysis
            // stays the same, we don't care if the attribute that generated it changed.
            x.name.hash(state);
            x.value.hash(state);
        });
    }

    /// If this node is a forward node, return the target it forwards to.
    pub fn forward_target(&self) -> Option<&ConfiguredTargetNode> {
        match &self.0.target_node {
            TargetNodeOrForward::TargetNode(_) => None,
            TargetNodeOrForward::Forward(_, n) => Some(n),
        }
    }
}

/// The representation of the deps for a ConfiguredTargetNode. Provides the operations we require
/// (iteration, eq, and hash), but guarantees those aren't recursive of the dep nodes' data.
#[derive(Allocative)]
struct ConfiguredTargetNodeDeps(Box<[ConfiguredTargetNode]>);

impl ConfiguredTargetNodeDeps {
    fn iter(&self) -> impl ExactSizeIterator<Item = &ConfiguredTargetNode> {
        self.0.iter()
    }
}

/// We only do Arc comparisons here. The rationale is that each ConfiguredTargetNode should only
/// ever exist in one instance in the graph, so if the ptr eq doesn't match, then we don't do a
/// deep comparison.
impl PartialEq for ConfiguredTargetNodeDeps {
    fn eq(&self, other: &Self) -> bool {
        let it1 = self.iter();
        let it2 = other.iter();
        it1.len() == it2.len() && it1.zip(it2).all(|(x, y)| Arc::ptr_eq(&x.0, &y.0))
    }
}

impl Eq for ConfiguredTargetNodeDeps {}

/// This has historically only hashed the labels. This may or may not be right, because it means
/// two nodes that reference different instances of a given dependency will hash equal, even if
/// the dependency has definitely changed (e.g. because its own deps changed).
impl Hash for ConfiguredTargetNodeDeps {
    fn hash<H: Hasher>(&self, state: &mut H) {
        let it = self.0.iter();
        state.write_usize(it.len());
        for node in it {
            node.label().hash(state);
        }
    }
}


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

use async_trait::async_trait;
use buck2_core::configuration::compatibility::MaybeCompatible;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_util::late_binding::LateBinding;
use dice::DiceComputations;

use crate::nodes::configured::ConfiguredTargetNode;

#[async_trait]
pub trait ConfiguredTargetNodeCalculationImpl: Send + Sync + 'static {
    /// Returns the ConfiguredTargetNode corresponding to a ConfiguredTargetLabel.
    async fn get_configured_target_node(
        &self,
        ctx: &DiceComputations,
        target: &ConfiguredTargetLabel,
    ) -> anyhow::Result<MaybeCompatible<ConfiguredTargetNode>>;
}

pub static CONFIGURED_TARGET_NODE_CALCULATION: LateBinding<
    &'static dyn ConfiguredTargetNodeCalculationImpl,
> = LateBinding::new("CONFIGURED_TARGET_NODE_CALCULATION");

#[async_trait]
pub trait ConfiguredTargetNodeCalculation {
    /// Returns the ConfiguredTargetNode corresponding to a ConfiguredTargetLabel.
    async fn get_configured_target_node(
        &self,
        target: &ConfiguredTargetLabel,
    ) -> anyhow::Result<MaybeCompatible<ConfiguredTargetNode>>;
}

#[async_trait]
impl ConfiguredTargetNodeCalculation for DiceComputations {
    async fn get_configured_target_node(
        &self,
        target: &ConfiguredTargetLabel,
    ) -> anyhow::Result<MaybeCompatible<ConfiguredTargetNode>> {
        CONFIGURED_TARGET_NODE_CALCULATION
            .get()?
            .get_configured_target_node(self, target)
            .await
    }
}


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

use async_trait::async_trait;
use buck2_query::query::traversal::async_fast_depth_first_postorder_traversal;
use buck2_query::query::traversal::AsyncTraversalDelegate;
use buck2_query::query::traversal::ChildVisitor;
use dupe::Dupe;

use crate::nodes::configured::ConfiguredTargetNode;
use crate::nodes::configured_ref::ConfiguredGraphNodeRef;
use crate::nodes::configured_ref::ConfiguredGraphNodeRefLookup;

/// Visit nodes and all dependencies recursively.
pub async fn configured_node_visit_all_deps(
    roots: impl IntoIterator<Item = ConfiguredTargetNode>,
    // TODO(nga): visitor does not need be either `Sync` or `Send`,
    //   this is artificial limitation of `async_depth_first_postorder_traversal`.
    visitor: impl FnMut(ConfiguredTargetNode) -> anyhow::Result<()> + Send + Sync,
) -> anyhow::Result<()> {
    // To support package/recursive patterns, we hold the map by package. To support a
    // single target name having multiple instances in the universe, we map them to a list of nodes.
    struct Delegate<F> {
        visitor: F,
    }

    #[async_trait]
    impl<F: FnMut(ConfiguredTargetNode) -> anyhow::Result<()> + Sync + Send>
        AsyncTraversalDelegate<ConfiguredGraphNodeRef> for Delegate<F>
    {
        fn visit(&mut self, target: ConfiguredGraphNodeRef) -> anyhow::Result<()> {
            (self.visitor)(target.0)
        }

        async fn for_each_child(
            &mut self,
            target: &ConfiguredGraphNodeRef,
            func: &mut dyn ChildVisitor<ConfiguredGraphNodeRef>,
        ) -> anyhow::Result<()> {
            for dep in target.0.deps() {
                func.visit(ConfiguredGraphNodeRef(dep.dupe()))?;
            }
            Ok(())
        }
    }
    let mut delegate = Delegate { visitor };

    let roots = roots
        .into_iter()
        .map(|node| ConfiguredGraphNodeRef(node.dupe()))
        .collect::<Vec<_>>();
    async_fast_depth_first_postorder_traversal(&ConfiguredGraphNodeRefLookup, &roots, &mut delegate)
        .await
}


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

use std::borrow::Cow;

use allocative::Allocative;
use async_trait::async_trait;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_query::query::environment::LabeledNode;
use buck2_query::query::environment::QueryTarget;
use buck2_query::query::traversal::AsyncNodeLookup;
use buck2_query::query::traversal::NodeLookup;
use dupe::Dupe;
use ref_cast::RefCast;
use serde::Serializer;

use crate::attrs::attr_type::any_matches::AnyMatches;
use crate::attrs::configured_attr::ConfiguredAttr;
use crate::attrs::display::AttrDisplayWithContextExt;
use crate::attrs::fmt_context::AttrFmtContext;
use crate::attrs::inspect_options::AttrInspectOptions;
use crate::attrs::serialize::AttrSerializeWithContext;
use crate::nodes::configured::ConfiguredTargetNode;

/// `ConfiguredTargetNode` as both `LabeledNode` and `NodeLabel` and also `QueryTarget`.
#[derive(Debug, Dupe, Clone, RefCast, Allocative)]
#[repr(C)]
pub struct ConfiguredGraphNodeRef(pub ConfiguredTargetNode);

impl ConfiguredGraphNodeRef {
    pub fn label(&self) -> &ConfiguredTargetLabel {
        self.0.label()
    }
}

impl std::fmt::Display for ConfiguredGraphNodeRef {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.label().fmt(f)
    }
}

impl PartialOrd for ConfiguredGraphNodeRef {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        self.label().partial_cmp(other.label())
    }
}

impl Ord for ConfiguredGraphNodeRef {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.label().cmp(other.label())
    }
}

impl PartialEq for ConfiguredGraphNodeRef {
    fn eq(&self, other: &Self) -> bool {
        self.label().eq(other.label())
    }
}

impl Eq for ConfiguredGraphNodeRef {}

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

impl LabeledNode for ConfiguredGraphNodeRef {
    type NodeRef = ConfiguredGraphNodeRef;

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

impl QueryTarget for ConfiguredGraphNodeRef {
    type Attr<'a> = ConfiguredAttr;

    fn rule_type(&self) -> Cow<str> {
        Cow::Borrowed(self.0.rule_type().name())
    }

    fn buildfile_path(&self) -> &BuildFilePath {
        self.0.buildfile_path()
    }

    // TODO(cjhopman): Use existential traits to remove the Box<> once they are stabilized.
    fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(self.0.deps().map(ConfiguredGraphNodeRef::ref_cast))
    }

    fn exec_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(self.0.exec_deps().map(ConfiguredGraphNodeRef::ref_cast))
    }

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

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

    fn special_attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        mut func: F,
    ) -> Result<(), E> {
        for (name, attr) in self.0.special_attrs() {
            func(name, &attr)?;
        }
        Ok(())
    }

    fn attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        mut func: F,
    ) -> Result<(), E> {
        for a in self.0.attrs(AttrInspectOptions::All) {
            func(a.name, &a.value)?;
        }
        Ok(())
    }

    fn map_attr<R, F: FnMut(Option<&Self::Attr<'_>>) -> R>(&self, key: &str, mut func: F) -> R {
        func(
            self.0
                .get(key, AttrInspectOptions::All)
                .as_ref()
                .map(|a| &a.value),
        )
    }

    fn inputs_for_each<E, F: FnMut(CellPath) -> Result<(), E>>(
        &self,
        mut func: F,
    ) -> Result<(), E> {
        for input in self.0.inputs() {
            func(input)?;
        }
        Ok(())
    }

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

    fn attr_to_string_alternate(&self, attr: &Self::Attr<'_>) -> String {
        format!(
            "{:#}",
            attr.as_display(&AttrFmtContext {
                package: Some(self.0.label().pkg().dupe()),
            })
        )
    }

    fn attr_serialize<S: Serializer>(
        &self,
        attr: &Self::Attr<'_>,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        attr.serialize_with_ctx(
            &AttrFmtContext {
                package: Some(self.0.label().pkg().dupe()),
            },
            serializer,
        )
    }
}

/// Graph lookup implementation for `ConfiguredGraphNodeRef`.
/// The implementation is trivial because `ConfiguredGraphNodeRef` is both node ref and node.
pub struct ConfiguredGraphNodeRefLookup;

#[async_trait]
impl AsyncNodeLookup<ConfiguredGraphNodeRef> for ConfiguredGraphNodeRefLookup {
    async fn get(&self, label: &ConfiguredGraphNodeRef) -> anyhow::Result<ConfiguredGraphNodeRef> {
        Ok(label.dupe())
    }
}

impl NodeLookup<ConfiguredGraphNodeRef> for ConfiguredGraphNodeRefLookup {
    fn get(&self, label: &ConfiguredGraphNodeRef) -> anyhow::Result<ConfiguredGraphNodeRef> {
        Ok(label.dupe())
    }
}


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

use std::collections::BTreeMap;
use std::fmt::Display;
use std::fmt::Write;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::bzl::ImportPath;
use buck2_core::package::PackageLabel;
use buck2_core::pattern::pattern_type::PatternType;
use buck2_core::pattern::PackageSpec;
use buck2_core::target::label::TargetLabel;
use buck2_core::target::name::TargetName;
use buck2_core::target::name::TargetNameRef;
use dupe::Dupe;
use dupe::IterDupedExt;
use gazebo::prelude::*;
use itertools::Itertools;

use crate::nodes::targets_map::TargetsMap;
use crate::nodes::unconfigured::TargetNode;

#[derive(Debug, thiserror::Error)]
enum EvalulationResultError {
    #[error(
        "Unknown target `{target}` from package `{package}`.\n\
Did you mean one of the {num_targets} targets in {buildfile_path}?{similar_targets}"
    )]
    UnknownTarget {
        target: TargetName,
        package: PackageLabel,
        num_targets: usize,
        buildfile_path: Arc<BuildFilePath>,
        similar_targets: SuggestedSimilarTargets,
    },
    #[error("Zero missing targets (internal error)")]
    ZeroMissingTargets,
}

#[derive(Debug)]
pub struct MissingTargets {
    missing_targets: Vec<TargetLabel>,
    package: PackageLabel,
    num_targets: usize,
    buildfile_path: Arc<BuildFilePath>,
    all_target_labels: Vec<TargetLabel>,
}

impl MissingTargets {
    /// Error message emitted when missing targets are not skipped.
    pub fn into_error(mut self) -> anyhow::Error {
        if self.missing_targets.is_empty() {
            return EvalulationResultError::ZeroMissingTargets.into();
        }
        let target = self.missing_targets.swap_remove(0);
        let similar_targets = SuggestedSimilarTargets::suggest(
            target.name(),
            self.package.dupe(),
            self.all_target_labels.iter().map(|x| x.name()),
        );
        EvalulationResultError::UnknownTarget {
            target: target.name().to_owned(),
            package: self.package,
            num_targets: self.num_targets,
            buildfile_path: self.buildfile_path,
            similar_targets,
        }
        .into()
    }

    fn gen_missing_target_warning(mut missing_targets: Vec<TargetLabel>) -> String {
        missing_targets.sort_unstable();
        let (head, middle, tail): (&[TargetLabel], &str, &[TargetLabel]) =
            if missing_targets.len() > 15 {
                let head = &missing_targets[..5];
                let tail = &missing_targets[missing_targets.len() - 5..];
                (head, "...", tail)
            } else {
                (&missing_targets, "", &[])
            };
        let mut message = String::new();
        writeln!(
            message,
            "Skipped {} missing targets:",
            missing_targets.len()
        )
        .unwrap();
        for target in head {
            writeln!(message, "  {}", target).unwrap();
        }
        if !middle.is_empty() {
            writeln!(message, "  {}", middle).unwrap();
        }
        for target in tail {
            writeln!(message, "  {}", target).unwrap();
        }
        message
    }

    /// Warning message emitted when missing targets are skipped.
    pub fn missing_targets_warning(self) -> String {
        Self::gen_missing_target_warning(self.missing_targets)
    }
}

/// An EvaluationResult contains the list of targets resulting from evaluating a build file.
#[derive(Debug, Allocative)]
pub struct EvaluationResult {
    /// The buildfile path that corresponds to this result.
    /// unlike a .bzl file, a build file (BUCK, TARGETS, etc) will only be loaded in
    /// its own cell, so we don't need a full ImportPath here.
    buildfile_path: Arc<BuildFilePath>,
    imports: Vec<ImportPath>,
    targets: TargetsMap,
}

impl EvaluationResult {
    pub fn new(
        buildfile_path: Arc<BuildFilePath>,
        imports: Vec<ImportPath>,
        targets: TargetsMap,
    ) -> Self {
        Self {
            buildfile_path,
            imports,
            targets,
        }
    }

    pub fn buildfile_path(&self) -> &Arc<BuildFilePath> {
        &self.buildfile_path
    }

    pub fn package(&self) -> PackageLabel {
        self.buildfile_path.package()
    }

    pub fn targets(&self) -> &TargetsMap {
        &self.targets
    }

    pub fn imports(&self) -> &[ImportPath] {
        &self.imports
    }

    pub fn get_target<'a>(&'a self, name: &TargetNameRef) -> Option<&'a TargetNode> {
        self.targets.get(name)
    }

    pub fn resolve_target<'a>(&'a self, path: &TargetNameRef) -> anyhow::Result<&'a TargetNode> {
        self.get_target(path).ok_or_else(|| {
            EvalulationResultError::UnknownTarget {
                target: path.to_owned(),
                package: self.package().dupe(),
                num_targets: self.targets.len(),
                buildfile_path: self.buildfile_path.dupe(),
                similar_targets: SuggestedSimilarTargets::suggest(
                    path,
                    self.package().dupe(),
                    self.targets.keys(),
                ),
            }
            .into()
        })
    }

    pub fn apply_spec<T: PatternType>(
        &self,
        spec: PackageSpec<T>,
    ) -> (
        BTreeMap<(TargetName, T), TargetNode>,
        Option<MissingTargets>,
    ) {
        match spec {
            PackageSpec::All => {
                let mut label_to_node = BTreeMap::new();
                for target_info in self.targets().values() {
                    label_to_node.insert(
                        (target_info.label().name().to_owned(), T::default()),
                        target_info.dupe(),
                    );
                }
                (label_to_node, None)
            }
            PackageSpec::Targets(targets) => {
                let mut label_to_node = BTreeMap::new();
                let mut missing_targets = Vec::new();
                for (target_name, extra) in targets {
                    let node = self.get_target(target_name.as_ref());
                    match node {
                        Some(node) => {
                            label_to_node.insert((target_name, extra), node.dupe());
                        }
                        None => missing_targets
                            .push(TargetLabel::new(self.package(), target_name.as_ref())),
                    }
                }
                let missing_targets = if missing_targets.is_empty() {
                    None
                } else {
                    Some(MissingTargets {
                        missing_targets,
                        package: self.package(),
                        num_targets: self.targets.len(),
                        buildfile_path: self.buildfile_path().dupe(),
                        all_target_labels: self.targets.key_target_labels().duped().collect(),
                    })
                };
                (label_to_node, missing_targets)
            }
        }
    }
}

#[derive(Debug)]
struct SuggestedSimilarTargets {
    package: PackageLabel,
    targets: Vec<TargetName>,
}

impl SuggestedSimilarTargets {
    fn suggest<'a>(
        target: &TargetNameRef,
        package: PackageLabel,
        available_targets: impl Iterator<Item = &'a TargetNameRef>,
    ) -> Self {
        const MAX_RESULTS: usize = 10;
        const MAX_LEVENSHTEIN_DISTANCE: usize = 5;
        let targets: Vec<TargetName> = available_targets
            .map(|t| (t, strsim::levenshtein(target.as_str(), t.as_str())))
            .filter(|(t, lev)| {
                lev <= &MAX_LEVENSHTEIN_DISTANCE
                    || target.as_str().starts_with(t.as_str())
                    || t.as_str().starts_with(target.as_str())
            })
            .sorted_by_key(|(_, lev)| *lev)
            .take(MAX_RESULTS)
            .map(|(v, _lev)| v.to_owned())
            .collect();
        Self { package, targets }
    }
}

impl Display for SuggestedSimilarTargets {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if !self.targets.is_empty() {
            let targets: Vec<String> = self
                .targets
                .map(|target| format!("  {}:{}", self.package, target));
            // Add a leading newline because this is used as a suffix in TargetsError.
            // For the same reason, print nothing when self.targets is empty.
            write!(
                f,
                "\nMaybe you meant one of these similar targets?\n{}",
                targets.join("\n")
            )?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use buck2_core::target::label::TargetLabel;

    use crate::nodes::eval_result::MissingTargets;

    #[test]
    fn test_missing_targets_message() {
        let targets: Vec<TargetLabel> = (0..30)
            .map(|i| TargetLabel::testing_parse(&format!("aaa//bbb:{i:02}")))
            .collect();
        assert_eq!(
            "Skipped 1 missing targets:\n  aaa//bbb:00\n",
            MissingTargets::gen_missing_target_warning(targets[..1].to_vec()),
        );
        assert_eq!(
            "Skipped 20 missing targets:\
                \n  aaa//bbb:00\n  aaa//bbb:01\n  aaa//bbb:02\n  aaa//bbb:03\n  aaa//bbb:04\
                \n  ...\
                \n  aaa//bbb:15\n  aaa//bbb:16\n  aaa//bbb:17\n  aaa//bbb:18\n  aaa//bbb:19\
                \n",
            MissingTargets::gen_missing_target_warning(targets[..20].to_vec()),
        );

        for i in 0..targets.len() {
            // Test it does not panic.
            let _ignore = MissingTargets::gen_missing_target_warning(targets[..i].to_vec());
        }
    }
}


/*
 * 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 anyhow::Context;
use async_trait::async_trait;
use buck2_core::package::PackageLabel;
use buck2_core::target::label::TargetLabel;
use buck2_util::late_binding::LateBinding;
use dice::DiceComputations;
use dupe::Dupe;
use futures::future::BoxFuture;
use futures::FutureExt;

use crate::nodes::eval_result::EvaluationResult;
use crate::nodes::unconfigured::TargetNode;

#[async_trait]
pub trait TargetGraphCalculationImpl: Send + Sync + 'static {
    /// Like `get_interpreter_results` but doesn't cache the result on the DICE graph.
    async fn get_interpreter_results_uncached(
        &self,
        ctx: &DiceComputations,
        package: PackageLabel,
    ) -> anyhow::Result<Arc<EvaluationResult>>;

    /// Returns the full interpreter evaluation result for a Package. This consists of the full set
    /// of `TargetNode`s of interpreting that build file.
    fn get_interpreter_results<'a>(
        &self,
        ctx: &'a DiceComputations,
        package: PackageLabel,
    ) -> BoxFuture<'a, anyhow::Result<Arc<EvaluationResult>>>;
}

pub static TARGET_GRAPH_CALCULATION_IMPL: LateBinding<&'static dyn TargetGraphCalculationImpl> =
    LateBinding::new("TARGET_GRAPH_CALCULATION_IMPL");

#[async_trait]
pub trait TargetGraphCalculation {
    /// Like `get_interpreter_results` but doesn't cache the result on the DICE graph.
    async fn get_interpreter_results_uncached(
        &self,
        package: PackageLabel,
    ) -> anyhow::Result<Arc<EvaluationResult>>;

    /// Returns the full interpreter evaluation result for a Package. This consists of the full set
    /// of `TargetNode`s of interpreting that build file.
    fn get_interpreter_results(
        &self,
        package: PackageLabel,
    ) -> BoxFuture<'_, anyhow::Result<Arc<EvaluationResult>>>;

    /// For a TargetLabel, returns the TargetNode. This is really just part of the the interpreter
    /// results for the the label's package, and so this is just a utility for accessing that, it
    /// isn't separately cached.
    fn get_target_node<'a>(
        &'a self,
        target: &'a TargetLabel,
    ) -> BoxFuture<'a, anyhow::Result<TargetNode>>;
}

#[async_trait]
impl TargetGraphCalculation for DiceComputations {
    async fn get_interpreter_results_uncached(
        &self,
        package: PackageLabel,
    ) -> anyhow::Result<Arc<EvaluationResult>> {
        TARGET_GRAPH_CALCULATION_IMPL
            .get()?
            .get_interpreter_results_uncached(self, package)
            .await
    }

    fn get_interpreter_results(
        &self,
        package: PackageLabel,
    ) -> BoxFuture<'_, anyhow::Result<Arc<EvaluationResult>>> {
        TARGET_GRAPH_CALCULATION_IMPL
            .get()
            .unwrap()
            .get_interpreter_results(self, package)
    }

    fn get_target_node<'a>(
        &'a self,
        target: &'a TargetLabel,
    ) -> BoxFuture<'a, anyhow::Result<TargetNode>> {
        TARGET_GRAPH_CALCULATION_IMPL
            .get()
            .unwrap()
            .get_interpreter_results(self, target.pkg())
            .map(move |res| {
                anyhow::Ok(
                    res.with_context(|| {
                        format!(
                            "Error loading targets in package `{}` for target `{}`",
                            target.pkg(),
                            target
                        )
                    })?
                    .resolve_target(target.name())?
                    .dupe(),
                )
            })
            .boxed()
    }
}


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

use async_trait::async_trait;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_core::target::label::TargetLabel;
use buck2_query::query::traversal::AsyncNodeLookup;
use dice::DiceComputations;

use crate::nodes::configured::ConfiguredTargetNode;
use crate::nodes::configured_frontend::ConfiguredTargetNodeCalculation;
use crate::nodes::frontend::TargetGraphCalculation;
use crate::nodes::unconfigured::TargetNode;

pub struct TargetNodeLookup<'c>(pub &'c DiceComputations);

#[async_trait]
impl<'c> AsyncNodeLookup<TargetNode> for TargetNodeLookup<'c> {
    async fn get(&self, label: &TargetLabel) -> anyhow::Result<TargetNode> {
        Ok(self.0.get_target_node(label).await?)
    }
}

pub struct ConfiguredTargetNodeLookup<'c>(pub &'c DiceComputations);

#[async_trait]
impl<'c> AsyncNodeLookup<ConfiguredTargetNode> for ConfiguredTargetNodeLookup<'c> {
    async fn get(&self, label: &ConfiguredTargetLabel) -> anyhow::Result<ConfiguredTargetNode> {
        self.0
            .get_configured_target_node(label)
            .await?
            .require_compatible()
    }
}


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

pub mod configured;
pub mod configured_frontend;
pub mod configured_node_visit_all_deps;
pub mod configured_ref;
pub mod eval_result;
pub mod frontend;
pub mod lookup;
pub mod targets_map;
pub mod unconfigured;

/// Attributes on target nodes that are generated by buck, not provided by users.
pub mod attributes {
    /// The nodes that this node depends on.
    pub static DEPS: &str = "buck.deps";

    /// The oncall for this node.
    pub static ONCALL: &str = "buck.oncall";

    /// The package or the buildfile path.
    /// The package in `targets` command, but the buildfile path in `*query` commands.
    pub static PACKAGE: &str = "buck.package";

    /// A string representation of the target's rule type.
    pub static TYPE: &str = "buck.type";

    /// The target hash of this target.
    pub static TARGET_HASH: &str = "buck.target_hash";

    /// The callstack for this target.
    pub static TARGET_CALL_STACK: &str = "buck.target_call_stack";

    /// The configuration deps, deps that appear as conditions in selects.
    pub static CONFIGURATION_DEPS: &str = "buck.configuration_deps";

    /// The resolved execution platform for this node.
    pub static EXECUTION_PLATFORM: &str = "buck.execution_platform";

    /// The resolved target configuration for this node.
    pub static TARGET_CONFIGURATION: &str = "buck.target_configuration";

    /// The input source files/directories that this node uses.
    pub static INPUTS: &str = "buck.inputs";
}


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

use allocative::Allocative;
use buck2_core::target::label::TargetLabel;
use buck2_core::target::name::TargetNameRef;
use buck2_util::collections::ordered_set;
use buck2_util::collections::ordered_set::OrderedSet;
use buck2_util::indent::indent;
use dupe::Dupe;

use crate::nodes::unconfigured::TargetNode;

#[derive(Debug, thiserror::Error)]
pub enum TargetsMapRecordError {
    #[error(
        "Attempted to register target {0} twice, {}",
        Self::format_call_stack_for_registered_target_twice(_1)
    )]
    RegisteredTargetTwice(TargetLabel, Option<String>),
}

impl TargetsMapRecordError {
    fn format_call_stack_for_registered_target_twice(call_stack: &Option<String>) -> String {
        // With current macro setup, duplicate target errors occur often
        // when suffixed targets are used. When we finally fix this suffixed target setup,
        // this code can be removed.
        match call_stack {
            None => "re-run the command with `--stack` \
                    to obtain a call stack of the first registration"
                .to_owned(),
            Some(call_stack) => format!("first registered at:\n{}", indent("  ", call_stack)),
        }
    }
}

#[derive(Debug, Clone, Dupe, Allocative)]
struct NameIndexed(TargetNode);

impl PartialEq for NameIndexed {
    fn eq(&self, other: &Self) -> bool {
        self.0.label().name() == other.0.label().name()
    }
}

impl Eq for NameIndexed {}

impl Hash for NameIndexed {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.label().name().hash(state);
    }
}

impl Borrow<TargetNameRef> for NameIndexed {
    fn borrow(&self) -> &TargetNameRef {
        self.0.label().name()
    }
}

/// Map of target -> details of those targets within a build file.
#[derive(Debug, Clone, Allocative)]
pub struct TargetsMap {
    map: OrderedSet<NameIndexed>,
}

impl TargetsMap {
    #[inline]
    pub fn new() -> TargetsMap {
        TargetsMap {
            map: OrderedSet::new(),
        }
    }

    #[inline]
    pub fn get(&self, name: &TargetNameRef) -> Option<&TargetNode> {
        self.map.get(name).map(|NameIndexed(n)| n)
    }

    #[inline]
    pub fn contains_key(&self, name: &TargetNameRef) -> bool {
        self.map.contains(name)
    }

    #[inline]
    pub fn len(&self) -> usize {
        self.map.len()
    }

    #[inline]
    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&TargetNameRef, &TargetNode)> {
        self.map.iter().map(|NameIndexed(n)| (n.label().name(), n))
    }

    #[inline]
    pub fn keys(&self) -> impl ExactSizeIterator<Item = &TargetNameRef> {
        self.iter().map(|(k, _)| k)
    }

    #[inline]
    pub fn key_target_labels(&self) -> impl ExactSizeIterator<Item = &TargetLabel> {
        self.map.iter().map(|NameIndexed(n)| n.label())
    }

    #[inline]
    pub fn values(&self) -> impl ExactSizeIterator<Item = &TargetNode> {
        self.iter().map(|(_, v)| v)
    }

    #[inline]
    pub fn record(&mut self, target_node: TargetNode) -> Result<(), TargetsMapRecordError> {
        match self.map.try_insert(NameIndexed(target_node)) {
            Err(ordered_set::OccupiedError {
                value: NameIndexed(target_node),
                occupied,
            }) => Err(TargetsMapRecordError::RegisteredTargetTwice(
                target_node.label().dupe(),
                occupied.0.call_stack(),
            )),
            Ok(()) => Ok(()),
        }
    }
}

/// For tests.
impl FromIterator<TargetNode> for TargetsMap {
    fn from_iter<T: IntoIterator<Item = TargetNode>>(iter: T) -> Self {
        let mut map = TargetsMap::new();
        for target_node in iter {
            map.record(target_node).unwrap();
        }
        map
    }
}


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

use std::hash::Hash;
use std::hash::Hasher;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::buck_path::path::BuckPathRef;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::configuration::transition::id::TransitionId;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::target::label::TargetLabel;
use buck2_util::arc_str::ArcStr;
use dupe::Dupe;

use crate::attrs::attr_type::string::StringLiteral;
use crate::attrs::coerced_attr::CoercedAttr;
use crate::attrs::coerced_attr_full::CoercedAttrFull;
use crate::attrs::coerced_deps_collector::CoercedDeps;
use crate::attrs::display::AttrDisplayWithContextExt;
use crate::attrs::inspect_options::AttrInspectOptions;
use crate::attrs::internal::DEFAULT_TARGET_PLATFORM_ATTRIBUTE_FIELD;
use crate::attrs::internal::TESTS_ATTRIBUTE_FIELD;
use crate::attrs::spec::AttributeSpec;
use crate::attrs::traversal::CoercedAttrTraversal;
use crate::attrs::values::AttrValues;
use crate::call_stack::StarlarkCallStack;
use crate::nodes::attributes::CONFIGURATION_DEPS;
use crate::nodes::attributes::DEPS;
use crate::nodes::attributes::ONCALL;
use crate::nodes::attributes::PACKAGE;
use crate::nodes::attributes::TYPE;
use crate::package::Package;
use crate::rule::Rule;
use crate::rule_type::RuleType;
use crate::visibility::VisibilitySpecification;

#[derive(Debug, thiserror::Error)]
enum TargetNodeError {
    #[error("`visibility` attribute coerced incorrectly (`{0}`) (internal error)")]
    IncorrectVisibilityAttribute(String),
}

/// Describes a target including its name, type, and the values that the user provided.
/// Some information (e.g. deps) is extracted eagerly, most is in the attrs map and needs to be
/// accessed via attribute visitors.
///
/// For attributes, to avoid duplicating data across many nodes the TargetNode itself doesn't store
/// the attribute names and it doesn't store an entry for something that has a default value. All
/// that information is contained in the AttributeSpec. This means that to access an attribute we
/// need to look at both the attrs held by the TargetNode and the information in the AttributeSpec.
#[derive(Debug, Clone, Dupe, Eq, PartialEq, Hash, Allocative)]
pub struct TargetNode(pub Arc<TargetNodeData>);

/// The kind of the rule, denoting where it can be used and how.
#[derive(Debug, Copy, Clone, Dupe, Eq, PartialEq, Hash, Allocative)]
pub enum RuleKind {
    /// A normal rule with no special properties.
    Normal,
    /// A configuration rule, meaning it is usable in a configuration context.
    Configuration,
    /// A toolchain rule, meaning it is only usable as a toolchain dep.
    Toolchain,
}

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub struct TargetNodeData {
    /// Rule type for this target.
    pub rule: Arc<Rule>,

    /// Package.
    package: Arc<Package>,

    label: TargetLabel,

    /// The attribute->value mapping for this rule. It's guaranteed that if an attribute does not
    /// have a value here, it does have a default value in the AttributeSpec.
    attributes: AttrValues,

    // TODO(cjhopman): Consider removing these cached derived fields. Query definitely needs deps
    // cached, but for builds it's potentially unimportant.
    deps_cache: CoercedDeps,

    /// Call stack for the target.
    call_stack: Option<StarlarkCallStack>,
}

impl TargetNode {
    pub fn new(
        rule: Arc<Rule>,
        package: Arc<Package>,
        label: TargetLabel,
        attributes: AttrValues,
        deps_cache: CoercedDeps,
        call_stack: Option<StarlarkCallStack>,
    ) -> TargetNode {
        TargetNode(Arc::new(TargetNodeData {
            rule,
            package,
            label,
            attributes,
            deps_cache,
            call_stack,
        }))
    }

    pub fn rule_kind(&self) -> RuleKind {
        self.0.rule.rule_kind
    }

    pub fn is_configuration_rule(&self) -> bool {
        self.0.rule.rule_kind == RuleKind::Configuration
    }

    pub fn is_toolchain_rule(&self) -> bool {
        self.0.rule.rule_kind == RuleKind::Toolchain
    }

    pub fn get_default_target_platform(&self) -> Option<&TargetLabel> {
        match self.attr_or_none(
            DEFAULT_TARGET_PLATFORM_ATTRIBUTE_FIELD,
            AttrInspectOptions::All,
        ) {
            Some(v) => match v.value {
                CoercedAttr::None => None,
                CoercedAttr::Dep(t) => Some(t.target()),
                CoercedAttr::Selector(_) | CoercedAttr::Concat(_) => {
                    unreachable!("coercer verified attribute is not configurable")
                }
                _ => unreachable!("coercer verified the attribute is dep"),
            },
            None => None,
        }
    }

    pub fn rule_type(&self) -> &RuleType {
        &self.0.rule.rule_type
    }

    pub fn buildfile_path(&self) -> &BuildFilePath {
        &self.0.package.buildfile_path
    }

    fn deps_cache(&self) -> &CoercedDeps {
        &self.0.deps_cache
    }

    /// Returns all deps for this node that we know about after processing the build file
    pub fn deps(&self) -> impl Iterator<Item = &TargetLabel> {
        let deps_cache = self.deps_cache();
        deps_cache
            .deps
            .iter()
            .chain(deps_cache.transition_deps.iter().map(|(dep, _tr)| dep))
            .chain(deps_cache.exec_deps.iter())
            .chain(deps_cache.toolchain_deps.iter())
    }

    /// Deps which are to be transitioned to other configuration using transition function.
    pub fn transition_deps(&self) -> impl Iterator<Item = (&TargetLabel, &Arc<TransitionId>)> {
        self.deps_cache()
            .transition_deps
            .iter()
            .map(|x| (&x.0, &x.1))
    }

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

    pub(crate) fn special_attrs(&self) -> impl Iterator<Item = (&str, CoercedAttr)> {
        let typ_attr = CoercedAttr::String(StringLiteral(self.rule_type().name().into()));
        let deps_attr = CoercedAttr::List(
            self.deps()
                .map(|t| CoercedAttr::Label(ProvidersLabel::default_for(t.dupe())))
                .collect(),
        );
        let package_attr = CoercedAttr::String(StringLiteral(ArcStr::from(
            self.buildfile_path().to_string(),
        )));
        vec![
            (TYPE, typ_attr),
            (
                CONFIGURATION_DEPS,
                CoercedAttr::List(
                    self.get_configuration_deps()
                        .map(|t| CoercedAttr::ConfigurationDep(t.dupe()))
                        .collect(),
                ),
            ),
            (DEPS, deps_attr),
            (PACKAGE, package_attr),
            (
                ONCALL,
                match self.oncall() {
                    None => CoercedAttr::None,
                    Some(x) => CoercedAttr::String(StringLiteral(ArcStr::from(x))),
                },
            ),
        ]
        .into_iter()
    }

    pub fn oncall(&self) -> Option<&str> {
        self.0.package.oncall.as_ref().map(|x| x.as_str())
    }

    pub fn visibility(&self) -> anyhow::Result<&VisibilitySpecification> {
        match self.0.attributes.get(AttributeSpec::visibility_attr_id()) {
            Some(CoercedAttr::Visibility(v)) => Ok(v),
            Some(a) => {
                // This code is unreachable: visibility attributes are validated
                // at the coercion stage. But if we did it wrong,
                // better error with all the context than panic.
                Err(TargetNodeError::IncorrectVisibilityAttribute(
                    a.as_display_no_ctx().to_string(),
                )
                .into())
            }
            None => {
                static DEFAULT: VisibilitySpecification = VisibilitySpecification::DEFAULT;
                Ok(&DEFAULT)
            }
        }
    }

    pub fn is_visible_to(&self, target: &TargetLabel) -> anyhow::Result<bool> {
        if self.label().pkg() == target.pkg() {
            return Ok(true);
        }
        Ok(self.visibility()?.0.matches_target(target))
    }

    pub fn attrs(&self, opts: AttrInspectOptions) -> impl Iterator<Item = CoercedAttrFull> {
        self.0.rule.attributes.attrs(&self.0.attributes, opts)
    }

    pub fn platform_deps(&self) -> impl Iterator<Item = &TargetLabel> {
        self.deps_cache().platform_deps.iter()
    }

    /// Return `None` if attribute is not present or unknown.
    pub fn attr_or_none<'a>(
        &'a self,
        key: &str,
        opts: AttrInspectOptions,
    ) -> Option<CoercedAttrFull<'a>> {
        self.0
            .rule
            .attributes
            .attr_or_none(&self.0.attributes, key, opts)
    }

    /// Get attribute.
    ///
    /// * `None` if attribute is known but not set and no default.
    /// * error if attribute is unknown.
    pub fn attr(
        &self,
        key: &str,
        opts: AttrInspectOptions,
    ) -> anyhow::Result<Option<&CoercedAttr>> {
        self.0.rule.attributes.attr(&self.0.attributes, key, opts)
    }

    pub fn target_deps(&self) -> impl Iterator<Item = &TargetLabel> {
        self.deps_cache().deps.iter()
    }

    pub fn exec_deps(&self) -> impl Iterator<Item = &TargetLabel> {
        self.deps_cache().exec_deps.iter()
    }

    pub fn get_configuration_deps(&self) -> impl Iterator<Item = &TargetLabel> {
        self.deps_cache().configuration_deps.iter()
    }

    pub fn tests(&self) -> impl Iterator<Item = &ProvidersLabel> {
        #[derive(Default)]
        struct TestCollector<'a> {
            labels: Vec<&'a ProvidersLabel>,
        }

        impl<'a> CoercedAttrTraversal<'a> for TestCollector<'a> {
            fn input(&mut self, _path: BuckPathRef) -> anyhow::Result<()> {
                Ok(())
            }

            fn dep(&mut self, _dep: &'a TargetLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn exec_dep(&mut self, _dep: &'a TargetLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn toolchain_dep(&mut self, _dep: &'a TargetLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn transition_dep(
                &mut self,
                _dep: &'a TargetLabel,
                _tr: &Arc<TransitionId>,
            ) -> anyhow::Result<()> {
                Ok(())
            }

            fn platform_dep(&mut self, _dep: &'a TargetLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn split_transition_dep(
                &mut self,
                _dep: &'a TargetLabel,
                _tr: &Arc<TransitionId>,
            ) -> anyhow::Result<()> {
                Ok(())
            }

            fn configuration_dep(&mut self, _dep: &'a TargetLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn label(&mut self, label: &'a ProvidersLabel) -> anyhow::Result<()> {
                self.labels.push(label);
                Ok(())
            }
        }

        let tests = self
            .attr_or_none(TESTS_ATTRIBUTE_FIELD, AttrInspectOptions::All)
            .expect("tests is an internal attribute field and will always be present");

        let mut traversal = TestCollector::default();
        tests.traverse(self.label().pkg(), &mut traversal).unwrap();
        traversal.labels.into_iter()
    }

    pub fn inputs(&self) -> impl Iterator<Item = CellPath> + '_ {
        struct InputsCollector {
            inputs: Vec<CellPath>,
        }

        impl<'a> CoercedAttrTraversal<'a> for InputsCollector {
            fn input(&mut self, path: BuckPathRef) -> anyhow::Result<()> {
                self.inputs.push(path.to_cell_path());
                Ok(())
            }

            fn dep(&mut self, _dep: &'a TargetLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn exec_dep(&mut self, _dep: &'a TargetLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn toolchain_dep(&mut self, _dep: &'a TargetLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn platform_dep(&mut self, _dep: &'a TargetLabel) -> anyhow::Result<()> {
                Ok(())
            }

            fn transition_dep(
                &mut self,
                _dep: &'a TargetLabel,
                _tr: &Arc<TransitionId>,
            ) -> anyhow::Result<()> {
                Ok(())
            }

            fn split_transition_dep(
                &mut self,
                _dep: &'a TargetLabel,
                _tr: &Arc<TransitionId>,
            ) -> anyhow::Result<()> {
                Ok(())
            }

            fn configuration_dep(&mut self, _dep: &'a TargetLabel) -> anyhow::Result<()> {
                Ok(())
            }
        }
        let mut traversal = InputsCollector { inputs: Vec::new() };
        for a in self.attrs(AttrInspectOptions::All) {
            a.traverse(self.label().pkg(), &mut traversal)
                .expect("inputs collector shouldn't return errors");
        }

        traversal.inputs.into_iter()
    }

    pub fn call_stack(&self) -> Option<String> {
        self.0.call_stack.as_ref().map(|s| s.to_string())
    }

    /// Hash the fields that impact how this target is built.
    /// Don't do any recursive hashing of the dependencies.
    pub fn target_hash<H: Hasher>(&self, state: &mut H) {
        self.label().hash(state);
        self.rule_type().hash(state);
        self.attrs(AttrInspectOptions::All).for_each(|x| {
            // We deliberately don't hash the attribute, as if the value being passed to analysis
            // stays the same, we don't care if the attribute that generated it changed.
            x.name.hash(state);
            x.value.hash(state);
        });
    }
}

pub mod testing {
    use std::sync::Arc;

    use buck2_core::build_file_path::BuildFilePath;
    use buck2_core::fs::paths::file_name::FileNameBuf;
    use buck2_core::package::PackageLabel;
    use buck2_core::target::label::TargetLabel;
    use dupe::Dupe;
    use serde_json::map::Map;
    use serde_json::value::Value;

    use super::*;
    use crate::attrs::attr::Attribute;
    use crate::attrs::coerced_attr::CoercedAttr;
    use crate::attrs::coerced_deps_collector::CoercedDepsCollector;
    use crate::attrs::fmt_context::AttrFmtContext;
    use crate::attrs::inspect_options::AttrInspectOptions;
    use crate::attrs::spec::AttributeSpec;
    use crate::attrs::values::AttrValues;
    use crate::nodes::targets_map::TargetsMap;
    use crate::rule_type::RuleType;

    pub trait TargetNodeExt {
        fn testing_new(
            label: TargetLabel,
            rule_type: RuleType,
            attrs: Vec<(&str, Attribute, CoercedAttr)>,
        ) -> Self;
    }

    impl TargetNodeExt for TargetNode {
        fn testing_new(
            label: TargetLabel,
            rule_type: RuleType,
            attrs: Vec<(&str, Attribute, CoercedAttr)>,
        ) -> TargetNode {
            let attr_spec = AttributeSpec::testing_new(
                attrs
                    .iter()
                    .map(|(name, attr, _)| ((*name).to_owned(), attr.clone()))
                    .collect(),
            );

            let mut attributes = AttrValues::with_capacity(attrs.len() + 1);

            attributes.push_sorted(
                AttributeSpec::name_attr_id(),
                CoercedAttr::String(StringLiteral(label.name().as_str().into())),
            );

            let mut deps_cache = CoercedDepsCollector::new();

            for (name, _attr, val) in attrs.into_iter() {
                let idx = attr_spec.attribute_id_by_name(name).unwrap();
                let attr = attr_spec.attribute(name).unwrap();
                val.traverse(attr.coercer(), label.pkg(), &mut deps_cache)
                    .unwrap();
                attributes.push_sorted(idx, val);
            }

            let buildfile_path = Arc::new(BuildFilePath::new(
                label.pkg().dupe(),
                FileNameBuf::unchecked_new("BUCK"),
            ));
            TargetNode::new(
                Arc::new(Rule {
                    attributes: attr_spec,
                    rule_type,
                    rule_kind: RuleKind::Normal,
                    cfg: None,
                }),
                Arc::new(Package {
                    buildfile_path,
                    oncall: None,
                }),
                label,
                attributes,
                CoercedDeps::from(deps_cache),
                None,
            )
        }
    }

    /// Take a TargetsMap and convert it to a nice json representation. Adds in a __type__ attr
    /// for each target's values to make it clear what the rule type is. That can probably go
    /// away eventually.
    pub fn targets_to_json(
        target: &TargetsMap,
        pkg: PackageLabel,
        opts: AttrInspectOptions,
    ) -> anyhow::Result<Value> {
        let map: Map<String, Value> = target
            .iter()
            .map(|(target_name, values)| {
                let mut json_values: Map<String, Value> = values
                    .attrs(opts)
                    .map(|a| {
                        Ok((
                            a.name.to_owned(),
                            a.value.to_json(&AttrFmtContext {
                                package: Some(pkg.dupe()),
                            })?,
                        ))
                    })
                    .collect::<anyhow::Result<Map<String, Value>>>()?;
                json_values.insert(
                    "__type__".to_owned(),
                    Value::String(values.rule_type().to_string()),
                );
                Ok((target_name.to_string(), Value::from(json_values)))
            })
            .collect::<anyhow::Result<Map<String, Value>>>()?;
        Ok(Value::from(map))
    }
}