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

use buck2_core::configuration::config_setting::ConfigSettingData;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::configuration::data::ConfigurationDataData;
use buck2_core::configuration::pair::ConfigurationNoExec;
use buck2_core::configuration::pair::ConfigurationWithExec;
use buck2_core::configuration::transition::applied::TransitionApplied;
use buck2_core::configuration::transition::id::TransitionId;
use buck2_core::target::label::TargetLabel;
use buck2_util::collections::ordered_map::OrderedMap;
use dupe::Dupe;

use crate::attrs::configuration_context::AttrConfigurationContext;

pub fn configuration_ctx() -> impl AttrConfigurationContext {
    struct TestAttrConfigurationContext(ConfigurationData, ConfigurationData, ConfigSettingData);
    impl AttrConfigurationContext for TestAttrConfigurationContext {
        fn cfg(&self) -> ConfigurationNoExec {
            ConfigurationNoExec::new(self.0.dupe())
        }

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

        fn matches<'a>(&'a self, label: &TargetLabel) -> Option<&'a ConfigSettingData> {
            match label.to_string().as_ref() {
                "root//other:config" => Some(&self.2),
                _ => None,
            }
        }

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

        fn platform_cfg(&self, _label: &TargetLabel) -> anyhow::Result<ConfigurationData> {
            panic!("not used in tests")
        }

        fn resolved_transitions(&self) -> &OrderedMap<Arc<TransitionId>, Arc<TransitionApplied>> {
            panic!("not used in tests")
        }
    }

    TestAttrConfigurationContext(
        ConfigurationData::testing_new(),
        ConfigurationData::from_platform(
            "cfg_for//:testing_exec".to_owned(),
            ConfigurationDataData {
                constraints: BTreeMap::new(),
            },
        )
        .unwrap(),
        ConfigSettingData {
            constraints: BTreeMap::new(),
            buckconfigs: BTreeMap::new(),
        },
    )
}


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

use std::sync::Arc;

use buck2_core::buck_path::path::BuckPathRef;
use buck2_core::configuration::transition::id::TransitionId;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::target::label::TargetLabel;

pub trait CoercedAttrTraversal<'a> {
    fn dep(&mut self, dep: &'a TargetLabel) -> anyhow::Result<()>;
    fn exec_dep(&mut self, dep: &'a TargetLabel) -> anyhow::Result<()>;
    fn toolchain_dep(&mut self, dep: &'a TargetLabel) -> anyhow::Result<()>;
    fn transition_dep(
        &mut self,
        dep: &'a TargetLabel,
        tr: &Arc<TransitionId>,
    ) -> anyhow::Result<()>;
    fn split_transition_dep(
        &mut self,
        dep: &'a TargetLabel,
        tr: &Arc<TransitionId>,
    ) -> anyhow::Result<()>;
    fn configuration_dep(&mut self, dep: &'a TargetLabel) -> anyhow::Result<()>;
    fn platform_dep(&mut self, dep: &'a TargetLabel) -> anyhow::Result<()>;
    fn input(&mut self, input: BuckPathRef) -> anyhow::Result<()>;
    fn label(&mut self, _label: &'a ProvidersLabel) -> anyhow::Result<()> {
        Ok(())
    }
}


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

use allocative::Allocative;
use starlark_map::vec2;
use starlark_map::vec2::Vec2;

use crate::attrs::coerced_attr::CoercedAttr;
use crate::attrs::id::AttributeId;

#[derive(Debug, Eq, PartialEq, Hash, Default, Allocative)]
pub struct AttrValues {
    sorted: Vec2<AttributeId, CoercedAttr>,
}

impl AttrValues {
    pub fn with_capacity(capacity: usize) -> AttrValues {
        AttrValues {
            sorted: Vec2::with_capacity(capacity),
        }
    }

    pub(crate) fn get_by_index(&self, index: usize) -> Option<(AttributeId, &CoercedAttr)> {
        self.sorted.get(index).map(|(id, v)| (*id, v))
    }

    pub(crate) fn get(&self, id: AttributeId) -> Option<&CoercedAttr> {
        // Could use binary search here, but for small attr map like 20
        // linear search is faster.
        for (next_id, next_value) in self {
            if next_id >= &id {
                if next_id == &id {
                    return Some(next_value);
                }

                // Attributes are sorted in `self`, no need to check the rest.
                return None;
            }
        }
        None
    }

    pub fn shrink_to_fit(&mut self) {
        self.sorted.shrink_to_fit();
    }

    pub fn push_sorted(&mut self, id: AttributeId, value: CoercedAttr) {
        if let Some((last_id, _)) = self.sorted.last() {
            assert!(&id > last_id, "attributed must be sorted");
        }
        self.sorted.push(id, value)
    }
}

impl<'a> IntoIterator for &'a AttrValues {
    type Item = (&'a AttributeId, &'a CoercedAttr);
    type IntoIter = vec2::Iter<'a, AttributeId, CoercedAttr>;

    fn into_iter(self) -> Self::IntoIter {
        self.sorted.iter()
    }
}


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

use allocative::Allocative;
use buck2_util::arc_str::ArcSlice;

use crate::attrs::attr_type::list::ListLiteral;
use crate::attrs::attr_type::string::StringLiteral;
use crate::attrs::coerced_attr::CoercedAttr;

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub struct AnyAttrType;

impl AnyAttrType {
    pub fn empty_string() -> CoercedAttr {
        CoercedAttr::String(StringLiteral::default())
    }

    pub fn empty_list() -> CoercedAttr {
        CoercedAttr::List(ListLiteral(ArcSlice::new([])))
    }
}


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

/// Check if any of the values in this literal or attribute matches the predicate.
pub trait AnyMatches {
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool>;
}


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

use std::fmt::Display;

use buck2_core::buck_path::path::BuckPathRef;
use dupe::Dupe;
use either::Either;
use serde_json::to_value;

use crate::attrs::attr_type::any_matches::AnyMatches;
use crate::attrs::attr_type::dep::ExplicitConfiguredDepMaybeConfigured;
use crate::attrs::attr_type::split_transition_dep::SplitTransitionDepMaybeConfigured;
use crate::attrs::coerced_attr::CoercedAttr;
use crate::attrs::coerced_path::CoercedPath;
use crate::attrs::configured_attr::ConfiguredAttr;
use crate::attrs::fmt_context::AttrFmtContext;
use crate::attrs::json::ToJsonWithContext;

impl ToJsonWithContext for ConfiguredAttr {
    fn to_json(&self, ctx: &AttrFmtContext) -> anyhow::Result<serde_json::Value> {
        match self {
            ConfiguredAttr::Bool(v) => Ok(to_value(v)?),
            ConfiguredAttr::Int(v) => Ok(to_value(v)?),
            ConfiguredAttr::String(v) | ConfiguredAttr::EnumVariant(v) => Ok(to_value(v)?),
            ConfiguredAttr::List(list) => list.to_json(ctx),
            ConfiguredAttr::Tuple(list) => list.to_json(ctx),
            ConfiguredAttr::Dict(dict) => dict.to_json(ctx),
            ConfiguredAttr::None => Ok(serde_json::Value::Null),
            ConfiguredAttr::OneOf(box l, _) => l.to_json(ctx),
            ConfiguredAttr::Visibility(v) => Ok(v.to_json()),
            ConfiguredAttr::WithinView(v) => Ok(v.to_json()),
            ConfiguredAttr::ExplicitConfiguredDep(e) => e.to_json(),
            ConfiguredAttr::SplitTransitionDep(e) => e.to_json(),
            ConfiguredAttr::ConfigurationDep(e) => Ok(to_value(e.to_string())?),
            ConfiguredAttr::Dep(e) => Ok(to_value(e.to_string())?),
            ConfiguredAttr::SourceLabel(e) => Ok(to_value(e.to_string())?),
            ConfiguredAttr::Label(e) => Ok(to_value(e.to_string())?),
            ConfiguredAttr::Arg(e) => Ok(to_value(e.to_string())?),
            ConfiguredAttr::Query(e) => Ok(to_value(e.query())?),
            ConfiguredAttr::SourceFile(e) => Ok(to_value(source_file_display(ctx, e).to_string())?),
        }
    }
}

impl AnyMatches for ConfiguredAttr {
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        match self {
            ConfiguredAttr::String(v) | ConfiguredAttr::EnumVariant(v) => filter(v),
            ConfiguredAttr::List(vals) => vals.any_matches(filter),
            ConfiguredAttr::Tuple(vals) => vals.any_matches(filter),
            ConfiguredAttr::Dict(d) => d.any_matches(filter),
            ConfiguredAttr::None => Ok(false),
            ConfiguredAttr::Bool(b) => b.any_matches(filter),
            ConfiguredAttr::Int(i) => filter(&i.to_string()),
            ConfiguredAttr::OneOf(l, _) => l.any_matches(filter),
            ConfiguredAttr::Visibility(v) => v.any_matches(filter),
            ConfiguredAttr::WithinView(v) => v.any_matches(filter),
            ConfiguredAttr::ExplicitConfiguredDep(e) => e.any_matches(filter),
            ConfiguredAttr::SplitTransitionDep(e) => e.any_matches(filter),
            ConfiguredAttr::ConfigurationDep(e) => filter(&e.to_string()),
            ConfiguredAttr::Dep(e) => filter(&e.to_string()),
            ConfiguredAttr::SourceLabel(e) => filter(&e.to_string()),
            ConfiguredAttr::Label(e) => filter(&e.to_string()),
            ConfiguredAttr::Arg(e) => filter(&e.to_string()),
            ConfiguredAttr::Query(e) => filter(e.query()),
            ConfiguredAttr::SourceFile(e) => filter(&e.path().to_string()),
        }
    }
}

impl ToJsonWithContext for CoercedAttr {
    fn to_json(&self, ctx: &AttrFmtContext) -> anyhow::Result<serde_json::Value> {
        CoercedAttr::to_json(self, ctx)
    }
}

impl AnyMatches for CoercedAttr {
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        CoercedAttr::any_matches(self, filter)
    }
}

pub(crate) fn source_file_display<'a>(
    ctx: &'a AttrFmtContext,
    source_file: &'a CoercedPath,
) -> impl Display + 'a {
    match &ctx.package {
        Some(pkg) => Either::Left(BuckPathRef::new(pkg.dupe(), source_file.path())),
        None => {
            // This code is unreachable, but better this than panic.
            Either::Right(format!("<no package>/{}", source_file.path()))
        }
    }
}


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

use std::fmt::Debug;
use std::hash::Hash;

pub trait AttrLike: Debug + Clone + Eq + PartialEq + Hash + Send + Sync {}

impl<T: Debug + Clone + Eq + Hash + Send + Sync + PartialEq> AttrLike for T {}


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

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

use allocative::Allocative;
use dupe::Dupe;
use serde::Serialize;

use crate::attrs::attr_type::any_matches::AnyMatches;

