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

use allocative::Allocative;
use anyhow::Context;
use buck2_core::buck_path::path::BuckPathRef;
use buck2_core::configuration::config_setting::ConfigSettingData;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::package::PackageLabel;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::target::label::TargetLabel;
use buck2_util::arc_str::ArcSlice;
use dupe::Dupe;
use dupe::IterDupedExt;
use gazebo::prelude::SliceExt;
use itertools::Itertools;
use serde::Serialize;
use serde::Serializer;
use serde_json::to_value;
use starlark_map::StarlarkHasherBuilder;

use crate::attrs::attr_type::any_matches::AnyMatches;
use crate::attrs::attr_type::arg::StringWithMacros;
use crate::attrs::attr_type::attr_config::source_file_display;
use crate::attrs::attr_type::bool::BoolLiteral;
use crate::attrs::attr_type::configuration_dep::ConfigurationDepAttrType;
use crate::attrs::attr_type::configured_dep::ExplicitConfiguredDepAttrType;
use crate::attrs::attr_type::configured_dep::UnconfiguredExplicitConfiguredDep;
use crate::attrs::attr_type::dep::DepAttr;
use crate::attrs::attr_type::dep::ExplicitConfiguredDepMaybeConfigured;
use crate::attrs::attr_type::dict::DictLiteral;
use crate::attrs::attr_type::label::LabelAttrType;
use crate::attrs::attr_type::list::ListLiteral;
use crate::attrs::attr_type::query::QueryAttr;
use crate::attrs::attr_type::string::StringLiteral;
use crate::attrs::attr_type::tuple::TupleLiteral;
use crate::attrs::attr_type::AttrType;
use crate::attrs::coerced_attr_with_type::CoercedAttrWithType;
use crate::attrs::coerced_path::CoercedPath;
use crate::attrs::configuration_context::AttrConfigurationContext;
use crate::attrs::configured_attr::ConfiguredAttr;
use crate::attrs::display::AttrDisplayWithContext;
use crate::attrs::display::AttrDisplayWithContextExt;
use crate::attrs::fmt_context::AttrFmtContext;
use crate::attrs::json::ToJsonWithContext;
use crate::attrs::serialize::AttrSerializeWithContext;
use crate::attrs::traversal::CoercedAttrTraversal;
use crate::visibility::VisibilitySpecification;
use crate::visibility::WithinViewSpecification;

#[derive(thiserror::Error, Debug)]
enum SelectError {
    #[error("None of {} conditions matched configuration `{}` and no default was set:\n{}",
        .1.len(),
        .0,
        .1.iter().map(| s | format ! ("  {}", s)).join("\n"),
    )]
    MissingDefault(ConfigurationData, Vec<TargetLabel>),
    #[error(
        "Both select keys `{0}` and `{1}` match the configuration, but neither is more specific"
    )]
    TwoKeysDoNotRefineEachOther(String, String),
    #[error("concat with no items (internal error)")]
    ConcatEmpty,
    #[error("duplicate key `{0}` in `select()`")]
    DuplicateKey(String),
}

#[derive(Debug, thiserror::Error)]
enum CoercedAttrError {
    #[error("Inconsistent number of elements in tuple")]
    InconsistentTupleLength,
}

enum CoercedSelectorKeyRef<'a> {
    Target(&'a TargetLabel),
    Default,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Allocative)]
pub struct CoercedSelector {
    pub(crate) entries: ArcSlice<(TargetLabel, CoercedAttr)>,
    pub(crate) default: Option<CoercedAttr>,
}

impl CoercedSelector {
    pub fn new(
        entries: ArcSlice<(TargetLabel, CoercedAttr)>,
        default: Option<CoercedAttr>,
    ) -> anyhow::Result<CoercedSelector> {
        Self::check_all_keys_unique(&entries)?;
        Ok(CoercedSelector { entries, default })
    }

    fn check_all_keys_unique(entries: &[(TargetLabel, CoercedAttr)]) -> anyhow::Result<()> {
        // This is possible when select keys are specified like:
        // ```
        // select({
        //   "cell//foo:bar": 2,
        //   "//foo:bar": 1,
        //   ":bar": 3,
        // })
        // ```
        // Keys are unique strings, but resolved to the same target.

        // Quadratic is cheaper than hashing for small `N`.
        // For 32 entries we do 496 comparisons, which is cheaper than 32 hashing operations.
        if entries.len() <= 32 {
            for i in 0..entries.len() {
                for j in i + 1..entries.len() {
                    if entries[i].0 == entries[j].0 {
                        return Err(SelectError::DuplicateKey(entries[i].0.to_string()).into());
                    }
                }
            }
        } else {
            let mut visited_keys: HashSet<&TargetLabel, _> =
                HashSet::with_capacity_and_hasher(entries.len(), StarlarkHasherBuilder);
            for (k, _) in entries {
                if !visited_keys.insert(k) {
                    return Err(SelectError::DuplicateKey(k.to_string()).into());
                }
            }
        }

        Ok(())
    }

    fn all_entries(&self) -> impl Iterator<Item = (CoercedSelectorKeyRef, &CoercedAttr)> {
        self.entries
            .iter()
            .map(|(k, v)| (CoercedSelectorKeyRef::Target(k), v))
            .chain(
                self.default
                    .iter()
                    .map(|default| (CoercedSelectorKeyRef::Default, default)),
            )
    }

    fn all_values(&self) -> impl Iterator<Item = &'_ CoercedAttr> {
        self.all_entries().map(|(_, v)| v)
    }
}

/// CoercedAttr is the "coerced" representation of an attribute. It has been type-checked and converted to
/// specific types (for example, where we expect target-like things, it has been converted to something like
/// a TargetLabel or ProvidersLabel).
///
/// CoercedAttr  provides support for the `select()` function. All coerced data is
/// potentially represented by a select that represents possibly different
/// configured representations.
///
/// CoercedData::Concat supports a representation for when a selectable is added
/// to something. Not all types support this case and those will return an error
/// during coercion and not ever use the ::Concat case.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Allocative)]
pub enum CoercedAttr {
    Selector(Box<CoercedSelector>),
    Concat(Box<[Self]>),

    Bool(BoolLiteral),
    Int(i32),
    // Note we store `String`, not `Arc<str>` here, because we store full attributes
    // in unconfigured target node, but configured target node is basically a pair
    // (reference to unconfigured target node, configuration).
    //
    // Configured attributes are created on demand and destroyed immediately after use.
    //
    // So when working with configured attributes with pay with CPU for string copies,
    // but don't increase total memory usage, because these string copies are short living.
    String(StringLiteral),
    // Like String, but drawn from a set of variants, so doesn't support concat
    EnumVariant(StringLiteral),
    List(ListLiteral<CoercedAttr>),
    Tuple(TupleLiteral<CoercedAttr>),
    Dict(DictLiteral<CoercedAttr>),
    None,
    // NOTE: unlike deps, labels are not traversed, as they are typically used in lieu of deps in
    // cases that would cause cycles.
    OneOf(
        Box<Self>,
        // Index of matched oneof attr type variant.
        u32,
    ),
    Visibility(VisibilitySpecification),
    WithinView(WithinViewSpecification),
    ExplicitConfiguredDep(Box<UnconfiguredExplicitConfiguredDep>),
    SplitTransitionDep(ProvidersLabel),
    ConfiguredDep(Box<DepAttr<ConfiguredProvidersLabel>>),
    ConfigurationDep(TargetLabel),
    Dep(ProvidersLabel),
    SourceLabel(ProvidersLabel),
    // NOTE: unlike deps, labels are not traversed, as they are typically used in lieu of deps in
    // cases that would cause cycles.
    Label(ProvidersLabel),
    Arg(StringWithMacros<ProvidersLabel>),
    Query(Box<QueryAttr<ProvidersLabel>>),
    SourceFile(CoercedPath),
}

// This is just to help understand any impact that changes have to the size of this.
// We store a lot of these, so we try to keep it to a reasonable size.
static_assertions::assert_eq_size!(CoercedAttr, [usize; 3]);

/// Provides roughly the stringified version of the starlark code that would produce this attr. For example, a dictionary
/// of string keys and values may result in `{"key1":"value1","key2":"value2"}` (note that strings will explicitly include
/// the wrapping `"`).
impl AttrDisplayWithContext for CoercedAttr {
    fn fmt(&self, ctx: &AttrFmtContext, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CoercedAttr::Selector(s) => {
                write!(f, "select(")?;
                for (i, (key, value)) in s.all_entries().enumerate() {
                    if i > 0 {
                        write!(f, ",")?;
                    }
                    match key {
                        CoercedSelectorKeyRef::Target(k) => {
                            write!(f, "\"{}\"={}", k, value.as_display(ctx))?;
                        }
                        CoercedSelectorKeyRef::Default => {
                            write!(f, "\"DEFAULT\"={}", value.as_display(ctx))?;
                        }
                    }
                }
                write!(f, ")")?;
                Ok(())
            }
            CoercedAttr::Concat(items) => {
                write!(f, "{}", items.iter().map(|a| a.as_display(ctx)).format("+"))
            }
            CoercedAttr::Bool(v) => {
                write!(f, "{}", v)
            }
            CoercedAttr::Int(v) => {
                write!(f, "{}", v)
            }
            CoercedAttr::String(v) | CoercedAttr::EnumVariant(v) => Display::fmt(v, f),
            CoercedAttr::List(list) => AttrDisplayWithContext::fmt(list, ctx, f),
            CoercedAttr::Tuple(v) => AttrDisplayWithContext::fmt(v, ctx, f),
            CoercedAttr::Dict(v) => AttrDisplayWithContext::fmt(v, ctx, f),
            CoercedAttr::None => write!(f, "None"),
            CoercedAttr::OneOf(box l, _) => AttrDisplayWithContext::fmt(l, ctx, f),
            CoercedAttr::Visibility(v) => Display::fmt(v, f),
            CoercedAttr::WithinView(v) => Display::fmt(v, f),
            CoercedAttr::ExplicitConfiguredDep(e) => Display::fmt(e, f),
            CoercedAttr::SplitTransitionDep(e) => Display::fmt(e, f),
            CoercedAttr::ConfiguredDep(e) => write!(f, "\"{}\"", e),
            CoercedAttr::ConfigurationDep(e) => write!(f, "\"{}\"", e),
            CoercedAttr::Dep(e) => write!(f, "\"{}\"", e),
            CoercedAttr::SourceLabel(e) => write!(f, "\"{}\"", e),
            CoercedAttr::Label(e) => write!(f, "\"{}\"", e),
            CoercedAttr::Arg(e) => write!(f, "\"{}\"", e),
            CoercedAttr::Query(e) => write!(f, "\"{}\"", e.query()),
            CoercedAttr::SourceFile(e) => write!(f, "\"{}\"", source_file_display(ctx, e)),
        }
    }
}

impl AttrSerializeWithContext for CoercedAttr {
    fn serialize_with_ctx<S>(&self, ctx: &AttrFmtContext, s: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // TODO this is inefficient. We should impl Serialize and derive value from this instead.
        self.to_json(ctx)
            .map_err(|e| serde::ser::Error::custom(format!("{}", e)))?
            .serialize(s)
    }
}