#[derive(Debug, Eq, PartialEq, Hash, Allocative, Clone, Copy, Dupe)]
pub struct BoolAttrType;

#[derive(Debug, Clone, Copy, Dupe, Eq, PartialEq, Hash, Allocative, Serialize)]
#[serde(transparent)]
pub struct BoolLiteral(pub bool);

impl Display for BoolLiteral {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        if self.0 {
            write!(f, "True")
        } else {
            write!(f, "False")
        }
    }
}

impl AnyMatches for BoolLiteral {
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        filter(if self.0 { "True" } else { "False" })
    }
}


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

use allocative::Allocative;
use buck2_core::target::label::TargetLabel;
use dupe::Dupe;

use crate::attrs::configuration_context::AttrConfigurationContext;
use crate::attrs::configured_attr::ConfiguredAttr;

/// A configuration dep attribute accepts a target as a value. This is different from
/// a dep in that the values themselves never undergo configuration and appear as bare
/// unconfigured labels even in the configured node. While the values aren't configured,
/// the attribute still is and so selects are still resolved and other values in the
/// attribute could be configured (for example, a `attrs.dict(attrs.dep(), attrs.configuration_dep())`
/// would have the keys configured).
///
/// This is generally used for things that refer to configuration nodes (like platforms or constraints)
/// in attributes like `target_compatible_with` or `exec_compatible_with`.
///
/// They resolve to just the string form of the target and so aren't particularly useful to UDR
/// directly (they are used by the framework).
#[derive(Debug, Eq, PartialEq, Hash, Allocative, Clone, Copy, Dupe)]
pub struct ConfigurationDepAttrType;

impl ConfigurationDepAttrType {
    pub(crate) fn configure(
        _ctx: &dyn AttrConfigurationContext,
        label: &TargetLabel,
    ) -> anyhow::Result<ConfiguredAttr> {
        Ok(ConfiguredAttr::ConfigurationDep(Box::new(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 allocative::Allocative;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::target::label::TargetLabel;
use dupe::Dupe;

use crate::attrs::attr_type::dep::ExplicitConfiguredDepMaybeConfigured;
use crate::attrs::configuration_context::AttrConfigurationContext;
use crate::attrs::configured_attr::ConfiguredAttr;
use crate::attrs::traversal::CoercedAttrTraversal;
use crate::provider_id_set::ProviderIdSet;

/// Represents attrs.configured_dep()
#[derive(Debug, Eq, PartialEq, Hash, Clone, Dupe, Allocative)]
pub struct ExplicitConfiguredDepAttrType {
    pub required_providers: ProviderIdSet,
}

impl ExplicitConfiguredDepAttrType {
    pub(crate) fn configure(
        ctx: &dyn AttrConfigurationContext,
        dep_attr: &UnconfiguredExplicitConfiguredDep,
    ) -> anyhow::Result<ConfiguredAttr> {
        let configured = Self::configure_target_with_platform(ctx, dep_attr)?;
        Ok(ConfiguredAttr::ExplicitConfiguredDep(Box::new(configured)))
    }

    fn configure_target_with_platform(
        ctx: &dyn AttrConfigurationContext,
        dep_attr: &UnconfiguredExplicitConfiguredDep,
    ) -> anyhow::Result<ConfiguredExplicitConfiguredDep> {
        let configuration = ctx.platform_cfg(&dep_attr.platform)?;
        let configured_label = dep_attr.label.configure(configuration.dupe());
        Ok(ConfiguredExplicitConfiguredDep::new(
            dep_attr.attr_type.dupe(),
            configured_label,
        ))
    }
}

/// Represents the value of an `attrs.configured_dep()`
/// in its unconfigured form.
#[derive(derive_more::Display, Debug, Hash, PartialEq, Eq, Clone, Allocative)]
#[display(fmt = "({}, {})", label, platform)]
pub struct UnconfiguredExplicitConfiguredDep {
    pub attr_type: ExplicitConfiguredDepAttrType,
    pub label: ProvidersLabel,
    pub platform: TargetLabel,
}

/// Represents the value of an `attrs.configured_dep()`
/// in its configured form.
#[derive(derive_more::Display, Hash, PartialEq, Eq, Debug, Clone, Allocative)]
#[display(fmt = "{}", label)]
pub struct ConfiguredExplicitConfiguredDep {
    pub attr_type: ExplicitConfiguredDepAttrType,
    pub label: ConfiguredProvidersLabel,
}

impl ConfiguredExplicitConfiguredDep {
    pub fn new(attr_type: ExplicitConfiguredDepAttrType, label: ConfiguredProvidersLabel) -> Self {
        Self { attr_type, label }
    }
}

impl UnconfiguredExplicitConfiguredDep {
    pub fn traverse<'a>(
        &'a self,
        traversal: &mut dyn CoercedAttrTraversal<'a>,
    ) -> anyhow::Result<()> {
        traversal.dep(self.label.target())?;
        traversal.platform_dep(&self.platform)
    }
}

impl ExplicitConfiguredDepMaybeConfigured for ConfiguredExplicitConfiguredDep {
    fn to_json(&self) -> anyhow::Result<serde_json::Value> {
        Ok(serde_json::to_value(self.to_string())?)
    }

    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        filter(&self.to_string())
    }
}

impl ExplicitConfiguredDepMaybeConfigured for UnconfiguredExplicitConfiguredDep {
    fn to_json(&self) -> anyhow::Result<serde_json::Value> {
        Ok(serde_json::to_value([
            self.label.to_string(),
            self.platform.to_string(),
        ])?)
    }

    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        filter(&self.to_string())
    }
}


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

use allocative::Allocative;

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub struct DefaultOnlyAttrType;


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

use std::fmt::Display;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::configuration::transition::id::TransitionId;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::provider::label::ProvidersLabelMaybeConfigured;
use dupe::Dupe;
use static_assertions::assert_eq_size;

use crate::attrs::attr_type::attr_like::AttrLike;
use crate::attrs::attr_type::configured_dep::ConfiguredExplicitConfiguredDep;
use crate::attrs::configuration_context::AttrConfigurationContext;
use crate::attrs::configured_attr::ConfiguredAttr;
use crate::attrs::configured_traversal::ConfiguredAttrTraversal;
use crate::attrs::traversal::CoercedAttrTraversal;
use crate::provider_id_set::ProviderIdSet;

// Just a placeholder for what a label should resolve to.
#[derive(Debug)]
pub struct DefaultProvider {}

/// How configuration is changed when configuring a dep.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Dupe, Allocative)]
pub enum DepAttrTransition {
    // No transition.
    Identity,
    // Transition to execution platform.
    Exec,
    // Transition to toolchain.
    Toolchain,
    // Transition dependency using given transition function.
    Transition(Arc<TransitionId>),
}

/// A dep attribute accepts a target label and will resolve to the provider collection from that label's analysis.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Dupe, Allocative)]
pub struct DepAttrType {
    /// The set of providers that are required to be available, during attr resolution we'll verify that these
    /// are present on each attribute value.
    pub required_providers: ProviderIdSet,
    pub transition: DepAttrTransition,
}

assert_eq_size!(DepAttrType, [usize; 3]);

#[derive(Clone, Debug, Eq, PartialEq, Hash, Allocative)]
pub struct DepAttr<T: ProvidersLabelMaybeConfigured + AttrLike> {
    pub attr_type: DepAttrType,
    pub label: T,
}

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

impl DepAttr<ConfiguredProvidersLabel> {
    pub(crate) fn traverse<'a>(
        &'a self,
        traversal: &mut dyn ConfiguredAttrTraversal,
    ) -> anyhow::Result<()> {
        match &self.attr_type.transition {
            DepAttrTransition::Identity => traversal.dep(&self.label),
            DepAttrTransition::Exec => traversal.exec_dep(&self.label),
            DepAttrTransition::Toolchain => traversal.toolchain_dep(&self.label),
            DepAttrTransition::Transition(..) => traversal.dep(&self.label),
        }
    }
}

impl DepAttr<ProvidersLabel> {
    pub fn traverse<'a>(
        label: &'a ProvidersLabel,
        attr_type: &DepAttrType,
        traversal: &mut dyn CoercedAttrTraversal<'a>,
    ) -> anyhow::Result<()> {
        match &attr_type.transition {
            DepAttrTransition::Identity => traversal.dep(label.target()),
            DepAttrTransition::Exec => traversal.exec_dep(label.target()),
            DepAttrTransition::Toolchain => traversal.toolchain_dep(label.target()),
            DepAttrTransition::Transition(tr) => traversal.transition_dep(label.target(), tr),
        }
    }
}

impl DepAttrType {
    pub fn new(required_providers: ProviderIdSet, transition: DepAttrTransition) -> Self {
        Self {
            required_providers,
            transition,
        }
    }

    pub(crate) fn configure(
        &self,
        label: &ProvidersLabel,
        ctx: &dyn AttrConfigurationContext,
    ) -> anyhow::Result<ConfiguredAttr> {
        let configured_label = match &self.transition {
            DepAttrTransition::Identity => ctx.configure_target(label),
            DepAttrTransition::Exec => ctx.configure_exec_target(label),
            DepAttrTransition::Toolchain => ctx.configure_toolchain_target(label),
            DepAttrTransition::Transition(tr) => ctx.configure_transition_target(label, tr)?,
        };
        Ok(ConfiguredAttr::Dep(Box::new(DepAttr {
            attr_type: self.dupe(),
            label: configured_label,
        })))
    }
}

/// Represents both configured and unconfigured forms.
pub trait ExplicitConfiguredDepMaybeConfigured: Display + Allocative {
    fn to_json(&self) -> anyhow::Result<serde_json::Value>;
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool>;
}

impl ConfiguredExplicitConfiguredDep {
    pub(crate) fn traverse<'a>(
        &'a self,
        traversal: &mut dyn ConfiguredAttrTraversal,
    ) -> anyhow::Result<()> {
        traversal.dep(&self.label)
    }
}


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

use std::fmt;
use std::fmt::Formatter;
use std::ops::Deref;

use allocative::Allocative;
use buck2_util::arc_str::ArcSlice;
use serde_json::Value;

use crate::attrs::attr_type::any_matches::AnyMatches;
use crate::attrs::attr_type::AttrType;
use crate::attrs::display::AttrDisplayWithContext;
use crate::attrs::display::AttrDisplayWithContextExt;
use crate::attrs::fmt_context::AttrFmtContext;
use crate::attrs::json::ToJsonWithContext;

#[derive(Debug, Hash, Eq, PartialEq, Allocative)]
pub struct DictAttrType {
    pub key: AttrType,
    pub value: AttrType,
    pub sorted: bool,
}

impl DictAttrType {
    pub fn new(key: AttrType, value: AttrType, sorted: bool) -> Self {
        Self { key, value, sorted }
    }

    pub(crate) fn fmt_with_arg(&self, f: &mut fmt::Formatter<'_>, arg: &str) -> fmt::Result {
        write!(
            f,
            "attrs.dict({}, {}, sorted={}{})",
            self.key,
            self.value,
            if self.sorted { "True" } else { "False" },
            arg
        )
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, Allocative, Default)]
pub struct DictLiteral<C: Eq>(pub ArcSlice<(C, C)>);

impl<C: Eq> Deref for DictLiteral<C> {
    type Target = ArcSlice<(C, C)>;

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

impl<C: Eq + AttrDisplayWithContext> AttrDisplayWithContext for DictLiteral<C> {
    fn fmt(&self, ctx: &AttrFmtContext, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{{")?;
        for (i, (k, v)) in self.0.iter().enumerate() {
            if i != 0 {
                write!(f, ",")?;
            }
            write!(f, "{}: {}", k.as_display(ctx), v.as_display(ctx))?;
        }
        write!(f, "}}")?;
        Ok(())
    }
}

impl<C: Eq> FromIterator<(C, C)> for DictLiteral<C> {
    fn from_iter<T: IntoIterator<Item = (C, C)>>(iter: T) -> Self {
        DictLiteral(ArcSlice::from_iter(iter))
    }
}

impl<C: Eq + AnyMatches> AnyMatches for DictLiteral<C> {
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        for (k, v) in self.0.iter() {
            if k.any_matches(filter)? || v.any_matches(filter)? {
                return Ok(true);
            }
        }
        Ok(false)
    }
}

impl<C: Eq + ToJsonWithContext> ToJsonWithContext for DictLiteral<C> {
    fn to_json(&self, ctx: &AttrFmtContext) -> anyhow::Result<Value> {
        let mut res: serde_json::Map<String, serde_json::Value> =
            serde_json::Map::with_capacity(self.len());
        for (k, v) in self.iter() {
            res.insert(
                k.to_json(ctx)?.as_str().unwrap().to_owned(),
                v.to_json(ctx)?,
            );
        }
        Ok(res.into())
    }
}


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

use std::fmt;
use std::hash::Hash;

use allocative::Allocative;
use buck2_util::arc_str::ArcStr;
use buck2_util::collections::ordered_set::OrderedSet;

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub struct EnumAttrType {
    pub variants: OrderedSet<ArcStr>,
}

#[derive(Debug, thiserror::Error)]
enum EnumAttrError {
    #[error("enum.attr() variant names must all be lowercase, got `{0}`")]
    NotLowercase(String),
    #[error("enum.attr() variant names must all be distinct, got repeated `{0}`")]
    DuplicateVariant(String),
}

impl EnumAttrType {
    pub fn new(variants: Vec<String>) -> anyhow::Result<Self> {
        let mut result = OrderedSet::with_capacity(variants.len());
        for x in variants {
            if x != x.to_lowercase() {
                return Err(EnumAttrError::NotLowercase(x).into());
            }
            let x = ArcStr::from(x);
            if result.contains(&x) {
                return Err(EnumAttrError::DuplicateVariant(x.as_str().to_owned()).into());
            }
            result.insert(x);
        }
        Ok(Self { variants: result })
    }

    pub fn fmt_with_arg(&self, f: &mut fmt::Formatter<'_>, arg: &str) -> fmt::Result {
        write!(f, "attrs.enum([")?;
        for (i, x) in self.variants.iter().enumerate() {
            if i != 0 {
                write!(f, ",")?;
            }
            write!(f, "{:?}", x)?;
        }
        write!(f, "]{})", arg)
    }
}


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

use allocative::Allocative;
use dupe::Dupe;

#[derive(Debug, Eq, PartialEq, Hash, Allocative, Clone, Copy, Dupe)]
pub struct IntAttrType;


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

use allocative::Allocative;
use buck2_core::provider::label::ProvidersLabel;
use dupe::Dupe;

use crate::attrs::configuration_context::AttrConfigurationContext;
use crate::attrs::configured_attr::ConfiguredAttr;

#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Dupe, Allocative)]
pub struct LabelAttrType;

impl LabelAttrType {
    pub(crate) fn configure(
        ctx: &dyn AttrConfigurationContext,
        label: &ProvidersLabel,
    ) -> anyhow::Result<ConfiguredAttr> {
        Ok(ConfiguredAttr::Label(Box::new(ctx.configure_target(label))))
    }
}


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

use std::fmt;
use std::fmt::Formatter;
use std::ops::Deref;

use allocative::Allocative;
use buck2_util::arc_str::ArcSlice;
use gazebo::prelude::SliceExt;
use serde_json::to_value;
use serde_json::Value;

use crate::attrs::attr_type::any_matches::AnyMatches;
use crate::attrs::attr_type::AttrType;
use crate::attrs::display::AttrDisplayWithContext;
use crate::attrs::fmt_context::AttrFmtContext;
use crate::attrs::json::ToJsonWithContext;

#[derive(Debug, Hash, Eq, PartialEq, Allocative)]
pub struct ListAttrType {
    pub inner: AttrType,
}

impl ListAttrType {
    pub(crate) fn new(inner: AttrType) -> Self {
        Self { inner }
    }

    pub(crate) fn fmt_with_arg(&self, f: &mut fmt::Formatter<'_>, arg: &str) -> fmt::Result {
        write!(f, "attrs.list({}{})", self.inner, arg)
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, Allocative, Default)]
pub struct ListLiteral<C: Eq>(pub ArcSlice<C>);

impl<C: Eq> Deref for ListLiteral<C> {
    type Target = ArcSlice<C>;

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

impl<C: Eq + AttrDisplayWithContext> AttrDisplayWithContext for ListLiteral<C> {
    fn fmt(&self, ctx: &AttrFmtContext, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "[")?;
        for (i, v) in self.0.iter().enumerate() {
            if i != 0 {
                write!(f, ",")?;
            }
            AttrDisplayWithContext::fmt(v, ctx, f)?;
        }
        write!(f, "]")?;
        Ok(())
    }
}

impl<C: Eq> FromIterator<C> for ListLiteral<C> {
    fn from_iter<T: IntoIterator<Item = C>>(iter: T) -> Self {
        ListLiteral(ArcSlice::from_iter(iter))
    }
}

impl<C: Eq + AnyMatches> AnyMatches for ListLiteral<C> {
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        for v in self.0.iter() {
            if v.any_matches(filter)? {
                return Ok(true);
            }
        }
        Ok(false)
    }
}

impl<C: Eq + ToJsonWithContext> ToJsonWithContext for ListLiteral<C> {
    fn to_json(&self, ctx: &AttrFmtContext) -> anyhow::Result<Value> {
        Ok(to_value(self.try_map(|c| c.to_json(ctx))?)?)
    }
}


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

use std::fmt;
use std::fmt::Display;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::configuration::transition::id::TransitionId;
use dupe::Dupe;

use crate::attrs::attr_type::any::AnyAttrType;
use crate::attrs::attr_type::arg::ArgAttrType;
use crate::attrs::attr_type::bool::BoolAttrType;
use crate::attrs::attr_type::configuration_dep::ConfigurationDepAttrType;
use crate::attrs::attr_type::configured_dep::ExplicitConfiguredDepAttrType;
use crate::attrs::attr_type::dep::DepAttrTransition;
use crate::attrs::attr_type::dep::DepAttrType;
use crate::attrs::attr_type::dict::DictAttrType;
use crate::attrs::attr_type::enumeration::EnumAttrType;
use crate::attrs::attr_type::int::IntAttrType;
use crate::attrs::attr_type::label::LabelAttrType;
use crate::attrs::attr_type::list::ListAttrType;
use crate::attrs::attr_type::one_of::OneOfAttrType;
use crate::attrs::attr_type::option::OptionAttrType;
use crate::attrs::attr_type::query::QueryAttrType;
use crate::attrs::attr_type::source::SourceAttrType;
use crate::attrs::attr_type::split_transition_dep::SplitTransitionDepAttrType;
use crate::attrs::attr_type::string::StringAttrType;
use crate::attrs::attr_type::tuple::TupleAttrType;
use crate::attrs::attr_type::visibility::VisibilityAttrType;
use crate::attrs::attr_type::within_view::WithinViewAttrType;
use crate::provider_id_set::ProviderIdSet;

pub mod any;
pub mod any_matches;
pub mod arg;
pub mod attr_config;
pub mod attr_like;
pub mod bool;
pub mod configuration_dep;
pub mod configured_dep;
pub mod default_only;
pub mod dep;
pub mod dict;
pub mod enumeration;
pub mod int;
pub mod label;
pub mod list;
pub mod one_of;
pub mod option;
pub mod query;
pub mod source;
pub mod split_transition_dep;
pub mod string;
pub mod tuple;
pub mod visibility;
pub mod within_view;

#[derive(Clone, Dupe, Debug, Hash, Eq, PartialEq, Allocative)]
pub struct AttrType(pub Arc<AttrTypeInner>);

#[derive(Debug, Hash, Eq, PartialEq, Allocative)]
pub enum AttrTypeInner {
    Any(AnyAttrType),
    Arg(ArgAttrType),
    ConfigurationDep(ConfigurationDepAttrType),
    ConfiguredDep(ExplicitConfiguredDepAttrType),
    Bool(BoolAttrType),
    Int(IntAttrType),
    Dep(DepAttrType),
    Dict(DictAttrType),
    List(ListAttrType),
    Tuple(TupleAttrType),
    OneOf(OneOfAttrType),
    Option(OptionAttrType),
    Query(QueryAttrType),
    Source(SourceAttrType),
    SplitTransitionDep(SplitTransitionDepAttrType),
    String(StringAttrType),
    Enum(EnumAttrType),
    Label(LabelAttrType),
    Visibility(VisibilityAttrType),
    WithinView(WithinViewAttrType),
}

impl AttrType {
    pub fn fmt_with_default(
        &self,
        f: &mut fmt::Formatter<'_>,
        default: Option<&str>,
    ) -> fmt::Result {
        let mut attr = |s| match default {
            None => write!(f, "attrs.{}()", s),
            Some(default) => write!(f, "attrs.{}(default={})", s, default),
        };
        let arg = || match default {
            None => String::new(),
            Some(x) => format!(", default={}", x),
        };

        match &*self.0 {
            AttrTypeInner::Any(_) => attr("any"),
            AttrTypeInner::Arg(_) => attr("arg"),
            AttrTypeInner::ConfigurationDep(_) => attr("configuration_dep"),
            AttrTypeInner::ConfiguredDep(_) => attr("configured_dep"),
            AttrTypeInner::Bool(_) => attr("bool"),
            AttrTypeInner::Int(_) => attr("int"),
            AttrTypeInner::Dep(_) => attr("dep"),
            AttrTypeInner::Query(_) => attr("query"),
            AttrTypeInner::Dict(x) => x.fmt_with_arg(f, &arg()),
            AttrTypeInner::List(x) => x.fmt_with_arg(f, &arg()),
            AttrTypeInner::Tuple(x) => x.fmt_with_arg(f, &arg()),
            AttrTypeInner::OneOf(x) => x.fmt_with_arg(f, &arg()),
            AttrTypeInner::Option(x) => x.fmt_with_arg(f, &arg()),
            AttrTypeInner::Enum(x) => x.fmt_with_arg(f, &arg()),
            AttrTypeInner::Source(_) => attr("source"),
            AttrTypeInner::SplitTransitionDep(_) => attr("split_transition_dep"),
            AttrTypeInner::String(_) => attr("string"),
            AttrTypeInner::Label(_) => attr("label"),
            AttrTypeInner::Visibility(_) => attr("visibility"),
            AttrTypeInner::WithinView(_) => attr("within_view"),
        }
    }