impl CoercedAttr {
    /// Converts the coerced attr to a serde_json Value. This is generally just used for debugging or introspective
    /// things, a lot of the types will be dropped without special handling. For example, an artifact will just end
    /// up as the stringified version of its coerced value (i.e. while `//a:b` might represent some list of targets,
    /// in to_json it just appears as the string "//a:b").
    pub fn to_json(&self, ctx: &AttrFmtContext) -> anyhow::Result<serde_json::Value> {
        match self {
            CoercedAttr::Selector(s) => {
                let mut map = serde_json::Map::new();
                for (key, value) in s.all_entries() {
                    match key {
                        CoercedSelectorKeyRef::Target(k) => {
                            map.insert(k.to_string(), value.to_json(ctx)?);
                        }
                        CoercedSelectorKeyRef::Default => {
                            map.insert("DEFAULT".to_owned(), value.to_json(ctx)?);
                        }
                    }
                }
                let select = serde_json::Value::Object(map);

                Ok(serde_json::Value::Object(serde_json::Map::from_iter([
                    (
                        "__type".to_owned(),
                        serde_json::Value::String("selector".to_owned()),
                    ),
                    ("entries".to_owned(), select),
                ])))
            }
            CoercedAttr::Concat(items) => {
                Ok(serde_json::Value::Object(serde_json::Map::from_iter([
                    (
                        "__type".to_owned(),
                        serde_json::Value::String("concat".to_owned()),
                    ),
                    (
                        "items".to_owned(),
                        serde_json::Value::Array(
                            items.try_map(|item| CoercedAttr::to_json(item, ctx))?,
                        ),
                    ),
                ])))
            }
            CoercedAttr::Bool(v) => Ok(to_value(v)?),
            CoercedAttr::Int(v) => Ok(to_value(v)?),
            CoercedAttr::String(v) | CoercedAttr::EnumVariant(v) => Ok(to_value(v)?),
            CoercedAttr::List(list) => list.to_json(ctx),
            CoercedAttr::Tuple(list) => list.to_json(ctx),
            CoercedAttr::Dict(dict) => dict.to_json(ctx),
            CoercedAttr::None => Ok(serde_json::Value::Null),
            CoercedAttr::OneOf(box l, _) => l.to_json(ctx),
            CoercedAttr::Visibility(v) => Ok(v.to_json()),
            CoercedAttr::WithinView(v) => Ok(v.to_json()),
            CoercedAttr::ExplicitConfiguredDep(e) => e.to_json(),
            CoercedAttr::SplitTransitionDep(e) => Ok(to_value(e.to_string())?),
            CoercedAttr::ConfiguredDep(e) => Ok(to_value(e.to_string())?),
            CoercedAttr::ConfigurationDep(e) => Ok(to_value(e.to_string())?),
            CoercedAttr::Dep(e) => Ok(to_value(e.to_string())?),
            CoercedAttr::SourceLabel(e) => Ok(to_value(e.to_string())?),
            CoercedAttr::Label(e) => Ok(to_value(e.to_string())?),
            CoercedAttr::Arg(e) => Ok(to_value(e.to_string())?),
            CoercedAttr::Query(e) => Ok(to_value(e.query())?),
            CoercedAttr::SourceFile(e) => Ok(to_value(source_file_display(ctx, e).to_string())?),
        }
    }

    /// Returns `true` if the result of evaluating this literal can ever be None.
    pub fn may_return_none(&self) -> bool {
        match self {
            CoercedAttr::None => true,
            CoercedAttr::Selector(s) => s.all_values().any(|x| x.may_return_none()),
            CoercedAttr::Concat(_) => false,
            _ => false,
        }
    }

    /// Traverses the coerced attribute and provides the traverser callbacks for all deps (those in select conditions
    /// are passed as configuration deps).
    pub fn traverse<'a>(
        &'a self,
        t: &AttrType,
        pkg: PackageLabel,
        traversal: &mut dyn CoercedAttrTraversal<'a>,
    ) -> anyhow::Result<()> {
        match CoercedAttrWithType::pack(self, t)? {
            CoercedAttrWithType::Selector(CoercedSelector { entries, default }, t) => {
                for (condition, value) in entries.iter() {
                    traversal.configuration_dep(condition)?;
                    value.traverse(t, pkg.dupe(), traversal)?;
                }
                if let Some(v) = default {
                    v.traverse(t, pkg.dupe(), traversal)?;
                }
                Ok(())
            }
            CoercedAttrWithType::Concat(items, t) => {
                for item in items {
                    item.traverse(t, pkg.dupe(), traversal)?;
                }
                Ok(())
            }

            CoercedAttrWithType::None => Ok(()),
            CoercedAttrWithType::Some(attr, t) => attr.traverse(&t.inner, pkg.dupe(), traversal),

            CoercedAttrWithType::AnyList(list) => {
                for v in list.iter() {
                    // This is no-op now, but any may contain selects in the future.
                    v.traverse(t, pkg.dupe(), traversal)?;
                }
                Ok(())
            }
            CoercedAttrWithType::AnyTuple(tuple) => {
                for v in tuple.iter() {
                    v.traverse(t, pkg.dupe(), traversal)?;
                }
                Ok(())
            }
            CoercedAttrWithType::AnyDict(dict) => {
                for (k, v) in dict.iter() {
                    k.traverse(t, pkg.dupe(), traversal)?;
                    v.traverse(t, pkg.dupe(), traversal)?;
                }
                Ok(())
            }

            CoercedAttrWithType::Bool(..) => Ok(()),
            CoercedAttrWithType::Int(..) => Ok(()),
            CoercedAttrWithType::String(..) => Ok(()),
            CoercedAttrWithType::EnumVariant(..) => Ok(()),
            CoercedAttrWithType::List(list, t) => {
                for v in list.iter() {
                    v.traverse(&t.inner, pkg.dupe(), traversal)?;
                }
                Ok(())
            }
            CoercedAttrWithType::Tuple(list, t) => {
                if list.len() != t.xs.len() {
                    return Err(CoercedAttrError::InconsistentTupleLength.into());
                }

                for (v, vt) in list.iter().zip(&t.xs) {
                    v.traverse(vt, pkg.dupe(), traversal)?;
                }
                Ok(())
            }
            CoercedAttrWithType::Dict(dict, t) => {
                for (k, v) in dict.iter() {
                    k.traverse(&t.key, pkg.dupe(), traversal)?;
                    v.traverse(&t.value, pkg.dupe(), traversal)?;
                }
                Ok(())
            }

            CoercedAttrWithType::OneOf(l, i, t) => {
                let item_type = t.xs.get(i as usize).context("invalid enum")?;
                l.traverse(item_type, pkg, traversal)
            }
            CoercedAttrWithType::Visibility(..) => Ok(()),
            CoercedAttrWithType::WithinView(..) => Ok(()),
            CoercedAttrWithType::ExplicitConfiguredDep(dep, _t) => dep.traverse(traversal),
            CoercedAttrWithType::SplitTransitionDep(dep, t) => {
                traversal.split_transition_dep(dep.target(), &t.transition)
            }
            CoercedAttrWithType::ConfiguredDep(dep) => {
                traversal.dep(dep.label.target().unconfigured())
            }
            CoercedAttrWithType::ConfigurationDep(dep, _t) => traversal.configuration_dep(dep),
            CoercedAttrWithType::Dep(dep, t) => {
                DepAttr::<ProvidersLabel>::traverse(dep, t, traversal)
            }
            CoercedAttrWithType::SourceLabel(s, _t) => traversal.dep(s.target()),
            CoercedAttrWithType::Label(label, _t) => traversal.label(label),
            CoercedAttrWithType::Arg(arg, _t) => arg.traverse(traversal),
            CoercedAttrWithType::Query(query, _t) => query.traverse(traversal),
            CoercedAttrWithType::SourceFile(source, _t) => {
                for x in source.inputs() {
                    traversal.input(BuckPathRef::new(pkg.dupe(), x))?;
                }
                Ok(())
            }
        }
    }

    /// If more than one select key matches, select the most specific.
    pub fn select_the_most_specific<'a>(
        ctx: &dyn AttrConfigurationContext,
        select_entries: &'a [(TargetLabel, CoercedAttr)],
    ) -> anyhow::Result<Option<&'a CoercedAttr>> {
        let mut matching: Option<(&TargetLabel, &ConfigSettingData, &CoercedAttr)> = None;
        for (k, v) in select_entries {
            matching = match (ctx.matches(k), matching) {
                (None, matching) => matching,
                (Some(conf), None) => Some((k, conf, v)),
                (Some(conf), Some((prev_k, prev_conf, prev_v))) => {
                    if conf.refines(prev_conf) {
                        Some((k, conf, v))
                    } else if prev_conf.refines(conf) {
                        Some((prev_k, prev_conf, prev_v))
                    } else {
                        return Err(SelectError::TwoKeysDoNotRefineEachOther(
                            prev_k.to_string(),
                            k.to_string(),
                        )
                        .into());
                    }
                }
            }
        }
        Ok(matching.map(|(_k, _conf, v)| v))
    }

    fn select<'a>(
        ctx: &dyn AttrConfigurationContext,
        select: &'a CoercedSelector,
    ) -> anyhow::Result<&'a CoercedAttr> {
        let CoercedSelector { entries, default } = select;
        if let Some(v) = Self::select_the_most_specific(ctx, entries)? {
            Ok(v)
        } else {
            default.as_ref().ok_or_else(|| {
                SelectError::MissingDefault(
                    ctx.cfg().cfg().dupe(),
                    entries.iter().map(|(k, _)| k).duped().collect(),
                )
                .into()
            })
        }
    }

    /// Returns the "configured" representation of the attribute in the provided context.
    /// This handles the resolution of the select() conditions and delegates to
    /// the actual attr type for handling any appropriate configuration-time
    /// processing.
    pub fn configure(
        &self,
        ty: &AttrType,
        ctx: &dyn AttrConfigurationContext,
    ) -> anyhow::Result<ConfiguredAttr> {
        Ok(match CoercedAttrWithType::pack(self, ty)? {
            CoercedAttrWithType::Selector(select, t) => {
                Self::select(ctx, select)?.configure(t, ctx)?
            }
            CoercedAttrWithType::Concat(items, t) => {
                let singleton = items.len() == 1;
                let mut it = items.iter().map(|item| item.configure(t, ctx));
                let first = it.next().ok_or(SelectError::ConcatEmpty)??;
                if singleton {
                    first
                } else {
                    first.concat(t, &mut it)?
                }
            }

            CoercedAttrWithType::AnyList(list) => {
                ConfiguredAttr::List(ListLiteral(list.try_map(|v| v.configure(ty, ctx))?.into()))
            }
            CoercedAttrWithType::AnyTuple(tuple) => ConfiguredAttr::Tuple(TupleLiteral(
                tuple.try_map(|v| v.configure(ty, ctx))?.into(),
            )),
            CoercedAttrWithType::AnyDict(dict) => ConfiguredAttr::Dict(DictLiteral(
                dict.try_map(|(k, v)| {
                    let k2 = k.configure(ty, ctx)?;
                    let v2 = v.configure(ty, ctx)?;
                    anyhow::Ok((k2, v2))
                })?
                .into(),
            )),

            CoercedAttrWithType::Bool(v, _t) => ConfiguredAttr::Bool(v),
            CoercedAttrWithType::Int(v, _t) => ConfiguredAttr::Int(v),
            CoercedAttrWithType::String(v, _t) => ConfiguredAttr::String(v.dupe()),
            CoercedAttrWithType::EnumVariant(v, _t) => ConfiguredAttr::EnumVariant(v.dupe()),
            CoercedAttrWithType::List(list, t) => ConfiguredAttr::List(ListLiteral(
                list.try_map(|v| v.configure(&t.inner, ctx))?.into(),
            )),
            CoercedAttrWithType::Tuple(list, t) => {
                if list.len() != t.xs.len() {
                    return Err(CoercedAttrError::InconsistentTupleLength.into());
                }
                ConfiguredAttr::Tuple(TupleLiteral(
                    list.iter()
                        .zip(&t.xs)
                        .map(|(v, vt)| v.configure(vt, ctx))
                        .collect::<anyhow::Result<_>>()?,
                ))
            }
            CoercedAttrWithType::Dict(dict, t) => ConfiguredAttr::Dict(DictLiteral(
                dict.try_map(|(k, v)| {
                    let k2 = k.configure(&t.key, ctx)?;
                    let v2 = v.configure(&t.value, ctx)?;
                    anyhow::Ok((k2, v2))
                })?
                .into(),
            )),
            CoercedAttrWithType::None => ConfiguredAttr::None,
            CoercedAttrWithType::Some(attr, t) => attr.configure(&t.inner, ctx)?,
            CoercedAttrWithType::OneOf(l, i, t) => {
                let item_ty = &t.xs[i as usize];
                let configured = l.configure(item_ty, ctx)?;
                ConfiguredAttr::OneOf(Box::new(configured), i)
            }
            CoercedAttrWithType::Visibility(v, _) => ConfiguredAttr::Visibility(v.clone()),
            CoercedAttrWithType::WithinView(v, _) => ConfiguredAttr::WithinView(v.clone()),
            CoercedAttrWithType::ExplicitConfiguredDep(dep, _) => {
                ExplicitConfiguredDepAttrType::configure(ctx, dep)?
            }
            CoercedAttrWithType::SplitTransitionDep(dep, t) => t.configure(dep, ctx)?,
            CoercedAttrWithType::ConfiguredDep(dep) => ConfiguredAttr::Dep(Box::new(dep.clone())),
            CoercedAttrWithType::ConfigurationDep(dep, _) => {
                ConfigurationDepAttrType::configure(ctx, dep)?
            }
            CoercedAttrWithType::Dep(dep, t) => t.configure(dep, ctx)?,
            CoercedAttrWithType::SourceLabel(source, _) => ConfiguredAttr::SourceLabel(Box::new(
                source.configure_pair(ctx.cfg().cfg_pair().dupe()),
            )),
            CoercedAttrWithType::Label(label, _) => LabelAttrType::configure(ctx, label)?,
            CoercedAttrWithType::Arg(arg, attr_type) => {
                ConfiguredAttr::Arg(arg.configure(ctx, attr_type.anon_target_compatible)?)
            }
            CoercedAttrWithType::Query(query, _) => {
                ConfiguredAttr::Query(Box::new(query.configure(ctx)?))
            }
            CoercedAttrWithType::SourceFile(s, _) => ConfiguredAttr::SourceFile(s.clone()),
        })
    }

    /// Checks if this attr matches the filter. For selectors and container-like things, will return true if any
    /// contained item matches the filter.
    pub fn any_matches(
        &self,
        filter: &dyn Fn(&str) -> anyhow::Result<bool>,
    ) -> anyhow::Result<bool> {
        match self {
            CoercedAttr::Selector(s) => {
                for value in s.all_values() {
                    if value.any_matches(filter)? {
                        return Ok(true);
                    }
                }
                Ok(false)
            }
            CoercedAttr::Concat(items) => {
                for item in &**items {
                    if item.any_matches(filter)? {
                        return Ok(true);
                    }
                }
                Ok(false)
            }
            CoercedAttr::String(v) | CoercedAttr::EnumVariant(v) => filter(v),
            CoercedAttr::List(vals) => vals.any_matches(filter),
            CoercedAttr::Tuple(vals) => vals.any_matches(filter),
            CoercedAttr::Dict(d) => d.any_matches(filter),
            CoercedAttr::None => Ok(false),
            CoercedAttr::Bool(b) => b.any_matches(filter),
            CoercedAttr::Int(i) => filter(&i.to_string()),
            CoercedAttr::OneOf(l, _) => l.any_matches(filter),
            CoercedAttr::Visibility(v) => v.any_matches(filter),
            CoercedAttr::WithinView(v) => v.any_matches(filter),
            CoercedAttr::ExplicitConfiguredDep(e) => e.any_matches(filter),
            CoercedAttr::SplitTransitionDep(e) => filter(&e.to_string()),
            CoercedAttr::ConfiguredDep(e) => filter(&e.to_string()),
            CoercedAttr::ConfigurationDep(e) => filter(&e.to_string()),
            CoercedAttr::Dep(e) => filter(&e.to_string()),
            CoercedAttr::SourceLabel(e) => filter(&e.to_string()),
            CoercedAttr::Label(e) => filter(&e.to_string()),
            CoercedAttr::Arg(e) => filter(&e.to_string()),
            CoercedAttr::Query(e) => filter(e.query()),
            CoercedAttr::SourceFile(e) => filter(&e.path().to_string()),
        }
    }
}