    pub fn any() -> Self {
        Self(Arc::new(AttrTypeInner::Any(AnyAttrType)))
    }

    /// An arg attribute. Args are similar to strings, but have built in support
    /// for string parameter macros and make variables. Command line
    /// builders used in rule implementations use args (and so an arg attribute
    /// can be directly added to them).
    ///
    /// Takes in an anon_target_compatible flag, which indicates whether the arg
    /// can be passed into anon targets. There is a slight memory hit when using
    /// this flag.
    pub fn arg(anon_target_compatible: bool) -> Self {
        Self(Arc::new(AttrTypeInner::Arg(ArgAttrType {
            anon_target_compatible,
        })))
    }

    pub fn enumeration(variants: Vec<String>) -> anyhow::Result<Self> {
        Ok(Self(Arc::new(AttrTypeInner::Enum(EnumAttrType::new(
            variants,
        )?))))
    }

    pub fn bool() -> Self {
        Self(Arc::new(AttrTypeInner::Bool(BoolAttrType)))
    }

    pub fn int() -> Self {
        Self(Arc::new(AttrTypeInner::Int(IntAttrType)))
    }

    pub fn configuration_dep() -> Self {
        Self(Arc::new(AttrTypeInner::ConfigurationDep(
            ConfigurationDepAttrType,
        )))
    }

    /// A TargetLabel attribute optionally with a specific provider/providers
    /// that are expected.
    ///
    /// If `required_providers` is non-empty, the dependency must return those providers
    /// from its implementation function. Otherwise an error will result at resolution time.
    pub fn dep(required_providers: ProviderIdSet) -> Self {
        Self(Arc::new(AttrTypeInner::Dep(DepAttrType::new(
            required_providers,
            DepAttrTransition::Identity,
        ))))
    }

    /// An execution dependency attribute optionally with a specific provider/providers
    /// that are expected.
    ///
    /// If `required_providers` is non-empty, the dependency must return those providers
    /// from its implementation function. Otherwise an error will result at resolution time.
    pub fn exec_dep(required_providers: ProviderIdSet) -> Self {
        Self(Arc::new(AttrTypeInner::Dep(DepAttrType::new(
            required_providers,
            DepAttrTransition::Exec,
        ))))
    }

    /// A toolchain dependency attribute optionally with a specific provider/providers
    /// that are expected.
    ///
    /// If `required_providers` is non-empty, the dependency must return those providers
    /// from its implementation function. Otherwise an error will result at resolution time.
    pub fn toolchain_dep(required_providers: ProviderIdSet) -> Self {
        Self(Arc::new(AttrTypeInner::Dep(DepAttrType::new(
            required_providers,
            DepAttrTransition::Toolchain,
        ))))
    }

    /// An a dependency attribute which changes the configuration optionally with a specific
    /// provider/providers that are expected.
    ///
    /// If `required_providers` is non-empty, the dependency must return those providers
    /// from its implementation function. Otherwise an error will result at resolution time.
    pub fn transition_dep(required_providers: ProviderIdSet, cfg: Arc<TransitionId>) -> Self {
        Self(Arc::new(AttrTypeInner::Dep(DepAttrType::new(
            required_providers,
            DepAttrTransition::Transition(cfg),
        ))))
    }

    pub fn configured_dep(required_providers: ProviderIdSet) -> Self {
        Self(Arc::new(AttrTypeInner::ConfiguredDep(
            ExplicitConfiguredDepAttrType { required_providers },
        )))
    }

    pub fn split_transition_dep(required_providers: ProviderIdSet, cfg: Arc<TransitionId>) -> Self {
        Self(Arc::new(AttrTypeInner::SplitTransitionDep(
            SplitTransitionDepAttrType::new(required_providers, cfg),
        )))
    }

    /// A dict attribute containing keys and values of the specified types.
    pub fn dict(key: AttrType, value: AttrType, sorted: bool) -> Self {
        Self(Arc::new(AttrTypeInner::Dict(DictAttrType::new(
            key, value, sorted,
        ))))
    }

    /// A list attribute containing items of some inner type.
    pub fn list(inner: AttrType) -> Self {
        Self(Arc::new(AttrTypeInner::List(ListAttrType::new(inner))))
    }

    pub fn tuple(xs: Vec<AttrType>) -> Self {
        Self(Arc::new(AttrTypeInner::Tuple(TupleAttrType::new(xs))))
    }

    pub fn one_of(xs: Vec<AttrType>) -> Self {
        Self(Arc::new(AttrTypeInner::OneOf(OneOfAttrType::new(xs))))
    }

    pub fn option(value: AttrType) -> Self {
        Self(Arc::new(AttrTypeInner::Option(OptionAttrType::new(value))))
    }

    pub fn query() -> Self {
        Self(Arc::new(AttrTypeInner::Query(QueryAttrType::new(
            DepAttrType::new(ProviderIdSet::EMPTY, DepAttrTransition::Identity),
        ))))
    }

    // A file attribute. This will accept paths or targets like
    /// `//some:target[inner]`. When contained within a list, one item may
    /// expand to multiple (e.g. an output group or a lazy glob).
    pub fn source(allow_directory: bool) -> Self {
        Self(Arc::new(AttrTypeInner::Source(SourceAttrType {
            allow_directory,
        })))
    }

    /// A string attribute. For flags passed to a command, an arg() attr is
    /// preferred to support macro and make variable substitution.
    pub fn string() -> Self {
        Self(Arc::new(AttrTypeInner::String(StringAttrType)))
    }

    pub fn label() -> Self {
        Self(Arc::new(AttrTypeInner::Label(LabelAttrType)))
    }

    pub(crate) fn visibility() -> Self {
        Self(Arc::new(AttrTypeInner::Visibility(VisibilityAttrType)))
    }

    pub(crate) fn within_view() -> Self {
        Self(Arc::new(AttrTypeInner::WithinView(WithinViewAttrType)))
    }

    /// Used when we first detect that concatenation is going to happen for an attr
    /// while loading a build file. Returning false here will make us provide an error
    /// during the loading phase at the point that the concatenation happens.
    ///
    /// In some cases, we can't detect that the concattenation isn't allowed at this
    /// point and can only provide an error when performing the actual concatenation.
    pub fn supports_concat(&self) -> bool {
        match &*self.0 {
            AttrTypeInner::Bool(_)
            | AttrTypeInner::Query(_)
            | AttrTypeInner::Source(_)
            | AttrTypeInner::ConfigurationDep(_)
            | AttrTypeInner::ConfiguredDep(_)
            | AttrTypeInner::Int(_)
            | AttrTypeInner::Dep(_)
            | AttrTypeInner::Tuple(_)
            | AttrTypeInner::SplitTransitionDep(_)
            | AttrTypeInner::Label(_)
            | AttrTypeInner::Enum(_)
            | AttrTypeInner::Visibility(_)
            | AttrTypeInner::WithinView(_) => false,
            AttrTypeInner::Any(_)
            | AttrTypeInner::Arg(_)
            | AttrTypeInner::Dict(_)
            | AttrTypeInner::List(_)
            | AttrTypeInner::String(_) => true,
            AttrTypeInner::Option(inner) => inner.inner.supports_concat(),
            // Reject if none of the inner types support concat. Mismatched types are rejected later.
            AttrTypeInner::OneOf(inner) => inner.any_supports_concat(),
        }
    }

    /// If type is option, return the element type.
    /// This function is needed because we store `Some` of coerced and configured attributes
    /// without indication they are `Some`. In other words, `[""]` is coerced and configured
    /// identically to both `attrs.list(attrs.string())`
    /// and `attrs.option(attrs.list(attrs.string()))`.
    pub(crate) fn unwrap_if_option(&self) -> &AttrType {
        match &*self.0 {
            AttrTypeInner::Option(inner) => &inner.inner,
            _ => self,
        }
    }
}

/// Invariant: All these displays look like function calls, so follow the pattern `attrs.foo(...)`.
impl Display for AttrType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.fmt_with_default(f, None)
    }
}


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

use std::fmt;

use allocative::Allocative;

use crate::attrs::attr_type::AttrType;

#[derive(Debug, thiserror::Error)]
enum OneOfAttrTypeError {
    #[error("Oneof index ({0}) out of bounds (internal error)")]
    IndexOutOfBounds(u32),
}

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub struct OneOfAttrType {
    pub xs: Vec<AttrType>,
}

impl OneOfAttrType {
    pub fn new(xs: Vec<AttrType>) -> OneOfAttrType {
        Self { xs }
    }

    pub fn fmt_with_arg(&self, f: &mut fmt::Formatter<'_>, arg: &str) -> fmt::Result {
        write!(f, "attrs.one_of(")?;
        for (i, x) in self.xs.iter().enumerate() {
            if i != 0 {
                write!(f, ", ")?;
            }
            write!(f, "{x}")?;
        }
        write!(f, "{})", arg)
    }

    pub(crate) fn any_supports_concat(&self) -> bool {
        self.xs.iter().any(AttrType::supports_concat)
    }

    pub(crate) fn get(&self, i: u32) -> anyhow::Result<&AttrType> {
        self.xs
            .get(i as usize)
            .ok_or_else(|| OneOfAttrTypeError::IndexOutOfBounds(i).into())
    }
}


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

use std::fmt;

use allocative::Allocative;

use crate::attrs::attr_type::AttrType;

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub struct OptionAttrType {
    pub inner: AttrType,
}

impl OptionAttrType {
    pub fn new(inner: AttrType) -> Self {
        Self { inner }
    }

    pub(crate) fn fmt_with_arg(&self, f: &mut fmt::Formatter<'_>, arg: &str) -> fmt::Result {
        write!(f, "attrs.option({}{})", self.inner, arg)
    }
}


/*
 * 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 allocative::Allocative;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::provider::label::ProvidersLabelMaybeConfigured;
use dupe::Dupe;

use crate::attrs::attr_type::arg::QueryExpansion;
use crate::attrs::attr_type::dep::DepAttrType;
use crate::attrs::configuration_context::AttrConfigurationContext;
use crate::attrs::configured_traversal::ConfiguredAttrTraversal;
use crate::attrs::traversal::CoercedAttrTraversal;
use crate::provider_id_set::ProviderIdSet;

#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub struct QueryAttrType {
    pub inner: DepAttrType,
}

impl QueryAttrType {
    pub fn new(inner: DepAttrType) -> Self {
        Self { inner }
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub struct QueryAttr<P: ProvidersLabelMaybeConfigured> {
    pub providers: ProviderIdSet,
    pub query: QueryAttrBase<P>,
}

impl<P: ProvidersLabelMaybeConfigured> QueryAttr<P> {
    pub fn query(&self) -> &str {
        self.query.query()
    }
}

impl QueryAttr<ConfiguredProvidersLabel> {
    pub(crate) fn traverse<'a>(
        &'a self,
        traversal: &mut dyn ConfiguredAttrTraversal,
    ) -> anyhow::Result<()> {
        self.query.traverse(traversal)
    }
}

impl QueryAttr<ProvidersLabel> {
    pub(crate) fn configure(
        &self,
        ctx: &dyn AttrConfigurationContext,
    ) -> anyhow::Result<QueryAttr<ConfiguredProvidersLabel>> {
        Ok(QueryAttr {
            query: self.query.configure(ctx)?,
            providers: self.providers.dupe(),
        })
    }

    pub(crate) fn traverse<'a>(
        &'a self,
        traversal: &mut dyn CoercedAttrTraversal<'a>,
    ) -> anyhow::Result<()> {
        self.query.traverse(traversal)
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub struct QueryMacroBase<P: ProvidersLabelMaybeConfigured> {
    pub expansion_type: QueryExpansion,
    pub query: QueryAttrBase<P>,
}

impl<P: ProvidersLabelMaybeConfigured> Display for QueryMacroBase<P> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} {}", &self.expansion_type, self.query.query())?;
        Ok(())
    }
}

impl QueryMacroBase<ConfiguredProvidersLabel> {
    pub(crate) fn traverse<'a>(
        &'a self,
        traversal: &mut dyn ConfiguredAttrTraversal,
    ) -> anyhow::Result<()> {
        self.query.traverse(traversal)
    }
}

impl QueryMacroBase<ProvidersLabel> {
    pub(crate) fn traverse<'a>(
        &'a self,
        traversal: &mut dyn CoercedAttrTraversal<'a>,
    ) -> anyhow::Result<()> {
        self.query.traverse(traversal)
    }

    pub(crate) fn configure(
        &self,
        ctx: &dyn AttrConfigurationContext,
    ) -> anyhow::Result<QueryMacroBase<ConfiguredProvidersLabel>> {
        Ok(QueryMacroBase {
            expansion_type: self.expansion_type.clone(),
            query: self.query.configure(ctx)?,
        })
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub struct QueryAttrBase<P: ProvidersLabelMaybeConfigured> {
    pub query: String,
    pub resolved_literals: ResolvedQueryLiterals<P>,
}

impl<P: ProvidersLabelMaybeConfigured> QueryAttrBase<P> {
    pub fn query(&self) -> &str {
        &self.query
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub struct ResolvedQueryLiterals<P: ProvidersLabelMaybeConfigured>(pub BTreeMap<String, P>);

impl QueryAttrBase<ConfiguredProvidersLabel> {
    pub(crate) fn traverse<'a>(
        &'a self,
        traversal: &mut dyn ConfiguredAttrTraversal,
    ) -> anyhow::Result<()> {
        // queries have no inputs.
        for dep in self.resolved_literals.0.values() {
            traversal.dep(dep)?;
        }
        traversal.query_macro(&self.query, &self.resolved_literals)?;
        Ok(())
    }
}

impl QueryAttrBase<ProvidersLabel> {
    fn configure(
        &self,
        ctx: &dyn AttrConfigurationContext,
    ) -> anyhow::Result<QueryAttrBase<ConfiguredProvidersLabel>> {
        Ok(QueryAttrBase {
            query: self.query.clone(),
            resolved_literals: ResolvedQueryLiterals(
                self.resolved_literals
                    .0
                    .iter()
                    .map(|(key, value)| Ok((key.clone(), ctx.configure_target(value))))
                    .collect::<anyhow::Result<_>>()?,
            ),
        })
    }

    fn traverse<'a>(&'a self, traversal: &mut dyn CoercedAttrTraversal<'a>) -> anyhow::Result<()> {
        // queries don't have any configuration_deps or inputs currently.
        for dep in self.resolved_literals.0.values() {
            traversal.dep(dep.target())?;
        }
        Ok(())
    }
}


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

use allocative::Allocative;
use dupe::Dupe;

#[derive(Debug, Eq, PartialEq, Hash, Allocative, Clone, Copy, Dupe)]
pub struct SourceAttrType {
    pub allow_directory: bool,
}


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

use std::fmt;
use std::fmt::Display;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::configuration::transition::id::TransitionId;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_util::collections::sorted_map::SortedMap;
use dupe::Dupe;

use crate::attrs::configuration_context::AttrConfigurationContext;
use crate::attrs::configured_attr::ConfiguredAttr;
use crate::provider_id_set::ProviderIdSet;

#[derive(Debug, PartialEq, Eq, Hash, Allocative)]
pub struct SplitTransitionDepAttrType {
    pub required_providers: ProviderIdSet,
    pub transition: Arc<TransitionId>,
}

impl SplitTransitionDepAttrType {
    pub fn new(required_providers: ProviderIdSet, transition: Arc<TransitionId>) -> Self {
        SplitTransitionDepAttrType {
            required_providers,
            transition,
        }
    }

    pub(crate) fn configure(
        &self,
        dep: &ProvidersLabel,
        ctx: &dyn AttrConfigurationContext,
    ) -> anyhow::Result<ConfiguredAttr> {
        let configured_providers = ctx.configure_split_transition_target(dep, &self.transition)?;
        Ok(ConfiguredAttr::SplitTransitionDep(Box::new(
            ConfiguredSplitTransitionDep {
                deps: configured_providers,
                required_providers: self.required_providers.dupe(),
            },
        )))
    }
}

/// Configured or unconfigured.
pub trait SplitTransitionDepMaybeConfigured: Display + Allocative {
    fn to_json(&self) -> anyhow::Result<serde_json::Value>;
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool>;
}

#[derive(Hash, PartialEq, Eq, Debug, Clone, Allocative)]
pub struct ConfiguredSplitTransitionDep {
    pub deps: SortedMap<String, ConfiguredProvidersLabel>,
    pub required_providers: ProviderIdSet,
}

impl Display for ConfiguredSplitTransitionDep {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{{")?;
        for (i, dep) in self.deps.iter().enumerate() {
            if i != 0 {
                write!(f, ", ")?;
            }
            write!(f, "{:?}: {}", dep.0, dep.1)?;
        }
        write!(f, "}}")?;
        Ok(())
    }
}

impl SplitTransitionDepMaybeConfigured for ConfiguredSplitTransitionDep {
    fn to_json(&self) -> anyhow::Result<serde_json::Value> {
        let mut map = serde_json::Map::with_capacity(self.deps.len());
        for (label, target) in &self.deps {
            map.insert(label.clone(), serde_json::to_value(target.to_string())?);
        }
        Ok(serde_json::Value::Object(map))
    }

    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        for (label, target) in &self.deps {
            if filter(label)? || filter(&target.to_string())? {
                return Ok(true);
            }
        }
        Ok(false)
    }
}

#[derive(derive_more::Display, Debug, Hash, PartialEq, Eq, Clone, Allocative)]
#[display(fmt = "{}", label)]
pub struct SplitTransitionDep {
    pub label: ProvidersLabel,
    pub transition: Arc<TransitionId>,
    pub required_providers: ProviderIdSet,
}

impl SplitTransitionDepMaybeConfigured for SplitTransitionDep {
    fn to_json(&self) -> anyhow::Result<serde_json::Value> {
        Ok(serde_json::to_value(self.to_string())?)
    }

    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        filter(&self.to_string())
    }
}


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

use std::fmt;
use std::fmt::Display;
use std::ops::Deref;

use allocative::Allocative;
use buck2_util::arc_str::ArcStr;
use dupe::Dupe;
use serde::Serialize;

#[derive(Debug, Eq, PartialEq, Hash, Allocative, Clone, Copy, Dupe)]
pub struct StringAttrType;

#[derive(
    Default, Debug, Eq, PartialEq, Hash, Clone, Dupe, Allocative, Serialize
)]
#[serde(transparent)]
pub struct StringLiteral(pub ArcStr);

impl Deref for StringLiteral {
    type Target = ArcStr;

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

impl Display for StringLiteral {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if f.alternate() {
            f.write_str(self.0.as_str())
        } else {
            write!(f, "\"{}\"", self.0)
        }
    }
}


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

use std::fmt;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::ops::Deref;

use allocative::Allocative;
use buck2_util::arc_str::ArcSlice;
use gazebo::prelude::SliceExt;
use serde_json::to_value;
use serde_json::Value;

use crate::attrs::attr_type::any_matches::AnyMatches;
use crate::attrs::attr_type::AttrType;
use crate::attrs::display::AttrDisplayWithContext;
use crate::attrs::fmt_context::AttrFmtContext;
use crate::attrs::json::ToJsonWithContext;

#[derive(Clone, Debug, Eq, PartialEq, Hash, Allocative)]
pub struct TupleAttrType {
    pub xs: Vec<AttrType>,
}

impl TupleAttrType {
    pub fn new(xs: Vec<AttrType>) -> Self {
        Self { xs }
    }

    pub(crate) fn fmt_with_arg(&self, f: &mut fmt::Formatter<'_>, arg: &str) -> fmt::Result {
        write!(f, "attrs.tuple(")?;
        for (i, x) in self.xs.iter().enumerate() {
            if i != 0 {
                write!(f, ", ")?;
            }
            write!(f, "{x}")?;
        }
        write!(f, "{})", arg)
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, Allocative, Default)]
pub struct TupleLiteral<C: Eq>(pub ArcSlice<C>);

impl<C: Eq> Deref for TupleLiteral<C> {
    type Target = ArcSlice<C>;

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

impl<C: Eq + AttrDisplayWithContext> AttrDisplayWithContext for TupleLiteral<C> {
    fn fmt(&self, ctx: &AttrFmtContext, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "(")?;
        for (i, v) in self.0.iter().enumerate() {
            if i != 0 {
                write!(f, ",")?;
            }
            AttrDisplayWithContext::fmt(v, ctx, f)?;
        }
        write!(f, ")")?;
        Ok(())
    }
}

impl<C: Eq> FromIterator<C> for TupleLiteral<C> {
    fn from_iter<T: IntoIterator<Item = C>>(iter: T) -> Self {
        TupleLiteral(ArcSlice::from_iter(iter))
    }
}

impl<C: Eq + AnyMatches> AnyMatches for TupleLiteral<C> {
    fn any_matches(&self, filter: &dyn Fn(&str) -> anyhow::Result<bool>) -> anyhow::Result<bool> {
        for v in self.0.iter() {
            if v.any_matches(filter)? {
                return Ok(true);
            }
        }
        Ok(false)
    }
}

impl<C: Eq + ToJsonWithContext> ToJsonWithContext for TupleLiteral<C> {
    fn to_json(&self, ctx: &AttrFmtContext) -> anyhow::Result<Value> {
        Ok(to_value(self.try_map(|c| c.to_json(ctx))?)?)
    }
}


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

use allocative::Allocative;
use dupe::Dupe;

#[derive(Debug, Eq, PartialEq, Hash, Allocative, Clone, Copy, Dupe)]
pub struct VisibilityAttrType;


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

use allocative::Allocative;
use dupe::Dupe;

#[derive(Debug, Eq, PartialEq, Hash, Allocative, Clone, Copy, Dupe)]
pub struct WithinViewAttrType;


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

use std::fmt::Display;

use allocative::Allocative;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::provider::label::ProvidersLabelMaybeConfigured;
use buck2_util::arc_str::ArcStr;
use derive_more::Display;
use dupe::Dupe;
use gazebo::prelude::SliceExt;
use static_assertions::assert_eq_size;

use crate::attrs::attr_type::query::QueryMacroBase;
use crate::attrs::configuration_context::AttrConfigurationContext;
use crate::attrs::configured_traversal::ConfiguredAttrTraversal;
use crate::attrs::traversal::CoercedAttrTraversal;

#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Dupe, Allocative)]
pub struct ArgAttrType {
    pub anon_target_compatible: bool,
}

/// [StringWithMacros] is the core representation for an attrs.arg() (in all of it's coerced, configured, and resolved
/// forms). The parsed arg string is held as a sequence of parts (each part either a literal string or a macro). When
/// being added to a command line, these parts will be concattenated together and added as a single arg.
/// Each variant takes in a boolean which determines if the resolved form should be compatible with anon targets.
#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub enum StringWithMacros<P: ProvidersLabelMaybeConfigured> {
    /// Semantically, StringWithMacros::StringPart(s) is equivalent to
    /// StringWithMacros::ManyParts(vec![StringWithMacrosPart::String(s)]). We special-case this
    /// for memory efficiency to avoid allocating unnecessary vectors, since lone string parts are
    /// very frequent.
    StringPart(ArcStr),
    // For resolution, we defer all work and simply alloc a ConfiguredStringWithMacros into the starlark
    // context. This allows us to defer resolution work to the point that it is being added to a command
    // line, but it requires that we can cheaply copy ConfiguredStringWithMacros.
    ManyParts(Box<[StringWithMacrosPart<P>]>),
}

// Avoid changing the size accidentally.
assert_eq_size!(StringWithMacros<ProvidersLabel>, [usize; 3]);

impl StringWithMacros<ConfiguredProvidersLabel> {
    pub fn concat(self, items: impl Iterator<Item = anyhow::Result<Self>>) -> anyhow::Result<Self> {
        let mut parts = Vec::new();
        for x in std::iter::once(Ok(self)).chain(items) {
            match x? {
                Self::StringPart(x) => {
                    parts.push(StringWithMacrosPart::String(x));
                }
                Self::ManyParts(xs) => {
                    parts.extend(xs.into_vec().into_iter());
                }
            }
        }
        Ok(Self::ManyParts(parts.into_boxed_slice()))
    }
}

impl StringWithMacros<ConfiguredProvidersLabel> {
    pub fn traverse<'a>(
        &'a self,
        traversal: &mut dyn ConfiguredAttrTraversal,
    ) -> anyhow::Result<()> {
        match self {
            Self::StringPart(..) => {}
            Self::ManyParts(ref parts) => {
                for part in parts.iter() {
                    match part {
                        StringWithMacrosPart::String(_) => {}
                        StringWithMacrosPart::Macro(_, m) => {
                            m.traverse(traversal)?;
                        }
                    }
                }
            }
        }
        Ok(())
    }
}

impl StringWithMacros<ProvidersLabel> {
    pub(crate) fn configure(
        &self,
        ctx: &dyn AttrConfigurationContext,
        anon_target_compatible: bool,
    ) -> anyhow::Result<ConfiguredStringWithMacros> {
        match self {
            Self::StringPart(part) => Ok(ConfiguredStringWithMacros {
                string_with_macros: StringWithMacros::StringPart(part.clone()),
                anon_target_compatible,
            }),
            Self::ManyParts(parts) => Ok(ConfiguredStringWithMacros {
                string_with_macros: StringWithMacros::ManyParts(
                    parts.try_map(|p| p.configure(ctx))?.into_boxed_slice(),
                ),
                anon_target_compatible,
            }),
        }
    }

    pub(crate) fn traverse<'a>(
        &'a self,
        traversal: &mut dyn CoercedAttrTraversal<'a>,
    ) -> anyhow::Result<()> {
        match self {
            Self::StringPart(..) => {}
            Self::ManyParts(ref parts) => {
                for part in parts.iter() {
                    match part {
                        StringWithMacrosPart::String(_) => {}
                        StringWithMacrosPart::Macro(_, m) => {
                            m.traverse(traversal)?;
                        }
                    }
                }
            }
        }

        Ok(())
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub enum StringWithMacrosPart<P: ProvidersLabelMaybeConfigured> {
    String(ArcStr),
    Macro(/* write_to_file */ bool, MacroBase<P>),
}