#[cfg(test)]
mod tests {

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

    use crate::attrs::coerced_attr::CoercedAttr;
    use crate::attrs::coerced_attr::CoercedSelector;

    #[test]
    fn test_check_all_keys_unique_small() {
        let a = TargetLabel::testing_parse("foo//:a");
        let b = TargetLabel::testing_parse("foo//:b");
        let c = TargetLabel::testing_parse("foo//:c");
        let attr = CoercedAttr::None;
        let a = (a.dupe(), attr.clone());
        let b = (b.dupe(), attr.clone());
        let c = (c.dupe(), attr);
        assert!(
            CoercedSelector::check_all_keys_unique(&[a.clone(), b.clone(), a.clone()]).is_err()
        );
        assert!(
            CoercedSelector::check_all_keys_unique(&[a.clone(), b.clone(), b.clone()]).is_err()
        );
        assert!(
            CoercedSelector::check_all_keys_unique(&[a.clone(), a.clone(), b.clone()]).is_err()
        );
        assert!(CoercedSelector::check_all_keys_unique(&[a, b, c]).is_ok());
    }

    #[test]
    fn test_check_all_keys_unique_large() {
        let attr = CoercedAttr::None;
        let mut long = (0..100)
            .map(|i| {
                (
                    TargetLabel::testing_parse(&format!("foo//:{}", i)),
                    attr.clone(),
                )
            })
            .collect::<Vec<_>>();
        assert!(CoercedSelector::check_all_keys_unique(&long).is_ok());
        long[10].0 = long[0].0.dupe();
        assert!(CoercedSelector::check_all_keys_unique(&long).is_err());
    }
}


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

use anyhow::Context;
use buck2_core::package::PackageLabel;

use crate::attrs::attr::Attribute;
use crate::attrs::coerced_attr::CoercedAttr;
use crate::attrs::configuration_context::AttrConfigurationContext;
use crate::attrs::configured_attr_full::ConfiguredAttrFull;
use crate::attrs::traversal::CoercedAttrTraversal;

/// Full coerced attribute: name, type, value.
pub struct CoercedAttrFull<'a> {
    pub name: &'a str,
    pub attr: &'a Attribute,
    pub value: &'a CoercedAttr,
}

impl<'a> CoercedAttrFull<'a> {
    pub fn configure(
        &self,
        ctx: &dyn AttrConfigurationContext,
    ) -> anyhow::Result<ConfiguredAttrFull<'a>> {
        Ok(ConfiguredAttrFull {
            name: self.name,
            attr: self.attr,
            value: self
                .value
                .configure(self.attr.coercer(), ctx)
                .with_context(|| format!("configuring attr `{}`", self.name))?,
        })
    }

    pub fn traverse(
        &self,
        pkg: PackageLabel,
        traversal: &mut dyn CoercedAttrTraversal<'a>,
    ) -> anyhow::Result<()> {
        self.value
            .traverse(self.attr.coercer(), pkg, traversal)
            .with_context(|| format!("traversing attribute `{}`", self.name))
    }
}


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

use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::target::label::TargetLabel;

use crate::attrs::attr_type::arg::ArgAttrType;
use crate::attrs::attr_type::arg::StringWithMacros;
use crate::attrs::attr_type::bool::BoolAttrType;
use crate::attrs::attr_type::bool::BoolLiteral;
use crate::attrs::attr_type::configuration_dep::ConfigurationDepAttrType;
use crate::attrs::attr_type::configured_dep::ExplicitConfiguredDepAttrType;
use crate::attrs::attr_type::configured_dep::UnconfiguredExplicitConfiguredDep;
use crate::attrs::attr_type::dep::DepAttr;
use crate::attrs::attr_type::dep::DepAttrType;
use crate::attrs::attr_type::dict::DictAttrType;
use crate::attrs::attr_type::dict::DictLiteral;
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::list::ListLiteral;
use crate::attrs::attr_type::one_of::OneOfAttrType;
use crate::attrs::attr_type::option::OptionAttrType;
use crate::attrs::attr_type::query::QueryAttr;
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::string::StringLiteral;
use crate::attrs::attr_type::tuple::TupleAttrType;
use crate::attrs::attr_type::tuple::TupleLiteral;
use crate::attrs::attr_type::visibility::VisibilityAttrType;
use crate::attrs::attr_type::within_view::WithinViewAttrType;
use crate::attrs::attr_type::AttrType;
use crate::attrs::attr_type::AttrTypeInner;
use crate::attrs::coerced_attr::CoercedAttr;
use crate::attrs::coerced_attr::CoercedSelector;
use crate::attrs::coerced_path::CoercedPath;
use crate::attrs::display::AttrDisplayWithContextExt;
use crate::visibility::VisibilitySpecification;
use crate::visibility::WithinViewSpecification;

#[derive(Debug, thiserror::Error)]
enum CoercedAttrWithTypeError {
    #[error(
        "attr and type mismatch: {}, {}; ({:?}) (internal error)",
        _0.as_display_no_ctx(),
        _1,
        _0,
    )]
    Mismatch(CoercedAttr, AttrType),
    #[error("Unreachable: select already handled")]
    Select,
    #[error("Unreachable any")]
    Any,
}

/// Bind `CoercedAttr` to `AttrType`.
pub enum CoercedAttrWithType<'a, 't> {
    Selector(&'a CoercedSelector, &'t AttrType),
    Concat(&'a [CoercedAttr], &'t AttrType),

    Some(&'a CoercedAttr, &'t OptionAttrType),
    None,