assert_eq_size!(MacroBase<ProvidersLabel>, [usize; 3]);
assert_eq_size!(StringWithMacrosPart<ProvidersLabel>, [usize; 4]);

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub struct UnrecognizedMacro {
    pub macro_type: Box<str>,
    pub args: Box<[String]>,
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub enum MacroBase<P: ProvidersLabelMaybeConfigured> {
    Location(P),
    /// Represents both $(exe) and $(exe_target) usages.
    Exe {
        label: P,
        exec_dep: bool,
    },
    /// A user-defined make variable (like `$(CXX)`). This will be resolved based on the propagated TemplateVariableInfos.
    UserUnkeyedPlaceholder(Box<str>),

    /// A user-defined macro (like `$(cxxppflags //some:target)`). This will be resolved based on the propagated TemplateVariableInfos.
    UserKeyedPlaceholder(Box<(Box<str>, P, Option<Box<str>>)>),

    Query(Box<QueryMacroBase<P>>),

    /// Right now, we defer error for unrecognized macros to the place where they are used. This just allows
    /// us to progress further into a build and detect more issues. Once we have all (or most) of the buckv1 macros
    /// recognized we'll remove this and make it an early error.
    UnrecognizedMacro(Box<UnrecognizedMacro>),
}

impl MacroBase<ConfiguredProvidersLabel> {
    pub fn traverse<'a>(
        &'a self,
        traversal: &mut dyn ConfiguredAttrTraversal,
    ) -> anyhow::Result<()> {
        // macros can't reference repo inputs (they only reference the outputs of other targets)
        match self {
            MacroBase::Location(l) | MacroBase::UserKeyedPlaceholder(box (_, l, _)) => {
                traversal.dep(l)
            }
            MacroBase::Exe {
                label,
                exec_dep: true,
            } => traversal.exec_dep(label),
            MacroBase::Exe {
                label,
                exec_dep: false,
            } => traversal.dep(label),
            MacroBase::Query(query_macro) => query_macro.traverse(traversal),
            MacroBase::UserUnkeyedPlaceholder(_) | MacroBase::UnrecognizedMacro(..) => Ok(()),
        }
    }
}

impl MacroBase<ProvidersLabel> {
    pub fn configure(&self, ctx: &dyn AttrConfigurationContext) -> anyhow::Result<ConfiguredMacro> {
        Ok(match self {
            UnconfiguredMacro::Location(target) => {
                ConfiguredMacro::Location(ctx.configure_target(target))
            }
            UnconfiguredMacro::Exe { label, exec_dep } => ConfiguredMacro::Exe {
                label: if *exec_dep {
                    ctx.configure_exec_target(label)
                } else {
                    ctx.configure_target(label)
                },
                exec_dep: *exec_dep,
            },
            UnconfiguredMacro::UserUnkeyedPlaceholder(var_name) => {
                ConfiguredMacro::UserUnkeyedPlaceholder(var_name.clone())
            }
            UnconfiguredMacro::UserKeyedPlaceholder(box (var_name, target, arg)) => {
                ConfiguredMacro::UserKeyedPlaceholder(Box::new((
                    var_name.clone(),
                    ctx.configure_target(target),
                    arg.clone(),
                )))
            }
            UnconfiguredMacro::Query(query) => {
                ConfiguredMacro::Query(Box::new(query.configure(ctx)?))
            }
            UnconfiguredMacro::UnrecognizedMacro(macr) => {
                ConfiguredMacro::UnrecognizedMacro(macr.clone())
            }
        })
    }

    pub fn traverse<'a>(
        &'a self,
        traversal: &mut dyn CoercedAttrTraversal<'a>,
    ) -> anyhow::Result<()> {
        match self {
            MacroBase::Location(l) | MacroBase::UserKeyedPlaceholder(box (_, l, _)) => {
                traversal.dep(l.target())
            }
            MacroBase::Exe {
                label,
                exec_dep: true,
            } => traversal.exec_dep(label.target()),
            MacroBase::Exe {
                label,
                exec_dep: false,
            } => traversal.dep(label.target()),
            MacroBase::Query(query) => query.traverse(traversal),
            MacroBase::UserUnkeyedPlaceholder(_) | MacroBase::UnrecognizedMacro(..) => Ok(()),
        }
    }
}

// These type aliases are just a little bit easier to use, the differentiating thing comes
// right at the beginning instead of at the end in a type param.
pub type UnconfiguredMacro = MacroBase<ProvidersLabel>;
pub type ConfiguredMacro = MacroBase<ConfiguredProvidersLabel>;

pub type UnconfiguredStringWithMacrosPart = StringWithMacrosPart<ProvidersLabel>;
pub type ConfiguredStringWithMacrosPart = StringWithMacrosPart<ConfiguredProvidersLabel>;

pub type UnconfiguredStringWithMacros = StringWithMacros<ProvidersLabel>;

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative, Display)]
#[display(fmt = "{}", string_with_macros)]
pub struct ConfiguredStringWithMacros {
    pub string_with_macros: StringWithMacros<ConfiguredProvidersLabel>,
    pub anon_target_compatible: bool,
}

/// Display attempts to approximately reproduce the string that created a macro.
impl<P: ProvidersLabelMaybeConfigured> Display for MacroBase<P> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // TODO: this should re-escape values in the args that need to be escaped to have returned that arg (it's not possible
        // to tell where there were unnecessary escapes and it's not worth tracking that).
        match self {
            MacroBase::Location(l) => write!(f, "location {}", l),
            MacroBase::Exe { label, exec_dep } => {
                write!(
                    f,
                    "{} {}",
                    if *exec_dep { "exe" } else { "exe_target" },
                    label
                )
            }
            MacroBase::Query(query) => Display::fmt(query, f),
            MacroBase::UserUnkeyedPlaceholder(var) => write!(f, "{}", var),
            MacroBase::UserKeyedPlaceholder(box (macro_type, target, arg)) => write!(
                f,
                "{} {}{}",
                macro_type,
                target,
                if let Some(arg) = arg {
                    format!(" {}", arg)
                } else {
                    "".to_owned()
                }
            ),
            MacroBase::UnrecognizedMacro(box UnrecognizedMacro { macro_type, args }) => {
                write!(f, "<unknown>({}) {}", macro_type, args.join(" "))
            }
        }
    }
}

impl<P: ProvidersLabelMaybeConfigured> Display for StringWithMacrosPart<P> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            StringWithMacrosPart::String(s) => write!(f, "{}", s),
            StringWithMacrosPart::Macro(write_to_file, m) => {
                write!(f, "$({}{})", if *write_to_file { "@" } else { "" }, m)
            }
        }
    }
}

impl StringWithMacrosPart<ProvidersLabel> {
    pub(crate) fn configure(
        &self,
        ctx: &dyn AttrConfigurationContext,
    ) -> anyhow::Result<ConfiguredStringWithMacrosPart> {
        match self {
            StringWithMacrosPart::String(val) => {
                Ok(ConfiguredStringWithMacrosPart::String(val.clone()))
            }
            StringWithMacrosPart::Macro(write_to_file, unconfigured) => Ok(
                ConfiguredStringWithMacrosPart::Macro(*write_to_file, unconfigured.configure(ctx)?),
            ),
        }
    }
}

impl<P: ProvidersLabelMaybeConfigured> Display for StringWithMacros<P> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::StringPart(part) => {
                write!(f, "{}", part)?;
            }
            Self::ManyParts(ref parts) => {
                for part in parts.iter() {
                    write!(f, "{}", part)?;
                }
            }
        }

        Ok(())
    }
}

/// Represents the type of a query placeholder (e.g. query_outputs, query_targets, query_targets_and_outputs).
#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub enum QueryExpansion {
    Output,
    Target,
    /// Holds the separator used between a target and its output. If not provided, they will be space-separated.
    TargetAndOutput(Option<String>),
}

impl Display for QueryExpansion {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            QueryExpansion::Output => f.write_str("query_outputs ")?,
            QueryExpansion::Target => f.write_str("query_targets ")?,
            QueryExpansion::TargetAndOutput(Some(separator)) => {
                write!(f, "query_targets_and_outputs '{}' ", separator)?;
            }
            QueryExpansion::TargetAndOutput(None) => f.write_str("query_targets_and_outputs ")?,
        };

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

//! Handles parsing macros out of an attrs.arg()
//!
//! Much of this behavior is inherited from buckv1, which is documented
//! here <https://buck.build/function/string_parameter_macros.html> and here
//! <https://github.com/facebook/buck/blob/5bc82b7c90f1a5c5ac70e2de7d2c2170c289ee79/src/com/facebook/buck/core/macros/MacroFinderAutomaton.java>
//!
//! Many rule attributes that accept strings actually accept an attrs.arg(). These allow
//! users to specify "String parameter macros" that are placeholders that buck will expand
//! to their final values later in the build. Common examples of these would `$(location //some:target)`
//! or `$(exe //some:target)`.
//!
//! This parser handles parsing a string with zero or more string parameter macros into a parsed
//! structure separating the literals from the macros and holding the types and args for each macro.
//!
//! A macro is defined to start at any `$(` with zero or an even number of preceding `\`. I.e. all of `x$(`,
//! `x\\$(`, and `x\\\\$(` start a macro, but none of `x\$(`, `x\\\$(` or `x\\\\\$(` start one.
//!
//! Within an escaped macro (a section starting with `\$(`), we ignore everything until a matching closing `)`.
//! If there are any `(` within that section, they will be matched against `)` first (i.e. we maintain a count
//! of opened parens and closing parens and the escaped macro ends when that reaches 0).
//!
//! Once within a macro, an `@` can appear just before the type to indicate that the value of the macro should
//! be written to a file and the command should be passed `@<filename>` where `<filename>` is the file where the
//! macro value has been written.
//!
//! A macro consists of that optional write-to-file signifier followed by a type and then zero or more
//! space-separated args.
//!
//! A macro type must be non-empty and consists of characters in `[a-zA-Z0-9_]` followed by whitespace or the
//! macro-ending ')'.
//!
//! Macro args are separated by whitespace. There are two types of args, "quoted" and "unquoted".
//!
//! A "quoted" arg starts with either `'` or `"`. It continues until it reaches the first matching
//! non-`\`-escaped `'` or `"` (as appropriate). The value of the arg will replace all `\`-escaped chars
//! with just the char itself. I.e. `\\` becomes `\`, `\"` becomes `"`, `\a` becomes `a`.
//!
//! An unquoted arg is normally terminated by the first whitespace or `)`. However, unquoted args have special
//! behavior when they encounter any `(` that will make them continue until the parens are balanced. While an
//! unquoted arg has seen more `(` than `)`, it will not be terminated by whitespace or `)`.
//!
//! We diverge from buckv1 in a handful of known ways.
//! 1. buck1 allows pretty much any characters to appear in a macro type. We restrict it to alphanumeric and `_`.
//! 2. buck1 disallows spaces entirely within unquoted args. This can be surprising. Unquoted args are generally used for
//! the query part of query macros, and in other contexts where buck accepts queries it allows whitespace.
//! Example, the string "$(query_outputs deps(//some:target, 3))" would be rejected by buck1 due to the space before the 3.
//!
//!
//! Some examples:
//!
//! ```console
//! some string $(location //some:target)
//! more $(complex with(some(function(calls))) and(more)) something $(exe)
//! this should have no backslash \$()
//! $(macro "some ( weird arg")
//! ```

// TODO(cjhopman): There's a lot awkward about this definition. IMO, we especially should remove the "unquoted" arg's special
// handling for parens. If someone wants a query macro, they can quote the arg. It's also kind of odd that we only really treat
// the string `\$(` as escaped in the outer string (we replace it with `$(`) but don't just generally treat `\` as an escaping
// character.
use std::result;

use thiserror::Error;

#[derive(Debug, PartialEq)]
pub struct ParsedMacro {
    /// Indicates that the value of the macro should be written to a file and the command should be passed `@<filename>` where
    /// `<filename>` is the file where the macro value has been written.
    pub write_to_file: bool,
    /// "type" of the macro. Ex. "location", "exe".
    pub macro_type: String,
    /// Macro args. Ex $(some_macro //a:b //c:d) would have ["//a:b", "//c:d"] as the args.
    pub args: Vec<String>,
}

#[derive(Debug, PartialEq)]
pub enum ArgItem {
    String(Box<str>),
    Macro(ParsedMacro),
}

#[derive(Debug, PartialEq)]
pub struct ParsedArg(pub(crate) Vec<ArgItem>);

impl ParsedArg {
    pub fn into_items(self) -> Vec<ArgItem> {
        self.0
    }
}

/// Parsed a string into a structure with macros and their types and args identified.
pub fn parse_macros(input: &str) -> anyhow::Result<ParsedArg> {
    match read(input) {
        Ok((remaining, value)) => {
            assert!(
                remaining.is_empty(),
                "somehow had remaining stuff after a successful macro parse. Had `{}` remaining.",
                remaining
            );

            Ok(value)
        }
        Err((remaining, err)) => {
            // remaining indicates what we were parsing when something went wrong. We process strictly left-to-right
            // and so it's easy to compute where that is in input.
            let error_start = input.len() - remaining.len();

            // We truncate the string to 65 chars, max 15 before.
            let message_start = std::cmp::max(15, error_start) - 15;
            let message_end = std::cmp::min(input.len(), message_start + 60);

            // We'll print a line of `^` to indicate where the processing failed.
            let pointer = error_start - message_start;

            Err(anyhow::anyhow!(
                "E:{}\n    V:{}\n    M:{}{}\n",
                err,
                &input[message_start..message_end],
                " ".repeat(pointer),
                "^".repeat(message_end - message_start - pointer)
            ))
        }
    }
}

#[derive(Debug, Error)]
enum ArgParseError {
    #[error("Unfinished quoted arg, expected a `{0}`")]
    UnfinishedQuotedArg(char),
    #[error("Unfinished non-quoted arg")]
    UnfinishedUnquotedArg,
    #[error("Unfinished macro invocation")]
    UnfinishedMacroInvocation,
    #[error("Invalid character in macro type, expected [a-zA-Z0-9_]")]
    MacroTypeInvalidChar,
    #[error("Unfinished macro type, expected a ' ' or ')'")]
    MacroTypeUnfinished,
    #[error("Macro type was missing")]
    MacroTypeMissing,
}

/// An Error includes the slice where the error occurred. As we process strictly
/// left-to-right, this slice allows us to easily identify the location of the error in the original input.
type Error<'a> = (&'a str, ArgParseError);

/// A Result includes both some parsed type and a slice of what remains to be parsed.
type Result<'a, T> = result::Result<(&'a str, T), Error<'a>>;

// We diverge slightly from buckv1 here.
//
// See https://www.internalfb.com/diffs/D3917438?transaction_fbid=1890520777849195 where that decision in v1 was questioned.

fn consume_whitespace(input: &str) -> &str {
    input.trim_start_matches(|c: char| c.is_ascii_whitespace())
}

fn unescape(input: &str) -> String {
    let mut fixed = String::with_capacity(input.len());
    let mut escaped = false;
    for c in input.chars() {
        if escaped {
            fixed.push(c);
            escaped = false;
        } else if c == '\\' {
            escaped = true;
        } else {
            fixed.push(c);
        }
    }
    fixed
}

// An unquoted arg has tricky behavior to support more complex query macros. While parsing the arg, if we encounter
// any (unescaped) left-paren, the arg will be considered to continue until we've seen a balanced set of left and
// right parens (it will end immediately at that point).
//
// Examples:
// ```ignore
// $(macro deps(123)) -> arg1 == "deps(123)"
// $(macro deps(123)abc) -> arg1 == "deps(123)", arg2=abc
// $(macro a(b(c(d)))) -> arg1 == "a(b(b(d))))"
// ```
// TODO: that second case seems like a bug in buckv1 and it should be just a single arg. We've preserved the v1 behavior.
fn read_unquoted_arg(input: &str) -> Result<String> {
    let mut has_escapes = false;
    let mut paren_count = 0;
    let mut char_indices = input.char_indices();
    let mut pos = char_indices.next();

    while let Some((_, c)) = pos {
        match c {
            ')' if paren_count == 0 => {
                break;
            }
            ')' => {
                paren_count -= 1;
            }
            '(' => {
                paren_count += 1;
            }
            '\\' => {
                has_escapes = true;
                char_indices.next();
            }
            v if v.is_ascii_whitespace() => {
                if paren_count == 0 {
                    break;
                }
            }
            _ => {}
        }
        pos = char_indices.next();
    }

    match pos {
        None => Err((input, ArgParseError::UnfinishedUnquotedArg)),
        Some((pos, _)) => {
            let (arg, rest) = (&input[0..pos], &input[pos..]);
            // The common case, by far, is that nothing is escaped.
            if !has_escapes {
                Ok((rest, arg.to_owned()))
            } else {
                Ok((rest, unescape(arg)))
            }
        }
    }
}