    AnyList(&'a ListLiteral<CoercedAttr>),
    AnyTuple(&'a TupleLiteral<CoercedAttr>),
    AnyDict(&'a DictLiteral<CoercedAttr>),

    Bool(BoolLiteral, BoolAttrType),
    Int(i32, IntAttrType),
    String(&'a StringLiteral, StringAttrType),
    EnumVariant(&'a StringLiteral, &'t EnumAttrType),
    List(&'a ListLiteral<CoercedAttr>, &'t ListAttrType),
    Tuple(&'a TupleLiteral<CoercedAttr>, &'t TupleAttrType),
    Dict(&'a DictLiteral<CoercedAttr>, &'t DictAttrType),
    OneOf(&'a CoercedAttr, u32, &'t OneOfAttrType),
    Visibility(&'a VisibilitySpecification, VisibilityAttrType),
    WithinView(&'a WithinViewSpecification, WithinViewAttrType),
    ExplicitConfiguredDep(
        &'a UnconfiguredExplicitConfiguredDep,
        &'t ExplicitConfiguredDepAttrType,
    ),
    SplitTransitionDep(&'a ProvidersLabel, &'t SplitTransitionDepAttrType),
    ConfiguredDep(&'a DepAttr<ConfiguredProvidersLabel>),
    ConfigurationDep(&'a TargetLabel, ConfigurationDepAttrType),
    Dep(&'a ProvidersLabel, &'t DepAttrType),
    SourceLabel(&'a ProvidersLabel, SourceAttrType),
    Label(&'a ProvidersLabel, LabelAttrType),
    Arg(&'a StringWithMacros<ProvidersLabel>, ArgAttrType),
    Query(&'a QueryAttr<ProvidersLabel>, &'t QueryAttrType),
    SourceFile(&'a CoercedPath, SourceAttrType),
}

impl<'a, 't> CoercedAttrWithType<'a, 't> {
    #[inline]
    pub fn pack(
        attr: &'a CoercedAttr,
        ty: &'t AttrType,
    ) -> anyhow::Result<CoercedAttrWithType<'a, 't>> {
        match (attr, &*(ty.0)) {
            (CoercedAttr::Selector(s), _) => Ok(CoercedAttrWithType::Selector(s, ty)),
            (CoercedAttr::Concat(c), _) => Ok(CoercedAttrWithType::Concat(c, ty)),

            (CoercedAttr::None, _) => Ok(CoercedAttrWithType::None),
            (attr, AttrTypeInner::Option(t)) => Ok(CoercedAttrWithType::Some(attr, t)),

            (a, AttrTypeInner::Any(_)) => Self::pack_any(a),

            (CoercedAttr::Bool(b), AttrTypeInner::Bool(t)) => Ok(CoercedAttrWithType::Bool(*b, *t)),
            (CoercedAttr::Int(i), AttrTypeInner::Int(t)) => Ok(CoercedAttrWithType::Int(*i, *t)),
            (CoercedAttr::String(s), AttrTypeInner::String(t)) => {
                Ok(CoercedAttrWithType::String(s, *t))
            }
            (CoercedAttr::EnumVariant(s), AttrTypeInner::Enum(t)) => {
                Ok(CoercedAttrWithType::EnumVariant(s, t))
            }
            (CoercedAttr::List(l), AttrTypeInner::List(t)) => Ok(CoercedAttrWithType::List(l, t)),
            (CoercedAttr::Tuple(t), AttrTypeInner::Tuple(ty)) => {
                Ok(CoercedAttrWithType::Tuple(t, ty))
            }
            (CoercedAttr::Dict(d), AttrTypeInner::Dict(t)) => Ok(CoercedAttrWithType::Dict(d, t)),
            (CoercedAttr::OneOf(o, i), AttrTypeInner::OneOf(t)) => {
                Ok(CoercedAttrWithType::OneOf(o, *i, t))
            }
            (CoercedAttr::Visibility(v), AttrTypeInner::Visibility(t)) => {
                Ok(CoercedAttrWithType::Visibility(v, *t))
            }
            (CoercedAttr::WithinView(v), AttrTypeInner::WithinView(t)) => {
                Ok(CoercedAttrWithType::WithinView(v, *t))
            }
            (CoercedAttr::ExplicitConfiguredDep(d), AttrTypeInner::ConfiguredDep(t)) => {
                Ok(CoercedAttrWithType::ExplicitConfiguredDep(d, t))
            }
            (CoercedAttr::SplitTransitionDep(d), AttrTypeInner::SplitTransitionDep(t)) => {
                Ok(CoercedAttrWithType::SplitTransitionDep(d, t))
            }
            (CoercedAttr::ConfiguredDep(d), _) => Ok(CoercedAttrWithType::ConfiguredDep(d)),
            (CoercedAttr::ConfigurationDep(d), AttrTypeInner::ConfigurationDep(t)) => {
                Ok(CoercedAttrWithType::ConfigurationDep(d, *t))
            }
            (CoercedAttr::Dep(d), AttrTypeInner::Dep(t)) => Ok(CoercedAttrWithType::Dep(d, t)),
            (CoercedAttr::SourceLabel(s), AttrTypeInner::Source(t)) => {
                Ok(CoercedAttrWithType::SourceLabel(s, *t))
            }
            (CoercedAttr::Label(l), AttrTypeInner::Label(t)) => {
                Ok(CoercedAttrWithType::Label(l, *t))
            }
            (CoercedAttr::Arg(a), AttrTypeInner::Arg(t)) => Ok(CoercedAttrWithType::Arg(a, *t)),
            (CoercedAttr::Query(q), AttrTypeInner::Query(t)) => {
                Ok(CoercedAttrWithType::Query(q, t))
            }
            (CoercedAttr::SourceFile(p), AttrTypeInner::Source(t)) => {
                Ok(CoercedAttrWithType::SourceFile(p, *t))
            }

            // Explicitly list the remaining pattern to make sure nothing is forgotten.
            (CoercedAttr::Bool(_), _)
            | (CoercedAttr::Int(_), _)
            | (CoercedAttr::String(_), _)
            | (CoercedAttr::EnumVariant(_), _)
            | (CoercedAttr::List(_), _)
            | (CoercedAttr::Tuple(_), _)
            | (CoercedAttr::Dict(_), _)
            | (CoercedAttr::OneOf(..), _)
            | (CoercedAttr::Visibility(_), _)
            | (CoercedAttr::WithinView(_), _)
            | (CoercedAttr::ExplicitConfiguredDep(_), _)
            | (CoercedAttr::SplitTransitionDep(_), _)
            | (CoercedAttr::ConfigurationDep(_), _)
            | (CoercedAttr::Dep(_), _)
            | (CoercedAttr::SourceLabel(_), _)
            | (CoercedAttr::Label(_), _)
            | (CoercedAttr::Arg(_), _)
            | (CoercedAttr::Query(_), _)
            | (CoercedAttr::SourceFile(_), _) => {
                Err(CoercedAttrWithTypeError::Mismatch(attr.clone(), ty.clone()).into())
            }
        }
    }

    #[inline]
    fn pack_any(attr: &'a CoercedAttr) -> anyhow::Result<CoercedAttrWithType<'a, 't>> {
        match attr {
            CoercedAttr::Selector(_) | CoercedAttr::Concat(_) => {
                Err(CoercedAttrWithTypeError::Select.into())
            }
            CoercedAttr::Bool(b) => Ok(CoercedAttrWithType::Bool(*b, BoolAttrType)),
            CoercedAttr::Int(i) => Ok(CoercedAttrWithType::Int(*i, IntAttrType)),
            CoercedAttr::String(s) => Ok(CoercedAttrWithType::String(s, StringAttrType)),
            CoercedAttr::List(l) => Ok(CoercedAttrWithType::AnyList(l)),
            CoercedAttr::Tuple(t) => Ok(CoercedAttrWithType::AnyTuple(t)),
            CoercedAttr::Dict(d) => Ok(CoercedAttrWithType::AnyDict(d)),
            CoercedAttr::None => Ok(CoercedAttrWithType::None),
            CoercedAttr::OneOf(_, _)
            | CoercedAttr::Visibility(_)
            | CoercedAttr::WithinView(_)
            | CoercedAttr::ExplicitConfiguredDep(_)
            | CoercedAttr::SplitTransitionDep(_)
            | CoercedAttr::ConfiguredDep(_)
            | CoercedAttr::ConfigurationDep(_)
            | CoercedAttr::Dep(_)
            | CoercedAttr::SourceLabel(_)
            | CoercedAttr::Label(_)
            | CoercedAttr::Arg(_)
            | CoercedAttr::Query(_)
            | CoercedAttr::EnumVariant(_)
            | CoercedAttr::SourceFile(_) => Err(CoercedAttrWithTypeError::Any.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::sync::Arc;

use allocative::Allocative;
use buck2_core::buck_path::path::BuckPathRef;
use buck2_core::configuration::transition::id::TransitionId;
use buck2_core::target::label::TargetLabel;
use buck2_util::collections::ordered_set::OrderedSet;
use dupe::Dupe;

use crate::attrs::traversal::CoercedAttrTraversal;

#[derive(Default, Debug, PartialEq, Eq, Hash, Allocative)]
pub struct CoercedDeps {
    /// Contains the deps derived from the attributes.
    /// Does not include the transition, exec or configuration deps.
    pub deps: Box<[TargetLabel]>,

    /// Contains the deps which are transitioned to other configuration
    /// (including split transitions).
    pub transition_deps: Box<[(TargetLabel, Arc<TransitionId>)]>,

    /// Contains the execution deps derived from the attributes.
    pub exec_deps: Box<[TargetLabel]>,

    /// Contains the toolchain deps derived from the attributes.
    pub toolchain_deps: Box<[TargetLabel]>,

    /// Contains the configuration deps. These are deps that appear as conditions in selects.
    pub configuration_deps: Box<[TargetLabel]>,

    /// Contains platform targets of configured_alias()
    pub platform_deps: Box<[TargetLabel]>,
}

impl From<CoercedDepsCollector> for CoercedDeps {
    fn from(collector: CoercedDepsCollector) -> CoercedDeps {
        let CoercedDepsCollector {
            deps,
            transition_deps,
            exec_deps,
            toolchain_deps,
            configuration_deps,
            platform_deps,
        } = collector;
        CoercedDeps {
            deps: deps.into_iter().collect(),
            transition_deps: transition_deps.into_iter().collect(),
            exec_deps: exec_deps.into_iter().collect(),
            toolchain_deps: toolchain_deps.into_iter().collect(),
            configuration_deps: configuration_deps.into_iter().collect(),
            platform_deps: platform_deps.into_iter().collect(),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Hash, Allocative)]
pub struct CoercedDepsCollector {
    /// Contains the deps derived from the attributes.
    /// Does not include the transition, exec or configuration deps.
    pub deps: OrderedSet<TargetLabel>,

    /// Contains the deps which are transitioned to other configuration
    /// (including split transitions).
    pub transition_deps: OrderedSet<(TargetLabel, Arc<TransitionId>)>,

    /// Contains the execution deps derived from the attributes.
    pub exec_deps: OrderedSet<TargetLabel>,

    /// Contains the toolchain deps derived from the attributes.
    pub toolchain_deps: OrderedSet<TargetLabel>,

    /// Contains the configuration deps. These are deps that appear as conditions in selects.
    pub configuration_deps: OrderedSet<TargetLabel>,

    /// Contains platform targets of configured_alias()
    pub platform_deps: OrderedSet<TargetLabel>,
}

impl CoercedDepsCollector {
    pub fn new() -> Self {
        Self {
            deps: OrderedSet::new(),
            exec_deps: OrderedSet::new(),
            toolchain_deps: OrderedSet::new(),
            transition_deps: OrderedSet::new(),
            configuration_deps: OrderedSet::new(),
            platform_deps: OrderedSet::new(),
        }
    }
}

impl<'a> CoercedAttrTraversal<'a> for CoercedDepsCollector {
    fn dep(&mut self, dep: &'a TargetLabel) -> anyhow::Result<()> {
        self.deps.insert(dep.dupe());
        Ok(())
    }

    fn exec_dep(&mut self, dep: &'a TargetLabel) -> anyhow::Result<()> {
        self.exec_deps.insert(dep.dupe());
        Ok(())
    }

    fn toolchain_dep(&mut self, dep: &'a TargetLabel) -> anyhow::Result<()> {
        self.toolchain_deps.insert(dep.dupe());
        Ok(())
    }

    fn transition_dep(
        &mut self,
        dep: &'a TargetLabel,
        tr: &Arc<TransitionId>,
    ) -> anyhow::Result<()> {
        self.transition_deps.insert((dep.dupe(), tr.dupe()));
        Ok(())
    }

    fn split_transition_dep(
        &mut self,
        dep: &'a TargetLabel,
        tr: &Arc<TransitionId>,
    ) -> anyhow::Result<()> {
        self.transition_deps.insert((dep.dupe(), tr.dupe()));
        Ok(())
    }

    fn configuration_dep(&mut self, dep: &'a TargetLabel) -> anyhow::Result<()> {
        self.configuration_deps.insert(dep.dupe());
        Ok(())
    }

    fn platform_dep(&mut self, dep: &'a TargetLabel) -> anyhow::Result<()> {
        self.platform_deps.insert(dep.dupe());
        Ok(())
    }

    fn input(&mut self, _input: BuckPathRef) -> 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 std::iter;

use allocative::Allocative;
use buck2_core::package::package_relative_path::PackageRelativePath;
use buck2_util::arc_str::ArcS;
use either::Either;
use static_assertions::assert_eq_size;

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub struct CoercedDirectory {
    pub dir: ArcS<PackageRelativePath>,
    // We can make this type DST, so there would be only one allocation
    // for directory itself and files. But we don't have a lot of directories,
    // so it is not worth the trouble.
    pub files: Box<[ArcS<PackageRelativePath>]>,
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub enum CoercedPath {
    File(ArcS<PackageRelativePath>),
    Directory(Box<CoercedDirectory>),
}

// Avoid changing the size accidentally.
assert_eq_size!(CoercedPath, [usize; 2]);

impl CoercedPath {
    pub fn path(&self) -> &ArcS<PackageRelativePath> {
        match self {
            CoercedPath::File(x) => x,
            CoercedPath::Directory(x) => &x.dir,
        }
    }

    pub fn inputs(&self) -> impl Iterator<Item = &'_ ArcS<PackageRelativePath>> {
        match self {
            CoercedPath::File(x) => Either::Left(iter::once(x)),
            CoercedPath::Directory(x) => Either::Right(x.files.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 buck2_core::pattern::pattern_type::TargetPatternExtra;
use buck2_core::pattern::ParsedPattern;
use buck2_core::provider::label::NonDefaultProvidersName;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::provider::label::ProvidersName;
use buck2_core::target::label::TargetLabel;
use buck2_query::query::syntax::simple::functions::QueryLiteralVisitor;
use buck2_query_parser::spanned::Spanned;
use buck2_query_parser::Expr;
use buck2_util::arc_str::ArcSlice;
use buck2_util::arc_str::ArcStr;

use super::coerced_attr::CoercedAttr;
use crate::attrs::coerced_path::CoercedPath;

#[derive(thiserror::Error, Debug)]
enum AttrCoercionContextError {
    #[error("Expected target label without name. Got `{0}`")]
    UnexpectedProvidersName(String),
}

/// The context for attribute coercion. Mostly just contains information about
/// the current package (to support things like parsing targets from strings).
pub trait AttrCoercionContext {
    fn coerce_target(&self, value: &str) -> anyhow::Result<TargetLabel> {
        let label = self.coerce_label(value)?;
        if let ProvidersName::NonDefault(box NonDefaultProvidersName::Named(_)) = label.name() {
            return Err(AttrCoercionContextError::UnexpectedProvidersName(value.to_owned()).into());
        }
        Ok(label.into_parts().0)
    }

    /// Attempt to convert a string into a label
    fn coerce_label(&self, value: &str) -> anyhow::Result<ProvidersLabel>;

    /// Reuse previously allocated string if possible.
    fn intern_str(&self, value: &str) -> ArcStr;

    // Reuse previously allocated slices if possible.
    fn intern_list(&self, value: Vec<CoercedAttr>) -> ArcSlice<CoercedAttr>;

    // Reuse previously allocated selects if possible.
    fn intern_select(
        &self,
        value: Vec<(TargetLabel, CoercedAttr)>,
    ) -> ArcSlice<(TargetLabel, CoercedAttr)>;

    // Reuse previously allocated dicts if possible.
    fn intern_dict(
        &self,
        value: Vec<(CoercedAttr, CoercedAttr)>,
    ) -> ArcSlice<(CoercedAttr, CoercedAttr)>;

    /// Attempt to convert a string into a BuckPath
    fn coerce_path(&self, value: &str, allow_directory: bool) -> anyhow::Result<CoercedPath>;

    fn coerce_target_pattern(
        &self,
        pattern: &str,
    ) -> anyhow::Result<ParsedPattern<TargetPatternExtra>>;

    fn visit_query_function_literals(
        &self,
        visitor: &mut dyn QueryLiteralVisitor,
        expr: &Spanned<Expr>,
        query: &str,
    ) -> anyhow::Result<()>;
}


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

use dupe::Dupe;

/// Can we use `select` with this attribute?
#[derive(Copy, Clone, Dupe, Eq, PartialEq)]
pub enum AttrIsConfigurable {
    Yes,
    No,
}


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

use std::sync::Arc;

use anyhow::Context;
use buck2_core::configuration::config_setting::ConfigSettingData;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::configuration::pair::ConfigurationNoExec;
use buck2_core::configuration::pair::ConfigurationWithExec;
use buck2_core::configuration::transition::applied::TransitionApplied;
use buck2_core::configuration::transition::id::TransitionId;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::target::label::TargetLabel;
use buck2_util::collections::ordered_map::OrderedMap;
use buck2_util::collections::sorted_map::SortedMap;
use dupe::Dupe;

use crate::configuration::resolved::ConfigurationSettingKeyRef;
use crate::configuration::resolved::ResolvedConfiguration;

#[derive(Debug, thiserror::Error)]
pub enum PlatformConfigurationError {
    #[error("Could not find configuration for platform target `{0}`")]
    UnknownPlatformTarget(TargetLabel),
}

/// The context for attribute configuration. Contains information about the
/// configuration.
pub trait AttrConfigurationContext {
    /// Return the content of the resolved `config_setting` on match.
    fn matches<'a>(&'a self, label: &TargetLabel) -> Option<&'a ConfigSettingData>;

    fn cfg(&self) -> ConfigurationNoExec;

    fn exec_cfg(&self) -> ConfigurationNoExec;

    /// Must be equal to `(cfg, Some(exec_cfg))`.
    fn toolchain_cfg(&self) -> ConfigurationWithExec;

    fn platform_cfg(&self, label: &TargetLabel) -> anyhow::Result<ConfigurationData>;

    /// Map of transition ids resolved to configurations
    /// using current node configuration as input.
    fn resolved_transitions(&self) -> &OrderedMap<Arc<TransitionId>, Arc<TransitionApplied>>;

    fn configure_target(&self, label: &ProvidersLabel) -> ConfiguredProvidersLabel {
        label.configure_pair(self.cfg().cfg_pair().dupe())
    }

    fn configure_exec_target(&self, label: &ProvidersLabel) -> ConfiguredProvidersLabel {
        label.configure_pair(self.exec_cfg().cfg_pair().dupe())
    }

    fn configure_toolchain_target(&self, label: &ProvidersLabel) -> ConfiguredProvidersLabel {
        // The toolchain dependency itself is always configured in the target configuration,
        // but its exec_deps are considered when picking an execution platform, and MUST
        // use the execution dependency of its parent.
        label.configure_pair(self.toolchain_cfg().cfg_pair().dupe())
    }

    /// Configure a transition target.
    fn configure_transition_target(
        &self,
        label: &ProvidersLabel,
        tr: &TransitionId,
    ) -> anyhow::Result<ConfiguredProvidersLabel> {
        let cfg = self
            .resolved_transitions()
            .get(tr)
            .context("internal error: no resolved transition")?;
        Ok(label.configure(cfg.single()?.dupe()))
    }

    fn configure_split_transition_target(
        &self,
        label: &ProvidersLabel,
        tr: &TransitionId,
    ) -> anyhow::Result<SortedMap<String, ConfiguredProvidersLabel>> {
        let cfg = self
            .resolved_transitions()
            .get(tr)
            .context("internal error: no resolved transition")?;
        let split = cfg.split()?;
        Ok(split
            .iter()
            .map(|(k, v)| (k.to_owned(), label.configure(v.dupe())))
            .collect())
    }
}

pub struct AttrConfigurationContextImpl<'b> {
    resolved_cfg: &'b ResolvedConfiguration,
    exec_cfg: ConfigurationNoExec,
    /// Must be equal to `(cfg, Some(exec_cfg))`.
    toolchain_cfg: ConfigurationWithExec,
    resolved_transitions: &'b OrderedMap<Arc<TransitionId>, Arc<TransitionApplied>>,
    platform_cfgs: &'b OrderedMap<TargetLabel, ConfigurationData>,
}

impl<'b> AttrConfigurationContextImpl<'b> {
    pub fn new(
        resolved_cfg: &'b ResolvedConfiguration,
        exec_cfg: ConfigurationNoExec,
        resolved_transitions: &'b OrderedMap<Arc<TransitionId>, Arc<TransitionApplied>>,
        platform_cfgs: &'b OrderedMap<TargetLabel, ConfigurationData>,
    ) -> AttrConfigurationContextImpl<'b> {
        AttrConfigurationContextImpl {
            resolved_cfg,
            toolchain_cfg: resolved_cfg.cfg().make_toolchain(&exec_cfg),
            exec_cfg,
            resolved_transitions,
            platform_cfgs,
        }
    }
}

impl<'b> AttrConfigurationContext for AttrConfigurationContextImpl<'b> {
    fn matches<'a>(&'a self, label: &TargetLabel) -> Option<&'a ConfigSettingData> {
        self.resolved_cfg
            .setting_matches(ConfigurationSettingKeyRef(label))
    }

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

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

    fn toolchain_cfg(&self) -> ConfigurationWithExec {
        self.toolchain_cfg.dupe()
    }

    fn platform_cfg(&self, label: &TargetLabel) -> anyhow::Result<ConfigurationData> {
        match self.platform_cfgs.get(label) {
            Some(configuration) => Ok(configuration.dupe()),
            None => Err(anyhow::anyhow!(
                PlatformConfigurationError::UnknownPlatformTarget(label.dupe())
            )),
        }
    }

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


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

use std::fmt::Debug;
use std::fmt::Display;

use allocative::Allocative;
use buck2_core::buck_path::path::BuckPathRef;
use buck2_core::package::PackageLabel;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::target::label::TargetLabel;
use buck2_util::arc_str::ArcStr;
use buck2_util::collections::ordered_map::OrderedMap;
use dupe::Dupe;
use serde::Serialize;
use serde::Serializer;
use starlark_map::small_map;

use super::attr_type::arg::ConfiguredStringWithMacros;
use crate::attrs::attr_type::attr_config::source_file_display;
use crate::attrs::attr_type::bool::BoolLiteral;
use crate::attrs::attr_type::configured_dep::ConfiguredExplicitConfiguredDep;
use crate::attrs::attr_type::dep::DepAttr;
use crate::attrs::attr_type::dict::DictLiteral;
use crate::attrs::attr_type::list::ListLiteral;
use crate::attrs::attr_type::query::QueryAttr;
use crate::attrs::attr_type::split_transition_dep::ConfiguredSplitTransitionDep;
use crate::attrs::attr_type::string::StringLiteral;
use crate::attrs::attr_type::tuple::TupleLiteral;
use crate::attrs::attr_type::AttrType;
use crate::attrs::attr_type::AttrTypeInner;
use crate::attrs::coerced_path::CoercedPath;
use crate::attrs::configured_traversal::ConfiguredAttrTraversal;
use crate::attrs::display::AttrDisplayWithContext;
use crate::attrs::display::AttrDisplayWithContextExt;
use crate::attrs::fmt_context::AttrFmtContext;
use crate::attrs::json::ToJsonWithContext;
use crate::attrs::serialize::AttrSerializeWithContext;
use crate::visibility::VisibilitySpecification;
use crate::visibility::WithinViewSpecification;

#[derive(Debug, thiserror::Error)]
enum ConfiguredAttrError {
    #[error("addition not supported for this attribute type `{0}`.")]
    ConcatNotSupported(String),
    #[error("addition not supported for these attribute type `{0}` and value `{1}`.")]
    ConcatNotSupportedValues(&'static str, String),
    #[error("got same key in both sides of dictionary concat (key `{0}`).")]
    DictConcatDuplicateKeys(String),
    #[error(
        "Cannot concatenate values coerced/configured to different oneof variants: `{0}` and `{1}`"
    )]
    ConcatDifferentOneofVariants(AttrType, AttrType),
    #[error("while concat, LHS is oneof, expecting RHS to also be oneof (internal error)")]
    LhsOneOfRhsNotOneOf,
    #[error("expecting a list, got `{0}`")]
    ExpectingList(String),
    #[error("expecting configuration dep, got `{0}`")]
    ExpectingConfigurationDep(String),
    #[error("Inconsistent attr value (`{}`) and attr type (`{}`) (internal error)",
            _0.as_display_no_ctx(), _1)]
    InconsistentAttrValueAndAttrType(ConfiguredAttr, AttrType),
}

#[derive(Eq, PartialEq, Hash, Clone, Allocative, Debug)]
pub enum ConfiguredAttr {
    Bool(BoolLiteral),
    Int(i32),
    // Note we store `String`, not `Arc<str>` here, because we store full attributes
    // in unconfigured target node, but configured target node is basically a pair
    // (reference to unconfigured target node, configuration).
    //
    // Configured attributes are created on demand and destroyed immediately after use.
    //
    // So when working with configured attributes with pay with CPU for string copies,
    // but don't increase total memory usage, because these string copies are short living.
    String(StringLiteral),
    // Like String, but drawn from a set of variants, so doesn't support concat
    EnumVariant(StringLiteral),
    List(ListLiteral<ConfiguredAttr>),
    Tuple(TupleLiteral<ConfiguredAttr>),
    Dict(DictLiteral<ConfiguredAttr>),
    None,
    // NOTE: unlike deps, labels are not traversed, as they are typically used in lieu of deps in
    // cases that would cause cycles.
    OneOf(
        Box<Self>,
        // Index of matched oneof attr type variant.
        u32,
    ),
    Visibility(VisibilitySpecification),
    WithinView(WithinViewSpecification),
    ExplicitConfiguredDep(Box<ConfiguredExplicitConfiguredDep>),
    SplitTransitionDep(Box<ConfiguredSplitTransitionDep>),
    ConfigurationDep(Box<TargetLabel>),
    Dep(Box<DepAttr<ConfiguredProvidersLabel>>),
    SourceLabel(Box<ConfiguredProvidersLabel>),
    // NOTE: unlike deps, labels are not traversed, as they are typically used in lieu of deps in
    // cases that would cause cycles.
    Label(Box<ConfiguredProvidersLabel>),
    Arg(ConfiguredStringWithMacros),
    Query(Box<QueryAttr<ConfiguredProvidersLabel>>),
    SourceFile(CoercedPath),
}

impl AttrSerializeWithContext for ConfiguredAttr {
    fn serialize_with_ctx<S>(&self, ctx: &AttrFmtContext, s: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        // TODO this is inefficient. We should impl Serialize and derive value from this instead.
        self.to_json(ctx)
            .map_err(|e| serde::ser::Error::custom(format!("{}", e)))?
            .serialize(s)
    }
}

impl AttrDisplayWithContext for ConfiguredAttr {
    fn fmt(&self, ctx: &AttrFmtContext, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ConfiguredAttr::Bool(v) => {
                write!(f, "{}", v)
            }
            ConfiguredAttr::Int(v) => {
                write!(f, "{}", v)
            }
            ConfiguredAttr::String(v) | ConfiguredAttr::EnumVariant(v) => Display::fmt(v, f),
            ConfiguredAttr::List(list) => AttrDisplayWithContext::fmt(list, ctx, f),
            ConfiguredAttr::Tuple(v) => AttrDisplayWithContext::fmt(v, ctx, f),
            ConfiguredAttr::Dict(v) => AttrDisplayWithContext::fmt(v, ctx, f),
            ConfiguredAttr::None => write!(f, "None"),
            ConfiguredAttr::OneOf(box l, _) => AttrDisplayWithContext::fmt(l, ctx, f),
            ConfiguredAttr::Visibility(v) => Display::fmt(v, f),
            ConfiguredAttr::WithinView(v) => Display::fmt(v, f),
            ConfiguredAttr::ExplicitConfiguredDep(e) => Display::fmt(e, f),
            ConfiguredAttr::SplitTransitionDep(e) => Display::fmt(e, f),
            ConfiguredAttr::ConfigurationDep(e) => write!(f, "\"{}\"", e),
            ConfiguredAttr::Dep(e) => write!(f, "\"{}\"", e),
            ConfiguredAttr::SourceLabel(e) => write!(f, "\"{}\"", e),
            ConfiguredAttr::Label(e) => write!(f, "\"{}\"", e),
            ConfiguredAttr::Arg(e) => write!(f, "\"{}\"", e),
            ConfiguredAttr::Query(e) => write!(f, "\"{}\"", e.query()),
            ConfiguredAttr::SourceFile(e) => write!(f, "\"{}\"", source_file_display(ctx, e)),
        }
    }
}

impl ConfiguredAttr {
    /// Traverses the configured attribute and calls the traverse for every encountered target label (in deps, sources, or other places).
    pub fn traverse<'a>(
        &'a self,
        pkg: PackageLabel,
        traversal: &mut dyn ConfiguredAttrTraversal,
    ) -> anyhow::Result<()> {
        match self {
            ConfiguredAttr::Bool(_) => Ok(()),
            ConfiguredAttr::Int(_) => Ok(()),
            ConfiguredAttr::String(_) => Ok(()),
            ConfiguredAttr::EnumVariant(_) => Ok(()),
            ConfiguredAttr::List(list) => {
                for v in list.iter() {
                    v.traverse(pkg.dupe(), traversal)?;
                }
                Ok(())
            }
            ConfiguredAttr::Tuple(list) => {
                for v in list.iter() {
                    v.traverse(pkg.dupe(), traversal)?;
                }
                Ok(())
            }
            ConfiguredAttr::Dict(dict) => {
                for (k, v) in dict.iter() {
                    k.traverse(pkg.dupe(), traversal)?;
                    v.traverse(pkg.dupe(), traversal)?;
                }
                Ok(())
            }
            ConfiguredAttr::None => Ok(()),
            ConfiguredAttr::OneOf(l, _) => l.traverse(pkg, traversal),
            ConfiguredAttr::Visibility(..) => Ok(()),
            ConfiguredAttr::WithinView(..) => Ok(()),
            ConfiguredAttr::ExplicitConfiguredDep(dep) => dep.as_ref().traverse(traversal),
            ConfiguredAttr::SplitTransitionDep(deps) => {
                for target in deps.deps.values() {
                    traversal.dep(target)?;
                }
                Ok(())
            }
            ConfiguredAttr::ConfigurationDep(dep) => traversal.configuration_dep(dep),
            ConfiguredAttr::Dep(dep) => dep.traverse(traversal),
            ConfiguredAttr::SourceLabel(dep) => traversal.dep(dep),
            ConfiguredAttr::Label(label) => traversal.label(label),
            ConfiguredAttr::Arg(arg) => arg.string_with_macros.traverse(traversal),
            ConfiguredAttr::Query(query) => query.traverse(traversal),
            ConfiguredAttr::SourceFile(source) => {
                for x in source.inputs() {
                    traversal.input(BuckPathRef::new(pkg.dupe(), x))?;
                }
                Ok(())
            }
        }
    }

    /// Used for concatting the configured result of concatted selects. For most types this isn't allowed (it
    /// should be unreachable as concat-ability is checked during coercion and the type would've returned false from `AttrType::supports_concat`).
    /// This is used when a select() is added to another value, like `select(<...>) + select(<...>)` or `select(<...>) + [...]`.
    pub(crate) fn concat(
        self,
        attr_type: &AttrType,
        items: &mut dyn Iterator<Item = anyhow::Result<Self>>,
    ) -> anyhow::Result<Self> {
        let mismatch = |ty, attr: ConfiguredAttr| {
            Err(ConfiguredAttrError::ConcatNotSupportedValues(
                ty,
                attr.as_display_no_ctx().to_string(),
            )
            .into())
        };

        match self {
            ConfiguredAttr::OneOf(box first, first_i) => {
                // Becaise if attr type if `option(oneof([list(string), ...])`,
                // value type is `oneof(list(...))`, without indication it is an option.
                let attr_type = attr_type.unwrap_if_option();

                let oneof_type = match &*attr_type.0 {
                    AttrTypeInner::OneOf(oneof_type) => oneof_type,
                    _ => {
                        return Err(ConfiguredAttrError::InconsistentAttrValueAndAttrType(
                            ConfiguredAttr::OneOf(Box::new(first), first_i),
                            attr_type.dupe(),
                        )
                        .into());
                    }
                };

                let first_t = oneof_type.get(first_i)?;

                first.concat(
                    first_t,
                    &mut items.map(|next| match next? {
                        ConfiguredAttr::OneOf(box next, next_i) => {
                            let next_t = oneof_type.get(next_i)?;
                            if first_i != next_i {
                                return Err(ConfiguredAttrError::ConcatDifferentOneofVariants(
                                    first_t.dupe(),
                                    next_t.dupe(),
                                )
                                .into());
                            }
                            Ok(next)
                        }
                        _ => Err(ConfiguredAttrError::LhsOneOfRhsNotOneOf.into()),
                    }),
                )
            }
            ConfiguredAttr::List(list) => {
                let mut res = list.to_vec();
                for x in items {
                    match x? {
                        ConfiguredAttr::List(list2) => {
                            res.extend(list2.iter().cloned());
                        }
                        attr => return mismatch("list", attr),
                    }
                }
                Ok(ConfiguredAttr::List(ListLiteral(res.into())))
            }
            ConfiguredAttr::Dict(left) => {
                let mut res = OrderedMap::new();
                for (k, v) in left.iter().cloned() {
                    res.insert(k, v);
                }
                for x in items {
                    match x? {
                        ConfiguredAttr::Dict(right) => {
                            for (k, v) in right.iter().cloned() {
                                match res.entry(k) {
                                    small_map::Entry::Vacant(e) => {
                                        e.insert(v);
                                    }
                                    small_map::Entry::Occupied(e) => {
                                        return Err(ConfiguredAttrError::DictConcatDuplicateKeys(
                                            e.key().as_display_no_ctx().to_string(),
                                        )
                                        .into());
                                    }
                                }
                            }
                        }
                        attr => return mismatch("dict", attr),
                    }
                }
                Ok(ConfiguredAttr::Dict(res.into_iter().collect()))
            }
            ConfiguredAttr::String(res) => {
                let mut items = items.peekable();
                if items.peek().is_none() {
                    Ok(ConfiguredAttr::String(res))
                } else {
                    let mut res = str::to_owned(&res.0);
                    for x in items {
                        match x? {
                            ConfiguredAttr::String(right) => res.push_str(&right.0),
                            attr => return mismatch("string", attr),
                        }
                    }
                    Ok(ConfiguredAttr::String(StringLiteral(ArcStr::from(res))))
                }
            }
            ConfiguredAttr::Arg(left) => {
                let res = left.string_with_macros.concat(items.map(|x| {
                    match x? {
                        ConfiguredAttr::Arg(x) => Ok(x.string_with_macros),
                        attr => Err(ConfiguredAttrError::ConcatNotSupportedValues(
                            "arg",
                            attr.as_display_no_ctx().to_string(),
                        )
                        .into()),
                    }
                }))?;
                Ok(ConfiguredAttr::Arg(ConfiguredStringWithMacros {
                    string_with_macros: res,
                    anon_target_compatible: left.anon_target_compatible,
                }))
            }
            val => Err(ConfiguredAttrError::ConcatNotSupported(
                val.as_display_no_ctx().to_string(),
            )
            .into()),
        }
    }

    pub(crate) fn try_into_configuration_dep(self) -> anyhow::Result<TargetLabel> {
        match self {
            ConfiguredAttr::ConfigurationDep(d) => Ok(*d),
            a => Err(ConfiguredAttrError::ExpectingConfigurationDep(
                a.as_display_no_ctx().to_string(),
            )
            .into()),
        }
    }

    pub fn unpack_list(&self) -> Option<&[ConfiguredAttr]> {
        match self {
            ConfiguredAttr::List(list) => Some(list),
            _ => None,
        }
    }

    pub(crate) fn try_into_list(self) -> anyhow::Result<Vec<ConfiguredAttr>> {
        match self {
            ConfiguredAttr::List(list) => Ok(list.to_vec()),
            a => Err(ConfiguredAttrError::ExpectingList(a.as_display_no_ctx().to_string()).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 anyhow::Context;
use buck2_core::package::PackageLabel;

use crate::attrs::attr::Attribute;
use crate::attrs::configured_attr::ConfiguredAttr;
use crate::attrs::configured_traversal::ConfiguredAttrTraversal;

/// Full configured attribute: name, type, value.
pub struct ConfiguredAttrFull<'a> {
    pub name: &'a str,
    pub attr: &'a Attribute,
    pub value: ConfiguredAttr,
}

impl<'a> ConfiguredAttrFull<'a> {
    pub fn traverse<'v>(
        &'v self,
        pkg: PackageLabel,
        traversal: &mut dyn ConfiguredAttrTraversal,
    ) -> anyhow::Result<()> {
        self.value
            .traverse(pkg, traversal)
            .with_context(|| format!("traversing attribute `{}`", self.name))
    }
}


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

use buck2_core::provider::label::ConfiguredProvidersLabel;
use starlark_map::small_set::SmallSet;

use crate::attrs::attr_type::query::ResolvedQueryLiterals;
use crate::attrs::configured_traversal::ConfiguredAttrTraversal;

#[derive(Default, Debug)]
pub struct ConfiguredAttrInfo {
    // Including transitioned deps.
    pub deps: SmallSet<ConfiguredProvidersLabel>,
    pub execution_deps: SmallSet<ConfiguredProvidersLabel>,
    pub has_query: bool,
}

impl ConfiguredAttrInfo {
    pub fn new() -> Self {
        Self::default()
    }
}

impl ConfiguredAttrTraversal for ConfiguredAttrInfo {
    fn dep(&mut self, dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
        self.deps.insert(dep.clone());
        Ok(())
    }

    fn query_macro(
        &mut self,
        _query: &str,
        _resolved_literals: &ResolvedQueryLiterals<ConfiguredProvidersLabel>,
    ) -> anyhow::Result<()> {
        self.has_query = true;
        Ok(())
    }

    fn exec_dep(&mut self, dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
        self.execution_deps.insert(dep.clone());
        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 buck2_core::buck_path::path::BuckPathRef;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::target::label::TargetLabel;

use crate::attrs::attr_type::query::ResolvedQueryLiterals;

pub trait ConfiguredAttrTraversal {
    fn dep(&mut self, dep: &ConfiguredProvidersLabel) -> anyhow::Result<()>;

    fn exec_dep(&mut self, dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
        // By default, just treat it as a dep. Most things don't care about the distinction.
        self.dep(dep)
    }

    fn toolchain_dep(&mut self, dep: &ConfiguredProvidersLabel) -> anyhow::Result<()> {
        // By default, just treat it as a dep. Most things don't care about the distinction.
        self.dep(dep)
    }

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

    fn query_macro(
        &mut self,
        _query: &str,
        _resolved_literals: &ResolvedQueryLiterals<ConfiguredProvidersLabel>,
    ) -> anyhow::Result<()> {
        Ok(())
    }

    fn input(&mut self, _path: BuckPathRef) -> anyhow::Result<()> {
        Ok(())
    }

    fn label(&mut self, _label: &ConfiguredProvidersLabel) -> 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 std::fmt;
use std::fmt::Display;

use crate::attrs::fmt_context::AttrFmtContext;

/// Like `Display` but has package context.
pub trait AttrDisplayWithContext {
    fn fmt(&self, ctx: &AttrFmtContext, f: &mut fmt::Formatter<'_>) -> fmt::Result;
}

pub trait AttrDisplayWithContextExt: AttrDisplayWithContext {
    fn as_display<'a>(
        &'a self,
        ctx: &'a AttrFmtContext,
    ) -> AttrDisplayWithContextAsDisplay<'a, Self> {
        AttrDisplayWithContextAsDisplay { ctx, value: self }
    }

    fn as_display_no_ctx(&self) -> AttrDisplayWithContextAsDisplay<Self> {
        self.as_display(&AttrFmtContext::NO_CONTEXT)
    }
}

impl<T: AttrDisplayWithContext + ?Sized> AttrDisplayWithContextExt for T {}

pub struct AttrDisplayWithContextAsDisplay<'a, A: ?Sized> {
    ctx: &'a AttrFmtContext,
    value: &'a A,
}

impl<'a, A: AttrDisplayWithContext + ?Sized> Display for AttrDisplayWithContextAsDisplay<'a, A> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.value.fmt(self.ctx, f)
    }
}


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

use buck2_core::package::PackageLabel;

/// Attribute formatting context (for `Display` or `Serialize`).
pub struct AttrFmtContext {
    /// `None` when there's no package, for example when formatting for:
    /// * default value for attribute definition
    /// * tests
    /// * error messages
    pub package: Option<PackageLabel>,
}

impl AttrFmtContext {
    pub const NO_CONTEXT: AttrFmtContext = AttrFmtContext { package: 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.
 */

//! Utility functions to introspect coerced values. This will go away once we have more of the value
//! coercion tooling done. For now, it handles things like stringification for the `targets` command,
//! converting to JSON, etc.

use buck2_core::package::PackageLabel;

use crate::attrs::coerced_attr::CoercedAttr;
use crate::attrs::fmt_context::AttrFmtContext;

pub fn value_to_json(value: &CoercedAttr, pkg: PackageLabel) -> anyhow::Result<serde_json::Value> {
    value.to_json(&AttrFmtContext { package: Some(pkg) })
}

pub fn value_to_string(value: &CoercedAttr, pkg: PackageLabel) -> anyhow::Result<String> {
    match value_to_json(value, pkg)?.as_str() {
        Some(s) => Ok(s.to_owned()),
        None => Err(anyhow::Error::msg("Expected a string, did not get one")),
    }
}


/*
 * 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, Clone, Dupe, Copy, Eq, Hash, PartialEq, Ord, PartialOrd, Allocative
)]
pub struct AttributeId {
    pub index_in_attribute_spec: u16,
}

impl AttributeId {
    pub const MAX_INDEX: u16 = u16::MAX;
}


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

#[derive(Clone, Dupe, Copy)]
pub enum AttrInspectOptions {
    DefinedOnly,
    All,
}

impl AttrInspectOptions {
    pub fn include_default(&self) -> bool {
        match self {
            AttrInspectOptions::DefinedOnly => false,
            _ => true,
        }
    }
}


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

//! Builtin note attributes.

use std::sync::Arc;

use buck2_core::provider::id::ProviderId;
use buck2_util::collections::ordered_map::OrderedMap;
use dupe::Dupe;
use once_cell::sync::Lazy;

use crate::attrs::attr::Attribute;
use crate::attrs::attr_type::any::AnyAttrType;
use crate::attrs::attr_type::AttrType;
use crate::attrs::coerced_attr::CoercedAttr;
use crate::attrs::configurable::AttrIsConfigurable;
use crate::provider_id_set::ProviderIdSet;
use crate::visibility::VisibilitySpecification;
use crate::visibility::WithinViewSpecification;

// TODO(cjhopman): figure out something better for these default attributes that we need to interpret
// internally. There's currently a lot of awkwardness involved: accessing the value, needing to create
// the repr string, setting defaults. Some of that is just about making it easier to work with the
// coerced attrs and some of it is about a nicer structure for defining these attributes and
// accessing them off nodes.
// TODO(cjhopman): these attributes should be marked as "unconfigurable" or "unselectable" or something
// since we need to be able to read them in their unconfigured form.
pub const NAME_ATTRIBUTE_FIELD: &str = "name";
pub const DEFAULT_TARGET_PLATFORM_ATTRIBUTE_FIELD: &str = "default_target_platform";

pub const TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD: &str = "target_compatible_with";
/// buck1 used "compatible_with" for this. in buck2, we have two "compatible with" concepts, both
/// target and exec compatibility and so we are switching to "target_compatible_with". For now we'll accept
/// either form for target compatibility (but not both).
pub const LEGACY_TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD: &str = "compatible_with";
pub const EXEC_COMPATIBLE_WITH_ATTRIBUTE_FIELD: &str = "exec_compatible_with";

pub const VISIBILITY_ATTRIBUTE_FIELD: &str = "visibility";
pub const WITHIN_VIEW_ATTRIBUTE_FIELD: &str = "within_view";

pub const TESTS_ATTRIBUTE_FIELD: &str = "tests";

fn name_attribute() -> Attribute {
    Attribute::new(None, "name of the target", AttrType::string())
}

pub fn internal_attrs_platform_info_provider_id() -> &'static Arc<ProviderId> {
    static PLATFORM_INFO_PROVIDER_ID: Lazy<Arc<ProviderId>> = Lazy::new(|| {
        // Hardcode provider name, because we do not depend on providers here.
        Arc::new(ProviderId {
            path: None,
            name: "PlatformInfo".to_owned(),
        })
    });
    &PLATFORM_INFO_PROVIDER_ID
}

fn default_target_platform_attribute() -> Attribute {
    Attribute::new(
        Some(Arc::new(CoercedAttr::None)),
        "specifies the default target platform, used when no platforms are specified on the command line",
        AttrType::option(AttrType::dep(ProviderIdSet::from(vec![
            internal_attrs_platform_info_provider_id().dupe(),
        ]))),
    )
}

fn target_compatible_with_attribute() -> Attribute {
    let entry_type = AttrType::configuration_dep();
    Attribute::new(
        Some(Arc::new(AnyAttrType::empty_list())),
        "a list of constraints that are required to be satisfied for this target to be compatible with a configuration",
        AttrType::list(entry_type),
    )
}

fn exec_compatible_with_attribute() -> Attribute {
    let entry_type = AttrType::configuration_dep();
    Attribute::new(
        Some(Arc::new(AnyAttrType::empty_list())),
        "a list of constraints that are required to be satisfied for this target to be compatible with an execution platform",
        AttrType::list(entry_type),
    )
}

fn visibility_attribute() -> Attribute {
    Attribute::new(
        Some(Arc::new(CoercedAttr::Visibility(
            VisibilitySpecification::DEFAULT,
        ))),
        "a list of visibility patterns restricting what targets can depend on this one",
        AttrType::visibility(),
    )
}

fn within_view_attribute() -> Attribute {
    Attribute::new(
        Some(Arc::new(CoercedAttr::WithinView(
            WithinViewSpecification::PUBLIC,
        ))),
        "a list of visibility patterns restricting what this target can depend on",
        AttrType::within_view(),
    )
}

fn tests_attribute() -> Attribute {
    let entry_type = AttrType::label();
    Attribute::new(
        Some(Arc::new(AnyAttrType::empty_list())),
        "a list of targets that provide tests for this one",
        AttrType::list(entry_type),
    )
}

pub fn internal_attrs() -> &'static OrderedMap<&'static str, Attribute> {
    static ATTRS: Lazy<OrderedMap<&'static str, Attribute>> = Lazy::new(|| {
        OrderedMap::from_iter([
            (NAME_ATTRIBUTE_FIELD, name_attribute()),
            (
                DEFAULT_TARGET_PLATFORM_ATTRIBUTE_FIELD,
                default_target_platform_attribute(),
            ),
            (
                TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD,
                target_compatible_with_attribute(),
            ),
            (
                LEGACY_TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD,
                target_compatible_with_attribute(),
            ),
            (
                EXEC_COMPATIBLE_WITH_ATTRIBUTE_FIELD,
                exec_compatible_with_attribute(),
            ),
            (VISIBILITY_ATTRIBUTE_FIELD, visibility_attribute()),
            (WITHIN_VIEW_ATTRIBUTE_FIELD, within_view_attribute()),
            (TESTS_ATTRIBUTE_FIELD, tests_attribute()),
        ])
    });
    &ATTRS
}

pub fn attr_is_configurable(name: &str) -> AttrIsConfigurable {
    // `compatible_with` is not configurable in Buck v1.
    if name == NAME_ATTRIBUTE_FIELD
        || name == LEGACY_TARGET_COMPATIBLE_WITH_ATTRIBUTE_FIELD
        || name == DEFAULT_TARGET_PLATFORM_ATTRIBUTE_FIELD
        // visibility attributes aren't configurable so that we can cache them on targetnodes.
        || name == VISIBILITY_ATTRIBUTE_FIELD
        || name == WITHIN_VIEW_ATTRIBUTE_FIELD
    {
        AttrIsConfigurable::No
    } else {
        AttrIsConfigurable::Yes
    }
}


/*
 * 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 crate::attrs::fmt_context::AttrFmtContext;

pub trait ToJsonWithContext {
    fn to_json(&self, ctx: &AttrFmtContext) -> anyhow::Result<serde_json::Value>;
}


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

pub mod attr;
pub mod attr_type;
pub mod coerced_attr;
pub mod coerced_attr_full;
pub mod coerced_attr_with_type;
pub mod coerced_deps_collector;
pub mod coerced_path;
pub mod coercion_context;
pub mod configurable;
pub mod configuration_context;
pub mod configured_attr;
pub mod configured_attr_full;
pub mod configured_info;
pub mod configured_traversal;
pub mod display;
pub mod fmt_context;
pub mod hacks;
pub mod id;
pub mod inspect_options;
pub mod internal;
pub mod json;
pub mod serialize;
pub mod spec;
pub mod testing;
pub mod traversal;
pub mod values;


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

use serde::Serialize;
use serde::Serializer;

use crate::attrs::fmt_context::AttrFmtContext;

pub trait AttrSerializeWithContext {
    fn serialize_with_ctx<S>(&self, ctx: &AttrFmtContext, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer;
}

pub trait AttrSerializeWithContextExt: AttrSerializeWithContext {
    fn as_serialize<'a>(
        &'a self,
        ctx: &'a AttrFmtContext,
    ) -> AttrSerializeWithContextAsSerialize<'a, Self> {
        AttrSerializeWithContextAsSerialize { ctx, value: self }
    }
}

impl<T: AttrSerializeWithContext + ?Sized> AttrSerializeWithContextExt for T {}

pub struct AttrSerializeWithContextAsSerialize<'a, A: ?Sized> {
    ctx: &'a AttrFmtContext,
    value: &'a A,
}

impl<A: AttrSerializeWithContext + ?Sized> Serialize
    for AttrSerializeWithContextAsSerialize<'_, A>
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.value.serialize_with_ctx(self.ctx, serializer)
    }
}


/*
 * 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::collections::ordered_map::OrderedMap;
use once_cell::sync::Lazy;
use starlark_map::small_map;

use crate::attrs::attr::Attribute;
use crate::attrs::coerced_attr::CoercedAttr;
use crate::attrs::coerced_attr_full::CoercedAttrFull;
use crate::attrs::id::AttributeId;
use crate::attrs::inspect_options::AttrInspectOptions;
use crate::attrs::internal::internal_attrs;
use crate::attrs::internal::NAME_ATTRIBUTE_FIELD;
use crate::attrs::internal::VISIBILITY_ATTRIBUTE_FIELD;
use crate::attrs::values::AttrValues;

/// AttributeSpec holds the specification for a rules attributes as defined in the rule() call. This
/// is split into a mapping of "attribute name" -> "attribute id". The Attributes are stored in a vec
/// that can then be indexed using the name->id mapping (the id is really just an index into this vec).
///
/// For its attribute values, a TargetNode will hold a sorted Vec<(AttributeId, CoercedAttr)> that will have values
/// only for the values that are explicitly set. Default values need to be looked up through the AttributeSpec.
#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub struct AttributeSpec {
    attributes: OrderedMap<String, Attribute>,
}

#[derive(Debug, thiserror::Error)]
pub(crate) enum AttributeSpecError {
    #[error("User provided attribute `{0}` overrides internal attribute")]
    InternalAttributeRedefined(String),
    #[error("Duplicate attribute `{0}`")]
    DuplicateAttribute(String),
    #[error("Rule definition has no attribute `{0}`")]
    UnknownAttribute(String),
    #[error("Too many attributes: {0} > {}", AttributeId::MAX_INDEX)]
    TooManyAttributes(usize),
}

impl AttributeSpec {
    pub(crate) fn name_attr_id() -> AttributeId {
        static ID: Lazy<AttributeId> = Lazy::new(|| {
            let index_in_attribute_spec = u16::try_from(
                internal_attrs()
                    .keys()
                    .position(|name| *name == NAME_ATTRIBUTE_FIELD)
                    .unwrap(),
            )
            .unwrap();
            AttributeId {
                index_in_attribute_spec,
            }
        });
        *ID
    }

    pub(crate) fn visibility_attr_id() -> AttributeId {
        static ID: Lazy<AttributeId> = Lazy::new(|| {
            let index_in_attribute_spec = u16::try_from(
                internal_attrs()
                    .keys()
                    .position(|name| *name == VISIBILITY_ATTRIBUTE_FIELD)
                    .unwrap(),
            )
            .unwrap();
            AttributeId {
                index_in_attribute_spec,
            }
        });
        *ID
    }

    fn new(attributes: OrderedMap<String, Attribute>) -> anyhow::Result<AttributeSpec> {
        if attributes.len() > AttributeId::MAX_INDEX as usize {
            return Err(AttributeSpecError::TooManyAttributes(attributes.len()).into());
        }
        Ok(AttributeSpec { attributes })
    }

    pub fn from(attributes: Vec<(String, Attribute)>) -> anyhow::Result<Self> {
        let internal_attrs = internal_attrs();

        let mut instances: OrderedMap<String, Attribute> =
            OrderedMap::with_capacity(attributes.len());
        for (name, instance) in internal_attrs {
            let prev = instances.insert((*name).to_owned(), instance.clone());
            if prev.is_some() {
                unreachable!("duplicate internal attr: '{}'", name);
            }
        }

        for (name, instance) in attributes.into_iter() {
            match instances.entry(name) {
                small_map::Entry::Vacant(e) => {
                    e.insert(instance);
                }
                small_map::Entry::Occupied(e) => {
                    let name = e.key();
                    if internal_attrs.contains_key(name.as_str()) {
                        return Err(anyhow::anyhow!(
                            AttributeSpecError::InternalAttributeRedefined(name.to_owned())
                        ));
                    } else {
                        return Err(anyhow::anyhow!(AttributeSpecError::DuplicateAttribute(
                            name.to_owned()
                        )));
                    }
                }
            }
        }

        AttributeSpec::new(instances)
    }

    #[allow(clippy::len_without_is_empty)]
    pub fn len(&self) -> usize {
        self.attributes.len()
    }

    pub fn attr_specs(&self) -> impl ExactSizeIterator<Item = (&str, AttributeId, &Attribute)> {
        self.attributes
            .iter()
            .enumerate()
            .map(|(index_in_attribute_spec, (name, attribute))| {
                (
                    name.as_str(),
                    AttributeId {
                        index_in_attribute_spec: index_in_attribute_spec as u16,
                    },
                    attribute,
                )
            })
    }

    fn attribute_by_id(&self, id: AttributeId) -> &Attribute {
        self.attributes
            .get_index(id.index_in_attribute_spec as usize)
            .unwrap()
            .1
    }

    fn attribute_name_by_id(&self, id: AttributeId) -> &str {
        self.attributes
            .get_index(id.index_in_attribute_spec as usize)
            .unwrap()
            .0
    }

    pub(crate) fn attribute_id_by_name(&self, name: &str) -> Option<AttributeId> {
        self.attributes
            .get_index_of(name)
            .map(|index_in_attribute_spec| AttributeId {
                index_in_attribute_spec: index_in_attribute_spec as u16,
            })
    }

    pub fn attribute(&self, name: &str) -> Option<&Attribute> {
        self.attributes.get(name)
    }

    /// Returns an iterator over all of the attribute (name, value) pairs.
    pub fn attrs<'v>(
        &'v self,
        attr_values: &'v AttrValues,
        opts: AttrInspectOptions,
    ) -> impl Iterator<Item = CoercedAttrFull<'v>> {
        let mut pos = 0;
        let mut entry: Option<(AttributeId, &CoercedAttr)> = attr_values.get_by_index(0);

        self.attr_specs()
            .filter_map(move |(name, idx, attr)| match entry {
                Some((entry_idx, entry_attr)) if entry_idx == idx => {
                    pos += 1;
                    entry = attr_values.get_by_index(pos);
                    Some(CoercedAttrFull {
                        name,
                        attr,
                        value: entry_attr,
                    })
                }
                _ => {
                    let default: &CoercedAttr = match attr.default() {
                        Some(default) => default,
                        None => panic!("no default for attribute `{}`", name),
                    };
                    if opts.include_default() {
                        Some(CoercedAttrFull {
                            name,
                            attr,
                            value: default,
                        })
                    } else {
                        None
                    }
                }
            })
    }

    pub fn known_attr_or_none<'v>(
        &'v self,
        idx: AttributeId,
        attr_values: &'v AttrValues,
        opts: AttrInspectOptions,
    ) -> Option<&'v CoercedAttr> {
        if let Some(attr) = attr_values.get(idx) {
            return Some(attr);
        }

        if opts.include_default() {
            return self.attribute_by_id(idx).default().map(|v| &**v);
        }
        None
    }

    pub fn attr_or_none<'v>(
        &'v self,
        attr_values: &'v AttrValues,
        name: &str,
        opts: AttrInspectOptions,
    ) -> Option<CoercedAttrFull<'v>> {
        if let Some(idx) = self.attribute_id_by_name(name) {
            // Same as function parameter `name` but with lifetime of `self`.
            let name = self.attribute_name_by_id(idx);
            let attr = self.attribute_by_id(idx);
            let value = self.known_attr_or_none(idx, attr_values, opts)?;
            Some(CoercedAttrFull { name, attr, value })
        } else {
            None
        }
    }

    pub fn attr<'v>(
        &'v self,
        attr_values: &'v AttrValues,
        key: &str,
        opts: AttrInspectOptions,
    ) -> anyhow::Result<Option<&'v CoercedAttr>> {
        if let Some(idx) = self.attribute_id_by_name(key) {
            Ok(self.known_attr_or_none(idx, attr_values, opts))
        } else {
            Err(AttributeSpecError::UnknownAttribute(key.to_owned()).into())
        }
    }
}

pub(crate) mod testing {

    use buck2_util::collections::ordered_map::OrderedMap;

    use crate::attrs::attr::Attribute;
    use crate::attrs::spec::AttributeSpec;

    impl AttributeSpec {
        pub(crate) fn testing_new(attributes: OrderedMap<String, Attribute>) -> AttributeSpec {
            AttributeSpec::from(attributes.into_iter().collect()).unwrap()
        }
    }
}