// A quoted arg is simple, we will read until we find a matching un-escaped quote.
fn read_quoted_arg(input: &str, quote: char) -> Result<String> {
    let mut has_escapes = false;
    let mut char_indices = input.char_indices();
    let mut pos = char_indices.next();
    while let Some((_, c)) = pos {
        match c {
            v if v == quote => {
                break;
            }
            '\\' => {
                has_escapes = true;
                char_indices.next();
            }
            _ => {}
        }
        pos = char_indices.next();
    }

    match pos {
        None => Err((input, ArgParseError::UnfinishedQuotedArg(quote))),
        Some((pos, _)) => {
            let (arg, rest) = (&input[0..pos], &input[(pos + 1)..]);
            // The common case, by far, is that nothing is escaped.
            if !has_escapes {
                Ok((rest, arg.to_owned()))
            } else {
                Ok((rest, unescape(arg)))
            }
        }
    }
}

fn read_macro_arg(input: &str) -> Result<String> {
    if let Some(rest) = input.strip_prefix('\'') {
        read_quoted_arg(rest, '\'')
    } else if let Some(rest) = input.strip_prefix('"') {
        read_quoted_arg(rest, '"')
    } else {
        read_unquoted_arg(input)
    }
}

// This is much stricter than buckv1. v1 allows nearly any character in the macro type. We allow only alphanumeric, '-', and '_'.
fn read_macro_type(input: &str) -> Result<String> {
    match input.find(|c: char| c.is_whitespace() || c == ')') {
        None => Err((input, ArgParseError::MacroTypeUnfinished)),
        Some(0) => Err((input, ArgParseError::MacroTypeMissing)),
        Some(pos) => {
            let macro_type = &input[..pos];
            if let Some(pos) =
                macro_type.find(|c: char| (!c.is_ascii_alphanumeric() && c != '_' && c != '-'))
            {
                Err((&input[pos..], ArgParseError::MacroTypeInvalidChar))
            } else {
                Ok((&input[pos..], macro_type.to_owned()))
            }
        }
    }
}

// A macro consists of a macro name followed by any number of space separated macro args eventually terminated by a closing paren.
fn read_macro(input: &str) -> Result<ParsedMacro> {
    // We only take the leading `$(` so that error message point to that instead of the beginning of the type.
    let working = input.strip_prefix("$(").unwrap_or_else(|| {
        panic!(
            "the caller should've ensure that we're actually at the start of a macro. Got `{}`",
            input,
        )
    });
    let (write_to_file, working) = match working.strip_prefix('@') {
        Some(working) => (true, working),
        None => (false, working),
    };
    let (working, macro_type) = read_macro_type(working)?;
    let mut working = consume_whitespace(working);

    let mut args = Vec::new();
    while let Some(c) = working.chars().next() {
        if c == ')' {
            return Ok((
                &working[1..],
                ParsedMacro {
                    write_to_file,
                    macro_type,
                    args,
                },
            ));
        }

        let (remaining, arg) = read_macro_arg(working)?;
        working = consume_whitespace(remaining);
        args.push(arg);
    }

    Err((input, ArgParseError::UnfinishedMacroInvocation))
}

fn read_literal_opt(input: &str) -> Result<Option<Box<str>>> {
    // To match v1's approach to this is non-trivial. To start a macro requires a `$(` with an
    // even number of preceding `\` (as the first of each pair escapes the second). If there's
    // an odd number of preceding `\`, one of them should be removed.

    let mut char_indices = input.char_indices();
    let mut indices_to_drop = Vec::new();
    enum State {
        Searching,
        Escaped,
        Dollar,
        EscapedDollar,
        // Tracks the number of left parens that need a matching right paren.
        EscapedMacro(usize),
    }

    use State::*;

    let mut state = Searching;

    let mut pos = char_indices.next();
    while let Some((idx, c)) = pos {
        state = match (state, c) {
            (Searching, '\\') => Escaped,
            (Dollar, '\\') => Escaped,
            (EscapedDollar, '\\') => Escaped,

            (Escaped, '$') => EscapedDollar,
            (Escaped, _) => Searching,

            (Searching, '$') => Dollar,
            (Searching, _) => Searching,

            (Dollar, '(') => {
                // found a macro
                break;
            }
            (EscapedDollar, '(') => {
                // Indicates we hit the sequence `\$(` and we need to drop the `\`
                indices_to_drop.push(idx - 2);
                EscapedMacro(1)
            }
            (EscapedMacro(1), ')') => Searching,
            (EscapedMacro(n), ')') => EscapedMacro(n - 1),
            (EscapedMacro(n), '(') => EscapedMacro(n + 1),
            (EscapedMacro(n), _) => EscapedMacro(n),
            // Note that '(' and '\' is handled for both of thes above.
            (Dollar, _) => Searching,
            (EscapedDollar, _) => Searching,
        };
        pos = char_indices.next();
    }

    let literal_end = pos.map_or_else(|| input.len(), |p| p.0 - 1);

    if literal_end > 0 {
        // TODO: fixup escaped things.
        let literal = if indices_to_drop.is_empty() {
            input[..literal_end].to_owned()
        } else {
            let mut literal = String::with_capacity(literal_end);
            literal.push_str(&input[..indices_to_drop[0]]);
            for window in indices_to_drop.windows(2) {
                literal.push_str(&input[(window[0] + 1)..window[1]]);
            }
            literal.push_str(&input[(indices_to_drop.last().unwrap() + 1)..literal_end]);
            literal
        };
        Ok((&input[literal_end..], Some(literal.into_boxed_str())))
    } else {
        Ok((input, None))
    }
}

fn read(input: &str) -> Result<ParsedArg> {
    let (remaining, literal) = read_literal_opt(input)?;
    let mut working = remaining;

    if working.is_empty() {
        return Ok((
            "",
            ParsedArg(vec![ArgItem::String(literal.unwrap_or_else(|| "".into()))]),
        ));
    }

    let mut complex = Vec::new();
    if let Some(literal) = literal {
        complex.push(ArgItem::String(literal));
    }

    while !working.is_empty() {
        let (remaining, literal) = read_literal_opt(working)?;
        working = remaining;
        if let Some(literal) = literal {
            complex.push(ArgItem::String(literal));
        }

        if !working.is_empty() {
            // we must be at the beginning of a macro.
            let (remaining, parsed_macro) = read_macro(working)?;
            working = remaining;
            complex.push(ArgItem::Macro(parsed_macro));
        }
    }

    Ok(("", ParsedArg(complex)))
}

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

    #[derive(Debug)]
    #[allow(unused)] // for tests debug display
    struct OwnedError {
        remaining: String,
        error: ArgParseError,
    }

    impl From<(&'_ str, ArgParseError)> for OwnedError {
        fn from(err: (&'_ str, ArgParseError)) -> Self {
            OwnedError {
                remaining: err.0.to_owned(),
                error: err.1,
            }
        }
    }

    #[test]
    fn test_unquoted() -> result::Result<(), OwnedError> {
        assert_eq!(read_macro_arg("abcd ")?, (" ", "abcd".to_owned()));
        assert_eq!(read_macro_arg("abcd)")?, (")", "abcd".to_owned()));
        assert_eq!(
            read_macro_arg("deps(//some:target))")?,
            (")", "deps(//some:target)".to_owned())
        );
        assert_eq!(
            read_macro_arg("deps(//some:target, 3, first_order_deps()) ")?,
            (" ", "deps(//some:target, 3, first_order_deps())".to_owned())
        );

        Ok(())
    }

    #[test]
    fn test_quoted() -> result::Result<(), OwnedError> {
        assert_eq!(read_macro_arg("' abcd )'")?, ("", " abcd )".to_owned()));
        assert_eq!(read_macro_arg("' ab%cd )'")?, ("", " ab%cd )".to_owned()));
        assert_eq!(
            read_macro_arg(r#"" \"\$\\ ")"#)?,
            (")", r#" "$\ "#.to_owned())
        );

        Ok(())
    }

    #[test]
    fn test_macro_type() -> result::Result<(), OwnedError> {
        assert_eq!(read_macro_type("name ")?, (" ", "name".to_owned()));
        assert_eq!(read_macro_type("name)")?, (")", "name".to_owned()));
        assert_eq!(
            read_macro_type("platform-name ")?,
            (" ", "platform-name".to_owned())
        );
        assert!(read_macro_type("platform%name ").is_err());
        assert!(read_macro_type("platform$name ").is_err());
        Ok(())
    }

    #[test]
    fn test_parse_macros() -> anyhow::Result<()> {
        assert_eq!(
            ParsedArg(vec![ArgItem::String("contains no $(macros)".into())]),
            parse_macros(r#"contains no \$(macros)"#)?
        );

        let macro1 = || {
            ArgItem::Macro(ParsedMacro {
                macro_type: "exe".to_owned(),
                args: Vec::new(),
                write_to_file: false,
            })
        };
        let macro2 = || {
            ArgItem::Macro(ParsedMacro {
                macro_type: "location".to_owned(),
                args: vec!["//some:target".to_owned()],
                write_to_file: true,
            })
        };

        assert_eq!(
            ParsedArg(vec![macro1(), ArgItem::String(" and ".into()), macro2()]),
            parse_macros(r#"$(exe) and $(@location //some:target)"#)?
        );

        let macro3 = ArgItem::Macro(ParsedMacro {
            macro_type: "zzz".to_owned(),
            args: vec![
                "1".to_owned(),
                r#"yyy(abc def)"#.to_owned(),
                r#"\\\"#.to_owned(),
            ],
            write_to_file: false,
        });

        assert_eq!(
            ParsedArg(vec![macro1(), macro2(), macro3]),
            parse_macros(
                r#"$(exe)$(@location     //some:target)$(zzz  '\1' yyy(abc def) "\\\\\\")"#
            )?
        );

        assert_eq!(
            ParsedArg(vec![ArgItem::String(r#"$(echo -n "$NEW" | sed -e :a -e "s|^.\{1,$(expr "$(echo -n "$OLD" | wc -c)" - 1)\}$|&/|;ta")"#.into())]),
            parse_macros(r#"\$(echo -n "$NEW" | sed -e :a -e "s|^.\{1,$(expr "$(echo -n "$OLD" | wc -c)" - 1)\}$|&/|;ta")"#)?
        );

        Ok(())
    }
}