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

//! Implements target pattern resolution.
//!
#![doc = include_str!("target_pattern.md")]

mod ascii_pattern;
pub mod parse_package;
pub mod pattern_type;
pub mod query_file_literal;

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

use allocative::Allocative;
use anyhow::Context;
use dupe::Dupe;
use once_cell::sync::Lazy;
use pattern_type::ConfigurationPredicate;
use pattern_type::ConfiguredProvidersPatternExtra;
use pattern_type::PatternType;
use pattern_type::ProvidersPatternExtra;
use pattern_type::TargetPatternExtra;
use regex::Regex;

use crate::cells::alias::CellAlias;
use crate::cells::cell_path::CellPath;
use crate::cells::cell_path::CellPathCow;
use crate::cells::cell_path::CellPathRef;
use crate::cells::cell_root_path::CellRootPathBuf;
use crate::cells::name::CellName;
use crate::cells::paths::CellRelativePath;
use crate::cells::CellResolver;
use crate::configuration::bound_label::BoundConfigurationLabel;
use crate::configuration::builtin::BuiltinPlatform;
use crate::configuration::hash::ConfigurationHash;
use crate::fs::paths::forward_rel_path::ForwardRelativePath;
use crate::package::PackageLabel;
use crate::pattern::ascii_pattern::split1_opt_ascii;
use crate::pattern::ascii_pattern::strip_suffix_ascii;
use crate::pattern::ascii_pattern::trim_prefix_ascii;
use crate::pattern::ascii_pattern::AsciiChar;
use crate::pattern::ascii_pattern::AsciiStr;
use crate::pattern::ascii_pattern::AsciiStr2;
use crate::provider::flavors::map_flavors;
use crate::provider::label::NonDefaultProvidersName;
use crate::provider::label::ProviderName;
use crate::provider::label::ProvidersLabel;
use crate::provider::label::ProvidersName;
use crate::target::label::TargetLabel;
use crate::target::name::TargetName;
use crate::target::name::TargetNameRef;
use crate::target_aliases::TargetAliasResolver;

#[derive(thiserror::Error, Debug)]
enum TargetPatternParseError {
    #[error("Expected a `:`, a trailing `/...` or the literal `...`.")]
    UnexpectedFormat,
    #[error("Package is empty")]
    PackageIsEmpty,
    #[error("Must be absolute, with a `//` or no package just `:`.")]
    AbsoluteRequired,
    #[error(
        "Packages may not end with a trailing `/` (except when provided on the command line where it's tolerated)"
    )]
    PackageTrailingSlash,
    #[error("Required a target literal, but got a non-literal pattern `{0}`")]
    TargetLiteralRequired(String),
    #[error(
        "You may be trying to use a macro instead of a target pattern. Macro usage is invalid here"
    )]
    PossibleMacroUsage,
    #[error("Expecting {0} pattern, got: `{1}`")]
    ExpectingPatternOfType(&'static str, String),
    #[error("Configuration part of the pattern must be enclosed in `()`")]
    ConfigurationPartMustBeEnclosedInParentheses,
    #[error(
        "Cell resolver cell `{0}` does not match the given relative dir `{1}` (internal error)"
    )]
    CellResolverCellDoesNotMatchWorkingDir(CellName, CellPath),
    #[error("Pattern `{0}` is parsed as `{1}` which crosses cell boundaries. Try `{2}` instead")]
    PatternCrossesCellBoundaries(String, String, String),
}

pub fn display_precise_pattern<'a, T: PatternType>(
    package: &'a PackageLabel,
    target_name: &'a TargetNameRef,
    extra: &'a T,
) -> impl Display + 'a {
    #[derive(derive_more::Display)]
    #[display(fmt = "{}:{}{}", package, target_name, extra)]
    struct Impl<'a, T: PatternType> {
        package: &'a PackageLabel,
        target_name: &'a TargetNameRef,
        extra: &'a T,
    }
    Impl {
        package,
        target_name,
        extra,
    }
}

/// Extract provider name from a target pattern.
pub(crate) fn split_providers_name(s: &str) -> anyhow::Result<(&str, ProvidersName)> {
    if let Some((t, flavors)) = split1_opt_ascii(s, AsciiChar::new('#')) {
        let name = map_flavors(flavors, s)?;
        Ok((t, name))
    } else if let Some((t, p)) = split1_opt_ascii(s, AsciiChar::new('[')) {
        let mut names = Vec::new();

        let mut remaining = if let Some((p, r)) = split1_opt_ascii(p, AsciiChar::new(']')) {
            names.push(ProviderName::new(p.to_owned())?);
            r
        } else {
            return Err(anyhow::anyhow!(
                "target pattern with `[` must end with `]` to mark end of providers set label"
            ));
        };

        while !remaining.is_empty() {
            if let Some(("", r)) = split1_opt_ascii(remaining, AsciiChar::new('[')) {
                if let Some((p, r)) = split1_opt_ascii(r, AsciiChar::new(']')) {
                    names.push(ProviderName::new(p.to_owned())?);
                    remaining = r;
                    continue;
                }
            }
            return Err(anyhow::anyhow!(
                "target pattern with `[` must end with `]` to mark end of providers set label"
            ));
        }

        Ok((
            t,
            ProvidersName::NonDefault(Box::new(NonDefaultProvidersName::Named(
                names.into_boxed_slice(),
            ))),
        ))
    } else {
        Ok((s, ProvidersName::Default))
    }
}

/// A parsed target pattern.
#[derive(Clone, Debug, Hash, Eq, PartialEq, Allocative)]
pub enum ParsedPattern<T: PatternType> {
    /// A target pattern that matches a explicit target pattern type T. See
    /// `PatternType` for pattern
    Target(PackageLabel, TargetName, T),
    /// A target pattern that matches an entire package. Ex. `//some/package:`
    Package(PackageLabel),
    /// A target pattern that matches all recursive packages. Ex.
    /// `//some/package/...`. The path component here is not required to be
    /// an actual package (i.e. a build file is not required at the path)
    /// and so we don't hold this as a [PackageLabel].
    Recursive(CellPath),
}

impl ParsedPattern<TargetPatternExtra> {
    /// Extract [`TargetLabel`] from a [`ParsedPattern`].
    pub fn as_target_label(self, original: &str) -> anyhow::Result<TargetLabel> {
        let (package, target_name, TargetPatternExtra) = self.as_literal(original)?;
        Ok(TargetLabel::new(package, target_name.as_ref()))
    }

    /// Check if a [`ParsedPattern`] matches a [`TargetLabel`]
    pub fn matches(&self, target: &TargetLabel) -> bool {
        let target_pkg = target.pkg();
        match self {
            ParsedPattern::Target(pkg, t, TargetPatternExtra) => {
                *pkg == target_pkg && t.as_ref() == target.name()
            }
            ParsedPattern::Package(pkg) => target_pkg.as_cell_path() == pkg.as_cell_path(),
            ParsedPattern::Recursive(cell_path) => {
                target_pkg.as_cell_path().starts_with(cell_path.as_ref())
            }
        }
    }
}

impl ParsedPattern<ProvidersPatternExtra> {
    /// Extract [`ProvidersLabel`] from a [`ParsedPattern`].
    pub fn as_providers_label(self, original: &str) -> anyhow::Result<ProvidersLabel> {
        let (package, target, ProvidersPatternExtra { providers }) = self.as_literal(original)?;
        Ok(ProvidersLabel::new(
            TargetLabel::new(package, target.as_ref()),
            providers,
        ))
    }
}

impl<T: PatternType> ParsedPattern<T> {
    pub(crate) fn cell_path(&self) -> CellPathRef {
        match self {
            ParsedPattern::Target(pkg, _, _) => pkg.as_cell_path(),
            ParsedPattern::Package(pkg) => pkg.as_cell_path(),
            ParsedPattern::Recursive(cell_path) => cell_path.as_ref(),
        }
    }

    pub fn try_map<U: PatternType>(
        self,
        f: impl FnOnce(T) -> anyhow::Result<U>,
    ) -> anyhow::Result<ParsedPattern<U>> {
        match self {
            ParsedPattern::Target(package, target_name, val) => {
                Ok(ParsedPattern::Target(package, target_name, f(val)?))
            }
            ParsedPattern::Package(package) => Ok(ParsedPattern::Package(package)),
            ParsedPattern::Recursive(cell_path) => Ok(ParsedPattern::Recursive(cell_path)),
        }
    }

    /// Extract a literal from a [ParsedPattern], or `Err` if it is not a literal.
    pub fn as_literal(self, original: &str) -> anyhow::Result<(PackageLabel, TargetName, T)> {
        // FIXME: Would be better if we had a Display on self, so we could produce a nice error message.
        //        For now, just require the original string to be passed in for good errors.
        match self {
            ParsedPattern::Target(package, target_name, val) => Ok((package, target_name, val)),
            _ => Err(TargetPatternParseError::TargetLiteralRequired(original.to_owned()).into()),
        }
    }

    /// Parse a TargetPattern, but where there there is no relative directory.
    pub fn parse_precise(
        pattern: &str,
        cell: CellName,
        cell_resolver: &CellResolver,
    ) -> anyhow::Result<Self> {
        parse_target_pattern(
            cell,
            cell_resolver,
            None,
            TargetParsingOptions::precise(),
            pattern,
        )
        .with_context(|| {
            format!(
                "Invalid absolute target pattern `{}` is not allowed",
                pattern
            )
        })
    }

    pub fn parsed_opt_absolute(
        pattern: &str,
        relative_dir: Option<CellPathRef>,
        cell: CellName,
        cell_resolver: &CellResolver,
    ) -> anyhow::Result<Self> {
        parse_target_pattern(
            cell,
            cell_resolver,
            None,
            TargetParsingOptions {
                relative: TargetParsingRel::RequireAbsolute(relative_dir),
                infer_target: false,
                strip_package_trailing_slash: false,
            },
            pattern,
        )
        .with_context(|| {
            format!(
                "Invalid absolute target pattern `{}` is not allowed",
                pattern
            )
        })
    }

    /// Parse a TargetPattern out, resolving aliases via `cell_resolver`, and resolving relative
    /// targets via `enclosing_package`, if provided.
    /// Allows everything from `parse_absolute`, plus relative patterns.
    pub fn parse_relative(
        target_alias_resolver: &dyn TargetAliasResolver,
        relative_dir: CellPathRef,
        pattern: &str,
        cell_resolver: &CellResolver,
    ) -> anyhow::Result<Self> {
        parse_target_pattern(
            relative_dir.cell(),
            cell_resolver,
            Some(target_alias_resolver),
            TargetParsingOptions {
                relative: TargetParsingRel::AllowRelative(relative_dir),
                infer_target: false,
                strip_package_trailing_slash: false,
            },
            pattern,
        )
        .with_context(|| {
            format!(
                "Invalid relative target pattern `{}` is not allowed",
                pattern
            )
        })
    }

    /// Parse a TargetPattern out, resolving aliases via `cell_resolver`, resolving relative
    /// targets via `relative_dir`, inferring a target name if no target or recursive pattern
    /// is provided (e.g. `//foo/bar` is inferred to be equivalent to `//foo/bar:bar`), and
    /// stripping trailing `/` in package names instead of rejecting them.
    ///
    /// This should only be used with user-provided command line arguments, as precision is
    /// generally preferred elsewhere.
    pub fn parse_relaxed(
        target_alias_resolver: &dyn TargetAliasResolver,
        relative_dir: CellPathRef,
        pattern: &str,
        cell_resolver: &CellResolver,
    ) -> anyhow::Result<Self> {
        parse_target_pattern(
            relative_dir.cell(),
            cell_resolver,
            Some(target_alias_resolver),
            TargetParsingOptions {
                relative: TargetParsingRel::AllowRelative(relative_dir),
                infer_target: true,
                strip_package_trailing_slash: true,
            },
            pattern,
        )
        .with_context(|| format!("Parsing target pattern `{}`", pattern))
    }

    pub fn testing_parse(pattern: &str) -> Self {
        let cell_name = pattern.split_once("//").unwrap().0;
        let cell_name = CellName::testing_new(cell_name);
        let cell_resolver =
            CellResolver::testing_with_name_and_path(cell_name, CellRootPathBuf::testing_new(""));
        Self::parse_precise(pattern, cell_name, &cell_resolver).unwrap()
    }
}

impl<T: PatternType> Display for ParsedPattern<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ParsedPattern::Target(package, target_name, pattern) => {
                write!(
                    f,
                    "{}",
                    display_precise_pattern(package, target_name.as_ref(), pattern)
                )
            }
            ParsedPattern::Package(package) => {
                write!(f, "{}:", package.as_cell_path())
            }
            ParsedPattern::Recursive(path) => {
                if path.path().is_empty() {
                    write!(f, "{}...", path)
                } else {
                    write!(f, "{}/...", path)
                }
            }
        }
    }
}

#[derive(Debug)]
pub struct PatternParts<'a, T: PatternType> {
    /// Is there a `foo//` or `//` part.
    pub cell_alias: Option<&'a str>,
    pub pattern: PatternDataOrAmbiguous<'a, T>,
}

impl<'a, T: PatternType> PatternParts<'a, T> {
    fn try_map<U: PatternType, F: FnOnce(T) -> anyhow::Result<U>>(
        self,
        f: F,
    ) -> anyhow::Result<PatternParts<'a, U>> {
        let PatternParts {
            cell_alias,
            pattern,
        } = self;
        Ok(PatternParts {
            cell_alias,
            pattern: pattern.try_map(f)?,
        })
    }
}

#[derive(Debug, derive_more::From)]
pub enum PatternDataOrAmbiguous<'a, T: PatternType> {
    /// We successfully extracted PatternData.
    PatternData(PatternData<'a, T>),

    /// This pattern looks like `foo/bar`, `foo/bar/` or `foo`. It could be a package + target if
    /// we allow inference (i.e. expanding `foo/bar:bar`).
    Ambiguous {
        /// The pattern. If we allow inference this will become the package.
        pattern: &'a str,
        /// Whether we should strip trailing slashes out of this pattern before doing inference
        /// (rather than throwing an error).
        strip_package_trailing_slash: bool,
        extra: T,
    },
}

impl<'a, T: PatternType> PatternDataOrAmbiguous<'a, T> {
    fn try_map<U: PatternType>(
        self,
        f: impl FnOnce(T) -> anyhow::Result<U>,
    ) -> anyhow::Result<PatternDataOrAmbiguous<'a, U>> {
        match self {
            PatternDataOrAmbiguous::PatternData(d) => {
                Ok(PatternDataOrAmbiguous::PatternData(d.try_map(f)?))
            }
            PatternDataOrAmbiguous::Ambiguous {
                pattern,
                strip_package_trailing_slash,
                extra,
            } => Ok(PatternDataOrAmbiguous::Ambiguous {
                pattern,
                strip_package_trailing_slash,
                extra: f(extra)?,
            }),
        }
    }
}

impl<'a, T> PatternDataOrAmbiguous<'a, T>
where
    T: PatternType,
{
    /// If the pattern is ambiguous, try to infer a target. This would convert `foo/bar` into
    /// `foo/bar:bar`.
    pub fn infer_target(self) -> anyhow::Result<PatternData<'a, T>> {
        match self {
            Self::PatternData(d) => Ok(d),
            Self::Ambiguous {
                pattern,
                strip_package_trailing_slash,
                extra,
            } => {
                let package = normalize_package(pattern, strip_package_trailing_slash)?;

                let target = package
                    .file_name()
                    .context(TargetPatternParseError::PackageIsEmpty)?;

                let target_name = TargetName::new(target.as_ref())?;

                Ok(PatternData::TargetInPackage {
                    package,
                    target_name,
                    extra,
                })
            }
        }
    }

    /// If the pattern is ambiguous, error out.
    pub fn reject_ambiguity(self) -> anyhow::Result<PatternData<'a, T>> {
        match self {
            Self::PatternData(d) => Ok(d),
            Self::Ambiguous { pattern, .. } => {
                // Check if the user maybe tried to use a macro
                if pattern.contains('$')
                    && pattern.contains(' ')
                    && pattern.contains('(')
                    && pattern.contains(')')
                {
                    return Err(TargetPatternParseError::PossibleMacroUsage.into());
                }
                Err(TargetPatternParseError::UnexpectedFormat.into())
            }
        }
    }
}

/// The pattern data we extracted.
#[derive(Debug)]
pub enum PatternData<'a, T: PatternType> {
    /// A pattern like `foo/bar/...`.
    Recursive { package: &'a ForwardRelativePath },

    /// A pattern like `foo/bar:`, or `:`
    AllTargetsInPackage { package: &'a ForwardRelativePath },

    /// A pattern like `foo/bar:qux`, or `:qux`. The target will never be empty.
    TargetInPackage {
        package: &'a ForwardRelativePath,
        target_name: TargetName,
        extra: T,
    },
}

impl<'a, T: PatternType> PatternData<'a, T> {
    fn try_map<U: PatternType>(
        self,
        f: impl FnOnce(T) -> anyhow::Result<U>,
    ) -> anyhow::Result<PatternData<'a, U>> {
        match self {
            PatternData::Recursive { package } => Ok(PatternData::Recursive { package }),
            PatternData::AllTargetsInPackage { package } => {
                Ok(PatternData::AllTargetsInPackage { package })
            }
            PatternData::TargetInPackage {
                package,
                target_name,
                extra,
            } => Ok(PatternData::TargetInPackage {
                package,
                target_name,
                extra: f(extra)?,
            }),
        }
    }

    pub fn package_path(&self) -> &'a ForwardRelativePath {
        match self {
            Self::Recursive { package } => package,
            Self::AllTargetsInPackage { package } => package,
            Self::TargetInPackage { package, .. } => package,
        }
    }

    pub fn target(&self) -> Option<(&TargetName, &T)> {
        match self {
            Self::Recursive { .. } => None,
            Self::AllTargetsInPackage { .. } => None,
            Self::TargetInPackage {
                target_name, extra, ..
            } => Some((target_name, extra)),
        }
    }

    /// Whether this is a target that looks like `:target`.
    pub fn is_adjacent_target(&self) -> bool {
        self.package_path().is_empty() && self.target().is_some()
    }
}

// Splits a pattern into cell alias and forward relative path if "//" is present, otherwise returns None,
pub fn maybe_split_cell_alias_and_relative_path<'a>(
    pattern: &'a str,
) -> anyhow::Result<Option<(CellAlias, &'a ForwardRelativePath)>> {
    Ok(match split1_opt_ascii(pattern, AsciiStr2::new("//")) {
        Some((a, p)) => Some((
            CellAlias::new(trim_prefix_ascii(a, AsciiChar::new('@')).to_owned()),
            ForwardRelativePath::new(p)?,
        )),
        None => None,
    })
}

fn lex_provider_pattern<'a>(
    pattern: &'a str,
    strip_package_trailing_slash: bool,
) -> anyhow::Result<PatternParts<ProvidersPatternExtra>> {
    let (cell_alias, pattern) = match split1_opt_ascii(pattern, AsciiStr2::new("//")) {
        Some((a, p)) => (Some(trim_prefix_ascii(a, AsciiChar::new('@'))), p),
        None => (None, pattern),
    };

    let pattern = match split1_opt_ascii(pattern, AsciiChar::new(':')) {
        Some((package, "")) => PatternData::AllTargetsInPackage {
            package: normalize_package(package, strip_package_trailing_slash)?,
        }
        .into(),
        Some((package, target)) => {
            let (target, providers) = split_providers_name(target)?;
            let target_name = TargetName::new(target)?;
            let extra = ProvidersPatternExtra { providers };
            PatternData::TargetInPackage {
                package: normalize_package(package, strip_package_trailing_slash)?,
                target_name,
                extra,
            }
            .into()
        }
        None => {
            if let Some(package) = strip_suffix_ascii(pattern, AsciiStr::new("/...")) {
                PatternData::Recursive {
                    package: ForwardRelativePath::new(package)?,
                }
                .into()
            } else if pattern == "..." {
                PatternData::Recursive {
                    package: ForwardRelativePath::new("")?,
                }
                .into()
            } else if !pattern.is_empty() {
                let (pattern, providers) = split_providers_name(pattern)?;
                PatternDataOrAmbiguous::Ambiguous {
                    pattern,
                    strip_package_trailing_slash,
                    extra: ProvidersPatternExtra { providers },
                }
            } else {
                return Err(TargetPatternParseError::UnexpectedFormat.into());
            }
        }
    };

    Ok(PatternParts {
        cell_alias,
        pattern,
    })
}

fn lex_configuration_predicate(pattern: &str) -> anyhow::Result<ConfigurationPredicate> {
    let pattern = pattern
        .strip_prefix('(')
        .context(TargetPatternParseError::ConfigurationPartMustBeEnclosedInParentheses)?;
    let pattern = pattern
        .strip_suffix(')')
        .context(TargetPatternParseError::ConfigurationPartMustBeEnclosedInParentheses)?;
    match pattern.split_once('#') {
        Some((cfg, hash)) => {
            let cfg = BoundConfigurationLabel::new(cfg.to_owned())?;
            let hash = ConfigurationHash::from_str(hash)?;
            Ok(ConfigurationPredicate::Bound(cfg, Some(hash)))
        }
        None => {
            if let Some(builtin) = BuiltinPlatform::from_label(pattern) {
                Ok(ConfigurationPredicate::Builtin(builtin))
            } else {
                Ok(ConfigurationPredicate::Bound(
                    BoundConfigurationLabel::new(pattern.to_owned())?,
                    None,
                ))
            }
        }
    }
}

/// Split target pattern and configuration preserving parentheses for better diagnostics.
fn split_cfg(s: &str) -> Option<(&str, &str)> {
    // Fast path.
    if !s.contains(' ') {
        return None;
    }

    let mut braces: u32 = 0;
    for (i, c) in s.char_indices() {
        match c {
            '(' => braces += 1,
            ')' => match braces.checked_sub(1) {
                Some(b) => braces = b,
                None => {
                    // Pattern is invalid, let parser fail elsewhere.
                    return None;
                }
            },
            ' ' if braces == 0 => return Some((&s[..i], &s[i + 1..])),
            _ => {}
        }
    }
    None
}

pub fn lex_configured_providers_pattern<'a>(
    pattern: &'a str,
    strip_package_trailing_slash: bool,
) -> anyhow::Result<PatternParts<ConfiguredProvidersPatternExtra>> {
    let (provider_pattern, cfg) = match split_cfg(pattern) {
        Some((providers, cfg)) => {
            let provider_pattern = lex_provider_pattern(providers, strip_package_trailing_slash)?;
            let cfg = lex_configuration_predicate(cfg)?;
            (provider_pattern, cfg)
        }
        None => (
            lex_provider_pattern(pattern, strip_package_trailing_slash)?,
            ConfigurationPredicate::Any,
        ),
    };
    provider_pattern.try_map(|ProvidersPatternExtra { providers }| {
        Ok(ConfiguredProvidersPatternExtra { providers, cfg })
    })
}

// Lex the target pattern into the relevant pieces.
pub fn lex_target_pattern<'a, T: PatternType>(
    pattern: &'a str,
    strip_package_trailing_slash: bool,
) -> anyhow::Result<PatternParts<T>> {
    let provider_pattern = lex_configured_providers_pattern(pattern, strip_package_trailing_slash)?;
    provider_pattern
        .try_map(|extra| T::from_configured_providers(extra))
        .with_context(|| {
            // This can only fail when `PatternType = TargetName`, so the message is correct.
            TargetPatternParseError::ExpectingPatternOfType(T::NAME, pattern.to_owned())
        })
}

fn normalize_package<'a>(
    package: &'a str,
    strip_package_trailing_slash: bool,
) -> anyhow::Result<&'a ForwardRelativePath> {
    // Strip or reject trailing `/`, such as in `foo/:bar`.
    if let Some(stripped) = strip_suffix_ascii(package, AsciiChar::new('/')) {
        if strip_package_trailing_slash {
            return ForwardRelativePath::new(stripped);
        } else {
            return Err(anyhow::Error::from(
                TargetPatternParseError::PackageTrailingSlash,
            ));
        }
    }

    ForwardRelativePath::new(package)
}

#[derive(Clone, Dupe)]
enum TargetParsingRel<'a> {
    /// The dir this pattern should be interpreted relative to.
    AllowRelative(CellPathRef<'a>),
    /// The dir this pattern should be interpreted relative to.
    /// This is only used for targets such as `:foo`.
    RequireAbsolute(Option<CellPathRef<'a>>),
}

impl<'a> TargetParsingRel<'a> {
    fn dir(&self) -> Option<CellPathRef<'a>> {
        match self {
            TargetParsingRel::AllowRelative(dir) => Some(*dir),
            TargetParsingRel::RequireAbsolute(dir) => *dir,
        }
    }

    fn allow_relative(&self) -> bool {
        match self {
            TargetParsingRel::AllowRelative(_) => true,
            TargetParsingRel::RequireAbsolute(_) => false,
        }
    }
}

#[derive(Clone, Dupe)]
struct TargetParsingOptions<'a> {
    relative: TargetParsingRel<'a>,
    /// Whether to infer the target in a pattern such as `foo/bar` (to `foo/bar:bar`).
    infer_target: bool,
    /// Whether to strip trailing slashes in package names, in e.g. `foo/bar/` or `foo/bar/:qux`.
    /// If not set, trailing slashes are an error. Note that this happens before target inference
    /// (if enabled), so e.g. `foo/bar/` becomes `foo/bar:bar`.
    strip_package_trailing_slash: bool,
}

impl<'a> TargetParsingOptions<'a> {
    fn precise() -> TargetParsingOptions<'a> {
        TargetParsingOptions {
            relative: TargetParsingRel::RequireAbsolute(None),
            infer_target: false,
            strip_package_trailing_slash: false,
        }
    }
}

/// Parse a TargetPattern out, resolving aliases via `cell_resolver`, and resolving relative
/// targets via `enclosing_package`, if provided.
fn parse_target_pattern<T>(
    cell_name: CellName,
    cell_resolver: &CellResolver,
    target_alias_resolver: Option<&dyn TargetAliasResolver>,
    opts: TargetParsingOptions,
    pattern: &str,
) -> anyhow::Result<ParsedPattern<T>>
where
    T: PatternType,
{
    let parsed_pattern = parse_target_pattern_no_validate::<T>(
        cell_name,
        cell_resolver,
        target_alias_resolver,
        opts,
        pattern,
    )?;

    let crossed_path =
        cell_resolver.resolve_path_crossing_cell_boundaries(parsed_pattern.cell_path())?;
    if crossed_path != parsed_pattern.cell_path() {
        let new_pattern = match &parsed_pattern {
            ParsedPattern::Target(_, target_name, extra) => ParsedPattern::Target(
                PackageLabel::from_cell_path(crossed_path),
                target_name.dupe(),
                extra.clone(),
            ),
            ParsedPattern::Package(_) => {
                ParsedPattern::Package(PackageLabel::from_cell_path(crossed_path))
            }
            ParsedPattern::Recursive(_) => ParsedPattern::Recursive(crossed_path.to_owned()),
        };

        soft_error!(
            "pattern_crosses_cell_boundary",
            TargetPatternParseError::PatternCrossesCellBoundaries(
                pattern.to_owned(),
                parsed_pattern.to_string(),
                new_pattern.to_string(),
            )
            .into()
        )?;
    }

    Ok(parsed_pattern)
}

fn parse_target_pattern_no_validate<T>(
    cell_name: CellName,
    cell_resolver: &CellResolver,
    target_alias_resolver: Option<&dyn TargetAliasResolver>,
    opts: TargetParsingOptions,
    pattern: &str,
) -> anyhow::Result<ParsedPattern<T>>
where
    T: PatternType,
{
    let TargetParsingOptions {
        relative,
        infer_target,
        strip_package_trailing_slash,
    } = opts;

    if let Some(dir) = relative.dir() {
        if dir.cell() != cell_name {
            return Err(
                TargetPatternParseError::CellResolverCellDoesNotMatchWorkingDir(
                    cell_name,
                    dir.to_owned(),
                )
                .into(),
            );
        }
    }

    let cell_alias_resolver = cell_resolver.get(cell_name)?.cell_alias_resolver();

    let lex = lex_target_pattern(pattern, strip_package_trailing_slash)?;

    if let Some(target_alias_resolver) = target_alias_resolver {
        if let Some(aliased) =
            resolve_target_alias(cell_name, cell_resolver, target_alias_resolver, &lex)?
        {
            return Ok(aliased);
        }
    }

    let PatternParts {
        cell_alias,
        pattern,
    } = lex;

    let pattern = if infer_target {
        pattern.infer_target()?
    } else {
        pattern.reject_ambiguity()?
    };

    // This allows things of the form `//foo` (having a cell alias) or `:bar` (no cell, no package,
    // just relative target). This is a bit of a wonky  definition of "is_absolute" but we rely on
    // it.
    let is_absolute_or_adjacent = cell_alias.is_some() || pattern.is_adjacent_target();
    if !relative.allow_relative() && !is_absolute_or_adjacent {
        return Err(TargetPatternParseError::AbsoluteRequired.into());
    }

    // Prohibit parsing `:foo` as `root//:foo`.
    if relative.dir().is_none() && cell_alias.is_none() {
        soft_error!(
            "adjacent_target_no_path",
            TargetPatternParseError::AbsoluteRequired.into()
        )?;
    }

    // We ask for the cell, but if the pattern is relative we might not use it
    let cell = cell_alias_resolver.resolve(cell_alias.unwrap_or_default())?;

    let package_path = pattern.package_path();

    let path = match relative.dir() {
        Some(rel)
            if cell_alias.is_none() && (relative.allow_relative() || package_path.is_empty()) =>
        {
            CellPathCow::Owned(rel.join(package_path))
        }
        _ => CellPathCow::Borrowed(CellPathRef::new(cell, CellRelativePath::new(package_path))),
    };

    match pattern {
        PatternData::Recursive { .. } => Ok(ParsedPattern::Recursive(path.into_owned())),
        PatternData::AllTargetsInPackage { .. } => Ok(ParsedPattern::Package(
            PackageLabel::from_cell_path(path.as_ref()),
        )),
        PatternData::TargetInPackage {
            target_name, extra, ..
        } => Ok(ParsedPattern::Target(
            PackageLabel::from_cell_path(path.as_ref()),
            target_name,
            extra,
        )),
    }
}

#[derive(thiserror::Error, Debug)]
enum ResolveTargetAliasError {
    #[error("Error dereferencing alias `{}` -> `{}`", target, alias)]
    ErrorDereferencing { target: String, alias: String },

    #[error("Invalid alias: `{}`", alias)]
    InvalidAlias { alias: String },

    #[error("Alias for `{}` is not a target: `{}`", target, alias)]
    AliasIsNotATarget { target: String, alias: String },
}

fn resolve_target_alias<T>(
    cell_name: CellName,
    cell_resolver: &CellResolver,
    target_alias_resolver: &dyn TargetAliasResolver,
    lex: &PatternParts<T>,
) -> anyhow::Result<Option<ParsedPattern<T>>>
where
    T: PatternType,
{
    // Imported from Buck1
    static ALIAS_REGEX: Lazy<Regex> =
        Lazy::new(|| Regex::new("^[a-zA-Z_-][a-zA-Z0-9_-]*$").unwrap());

    // If the input starts with a cell path, it can't be an alias.
    if lex.cell_alias.is_some() {
        return Ok(None);
    }

    // Unless the input is a standalone bit of ambiguous text then it cannot be an alias.
    let (target, extra) = match &lex.pattern {
        PatternDataOrAmbiguous::Ambiguous { pattern, extra, .. } => (*pattern, extra),
        _ => return Ok(None),
    };

    // Check if this is an alias after all.
    let alias = match target_alias_resolver.get(target)? {
        Some(alias) => alias,
        None => return Ok(None),
    };

    // Now that we know it's an alias, check it matches the regex. We only do this once we know the
    // alias is valid so that we avoid throwing "alias is invalid" if the user didn't mean to use
    // an alias.
    if !ALIAS_REGEX.is_match(target) {
        return Err(ResolveTargetAliasError::InvalidAlias {
            alias: alias.to_owned(),
        }
        .into());
    }

    // We found a matching alias. Parse the alias as a target.
    let res = parse_target_pattern::<TargetPatternExtra>(
        cell_name,
        cell_resolver,
        None,
        TargetParsingOptions::precise(),
        alias,
    )
    .with_context(|| ResolveTargetAliasError::ErrorDereferencing {
        target: target.to_owned(),
        alias: alias.to_owned(),
    })?;

    // And finally, put the `T` we were looking for back together.
    let res = match res {
        ParsedPattern::Target(package, target_name, TargetPatternExtra) => {
            ParsedPattern::Target(package, target_name, extra.clone())
        }
        _ => {
            return Err(ResolveTargetAliasError::AliasIsNotATarget {
                target: target.to_owned(),
                alias: alias.to_owned(),
            }
            .into());
        }
    };

    Ok(Some(res))
}

#[derive(Debug, Eq, PartialEq)]
pub enum PackageSpec<T: PatternType> {
    /// Given targets in a package.
    Targets(Vec<(TargetName, T)>),
    /// All targets in a package, without subpackages.
    /// Syntax for this variant is `foo:`.
    All,
}

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

    use assert_matches::assert_matches;
    use gazebo::prelude::*;
    use pattern_type::PatternType;
    use test_case::test_case;

    use super::*;
    use crate::cells::alias::NonEmptyCellAlias;
    use crate::cells::cell_root_path::CellRootPathBuf;
    use crate::cells::name::CellName;
    use crate::cells::paths::CellRelativePathBuf;
    use crate::pattern::pattern_type::ConfiguredTargetPatternExtra;
    use crate::target::label::TargetLabel;
    use crate::target::name::TargetNameRef;

    fn mk_package<P: PatternType>(cell: &str, path: &str) -> ParsedPattern<P> {
        ParsedPattern::Package(PackageLabel::testing_new(cell, path))
    }

    fn mk_recursive<P: PatternType>(cell: &str, path: &str) -> ParsedPattern<P> {
        ParsedPattern::Recursive(CellPath::new(
            CellName::testing_new(cell),
            CellRelativePathBuf::unchecked_new(path.to_owned()),
        ))
    }

    fn mk_target(cell: &str, path: &str, target: &str) -> ParsedPattern<TargetPatternExtra> {
        ParsedPattern::Target(
            PackageLabel::testing_new(cell, path),
            TargetName::unchecked_new(target),
            TargetPatternExtra,
        )
    }

    fn mk_providers(
        cell: &str,
        path: &str,
        target: &str,
        providers: Option<&[&str]>,
    ) -> ParsedPattern<ProvidersPatternExtra> {
        ParsedPattern::Target(
            PackageLabel::testing_new(cell, path),
            TargetName::unchecked_new(target),
            ProvidersPatternExtra {
                providers: providers.map_or(ProvidersName::Default, |n| {
                    ProvidersName::NonDefault(Box::new(NonDefaultProvidersName::Named(
                        n.map(|s| ProviderName::new((*s).to_owned()).unwrap())
                            .into_boxed_slice(),
                    )))
                }),
            },
        )
    }

    fn mk_configured_providers(
        cell: &str,
        path: &str,
        target: &str,
        providers: Option<&[&str]>,
        cfg: ConfigurationPredicate,
    ) -> ParsedPattern<ConfiguredProvidersPatternExtra> {
        mk_providers(cell, path, target, providers)
            .try_map(|ProvidersPatternExtra { providers }| {
                Ok(ConfiguredProvidersPatternExtra { providers, cfg })
            })
            .unwrap()
    }

    fn fails<R>(x: anyhow::Result<R>, msgs: &[&str]) {
        match x {
            Err(e) => {
                let s = format!("{:#}", e);
                for msg in msgs {
                    if !s.contains(msg) {
                        panic!("Expected `{}` but missing from error `{:#}`", msg, e)
                    }
                }
            }
            Ok(_) => panic!("Expected failure but succeeded"),
        }
    }

    struct NoAliases;

    impl TargetAliasResolver for NoAliases {
        fn get<'a>(&'a self, _name: &str) -> anyhow::Result<Option<&'a str>> {
            Ok(None)
        }
    }

    fn aliases(aliases: &[(&str, &str)]) -> impl TargetAliasResolver {
        struct Aliases(Vec<(String, String)>);

        impl TargetAliasResolver for Aliases {
            fn get<'a>(&'a self, name: &str) -> anyhow::Result<Option<&'a str>> {
                Ok(self
                    .0
                    .iter()
                    .find(|(a, _)| *a == name)
                    .map(|(_, b)| b.as_str()))
            }
        }

        Aliases(
            aliases
                .iter()
                .map(|(a, b)| ((*a).to_owned(), (*b).to_owned()))
                .collect(),
        )
    }

    fn resolver() -> CellResolver {
        CellResolver::testing_with_names_and_paths_with_alias(&[
            (
                CellName::testing_new("root"),
                CellRootPathBuf::testing_new(""),
                HashMap::from_iter([
                    (
                        NonEmptyCellAlias::testing_new("cell1"),
                        CellName::testing_new("cell1"),
                    ),
                    (
                        NonEmptyCellAlias::testing_new("alias2"),
                        CellName::testing_new("cell2"),
                    ),
                ]),
            ),
            (
                CellName::testing_new("cell1"),
                CellRootPathBuf::testing_new("cell1"),
                HashMap::new(),
            ),
            (
                CellName::testing_new("cell2"),
                CellRootPathBuf::testing_new("cell2"),
                HashMap::new(),
            ),
        ])
    }

    #[test_case(PhantomData::< TargetPatternExtra >; "parsing TargetPattern")]
    #[test_case(PhantomData::< ProvidersPatternExtra >; "parsing ProvidersPattern")]
    #[test_case(PhantomData::< ConfiguredTargetPatternExtra >; "parsing ConfiguredTargetPatternExtra")]
    #[test_case(PhantomData::< ConfiguredProvidersPatternExtra >; "parsing ConfiguredProvidersPatternExtra")]
    fn parse_absolute_pattern<T: PatternType>(_: PhantomData<T>) {
        let package = CellPath::new(
            CellName::testing_new("root"),
            CellRelativePath::unchecked_new("package/path").to_owned(),
        );

        assert_eq!(
            mk_package::<T>("root", "package/path"),
            ParsedPattern::<T>::parse_precise(
                "//package/path:",
                CellName::testing_new("root"),
                &resolver()
            )
            .unwrap()
        );
        assert_eq!(
            mk_package::<T>("root", ""),
            ParsedPattern::<T>::parse_precise("//:", CellName::testing_new("root"), &resolver())
                .unwrap()
        );
        assert_eq!(
            mk_package::<T>("cell1", "package/path"),
            ParsedPattern::<T>::parse_precise(
                "cell1//package/path:",
                CellName::testing_new("root"),
                &resolver()
            )
            .unwrap()
        );
        assert_matches!(
            ParsedPattern::<T>::parse_precise("package/path:", CellName::testing_new("root"), &resolver()),
            Err(e) => {
                assert_matches!(
                    e.downcast_ref::<TargetPatternParseError>(),
                    Some(TargetPatternParseError::AbsoluteRequired)
                );
            }
        );
        assert_eq!(
            mk_package::<T>("cell2", "package/path"),
            ParsedPattern::<T>::parse_precise(
                "alias2//package/path:",
                CellName::testing_new("root"),
                &resolver()
            )
            .unwrap()
        );
        assert_eq!(
            mk_package::<T>("cell2", "package/path"),
            ParsedPattern::<T>::parse_precise(
                "@alias2//package/path:",
                CellName::testing_new("root"),
                &resolver()
            )
            .unwrap()
        );
        assert_eq!(
            mk_recursive::<T>("root", "package/path"),
            ParsedPattern::<T>::parse_precise(
                "//package/path/...",
                CellName::testing_new("root"),
                &resolver()
            )
            .unwrap()
        );
        assert_eq!(
            mk_recursive::<T>("root", "package/path"),
            ParsedPattern::<T>::parse_relative(&NoAliases, package.as_ref(), "...", &resolver(),)
                .unwrap()
        );
        assert_eq!(
            mk_recursive::<T>("root", "package/path/foo"),
            ParsedPattern::<T>::parse_relative(
                &NoAliases,
                package.as_ref(),
                "foo/...",
                &resolver(),
            )
            .unwrap()
        );
    }

    #[test]
    fn parse_relative_pattern() -> anyhow::Result<()> {
        let package = CellPath::new(
            CellName::testing_new("root"),
            CellRelativePath::unchecked_new("package/path").to_owned(),
        );

        assert_eq!(
            mk_target("root", "package/path", "target"),
            ParsedPattern::parse_precise(
                "//package/path:target",
                CellName::testing_new("root"),
                &resolver()
            )?
        );
        assert_eq!(
            mk_target("root", "package/path/foo", "target"),
            ParsedPattern::parse_relative(&NoAliases, package.as_ref(), "foo:target", &resolver(),)?
        );
        Ok(())
    }

    #[test]
    fn test_relaxed() -> anyhow::Result<()> {
        let package = CellPath::new(
            CellName::testing_new("root"),
            CellRelativePath::unchecked_new("package").to_owned(),
        );

        assert_matches!(
            ParsedPattern::<TargetPatternExtra>::parse_relative(
                &NoAliases,
                package.as_ref(),
                "path",
                &resolver(),
            ),
            Err(e) => {
                assert_matches!(
                    e.downcast_ref::<TargetPatternParseError>(),
                    Some(TargetPatternParseError::UnexpectedFormat)
                );
            }
        );

        assert_eq!(
            mk_target("root", "package/path", "path"),
            ParsedPattern::parse_relaxed(
                &NoAliases,
                package.as_ref(),
                "//package/path",
                &resolver(),
            )?
        );
        assert_eq!(
            mk_target("root", "package/path", "path"),
            ParsedPattern::parse_relaxed(&NoAliases, package.as_ref(), "path", &resolver(),)?
        );
        assert_eq!(
            mk_providers("root", "package/path", "path", Some(&["provider"])),
            ParsedPattern::parse_relaxed(
                &NoAliases,
                package.as_ref(),
                "path[provider]",
                &resolver(),
            )?
        );
        assert_eq!(
            mk_providers(
                "root",
                "package/path/subpath",
                "subpath",
                Some(&["provider"])
            ),
            ParsedPattern::parse_relaxed(
                &NoAliases,
                package.as_ref(),
                "path/subpath[provider]",
                &resolver(),
            )?
        );
        assert_eq!(
            mk_target("root", "package/path/subpath", "subpath"),
            ParsedPattern::parse_relaxed(
                &NoAliases,
                package.as_ref(),
                "path/subpath",
                &resolver(),
            )?
        );
        assert_eq!(
            mk_target("root", "package/path", "path"),
            ParsedPattern::parse_relaxed(
                &NoAliases,
                package.as_ref(),
                "//package/path/",
                &resolver(),
            )?
        );
        assert_eq!(
            mk_target("root", "package/path", "target"),
            ParsedPattern::parse_relaxed(
                &NoAliases,
                package.as_ref(),
                "//package/path/:target",
                &resolver(),
            )?
        );

        // Awkward but technically valid?
        assert_eq!(
            mk_target("root", "package", "foo"),
            ParsedPattern::parse_relaxed(&NoAliases, package.as_ref(), "/:foo", &resolver(),)?
        );

        // There's no target here so this is invalid.
        assert_matches!(
            ParsedPattern::<TargetPatternExtra>::parse_relaxed(
                &NoAliases,
                package.as_ref(),
                "/",
                &resolver(),
            ),
            Err(e) => {
                assert_matches!(
                    e.downcast_ref::<TargetPatternParseError>(),
                    Some(TargetPatternParseError::PackageIsEmpty)
                );
            }
        );

        Ok(())
    }

    #[test]
    fn test_parsed_opt_absolute() -> anyhow::Result<()> {
        let package = CellPath::new(
            CellName::testing_new("root"),
            CellRelativePath::unchecked_new("package/path").to_owned(),
        );

        assert_eq!(
            mk_target("root", "other", "target"),
            ParsedPattern::parsed_opt_absolute(
                "//other:target",
                Some(package.as_ref()),
                CellName::testing_new("root"),
                &resolver(),
            )?
        );
        assert_eq!(
            mk_target("root", "package/path", "target"),
            ParsedPattern::parsed_opt_absolute(
                ":target",
                Some(package.as_ref()),
                CellName::testing_new("root"),
                &resolver(),
            )?
        );
        let err = ParsedPattern::<TargetPatternExtra>::parsed_opt_absolute(
            ":target",
            None,
            CellName::testing_new("root"),
            &resolver(),
        )
        .unwrap_err();
        assert!(
            err.to_string()
                .contains("Invalid absolute target pattern `:target` is not allowed"),
            "{}",
            err
        );
        // But this should be fine.
        assert_eq!(
            mk_target("cell1", "", "target"),
            ParsedPattern::parsed_opt_absolute(
                "cell1//:target",
                None,
                CellName::testing_new("root"),
                &resolver(),
            )?
        );

        assert_matches!(
            ParsedPattern::<TargetPatternExtra>::parsed_opt_absolute(
                "foo/bar",
                Some(package.as_ref()),
                CellName::testing_new("root"),
                &resolver(),
            ),
            Err(e) => {
                assert_matches!(
                    e.downcast_ref::<TargetPatternParseError>(),
                    Some(TargetPatternParseError::UnexpectedFormat)
                );
            }
        );

        assert_matches!(
            ParsedPattern::<TargetPatternExtra>::parsed_opt_absolute(
                "foo/bar:bar",
                Some(package.as_ref()),
                CellName::testing_new("root"),
                &resolver(),
            ),
            Err(e) => {
                assert_matches!(
                    e.downcast_ref::<TargetPatternParseError>(),
                    Some(TargetPatternParseError::AbsoluteRequired)
                );
            }
        );
        assert_matches!(
            ParsedPattern::<TargetPatternExtra>::parsed_opt_absolute(
                "foo/bar:bar",
                None,
                CellName::testing_new("root"),
                &resolver(),
            ),
            Err(e) => {
                assert_matches!(
                    e.downcast_ref::<TargetPatternParseError>(),
                    Some(TargetPatternParseError::AbsoluteRequired)
                );
            }
        );

        Ok(())
    }

    #[test]
    fn test_aliases() -> anyhow::Result<()> {
        let package = CellPath::new(
            CellName::testing_new("root"),
            CellRelativePath::unchecked_new("package").to_owned(),
        );

        let config = aliases(&[
            ("foo", "cell1//foo/bar:target"),
            ("invalid/alias", "cell1//foo/bar:target"),
            ("badalias", "cell1//foo/bar:"),
        ]);

        assert_eq!(
            mk_target("cell1", "foo/bar", "target"),
            ParsedPattern::parse_relaxed(&config, package.as_ref(), "foo", &resolver(),)?
        );

        assert_matches!(
            ParsedPattern::<TargetPatternExtra>::parse_relaxed(
                &config,
                package.as_ref(),
                "invalid/alias",
                &resolver(),
            ),
            Err(e) => {
                assert_matches!(
                    e.downcast_ref::<ResolveTargetAliasError>(),
                    Some(ResolveTargetAliasError::InvalidAlias { .. })
                );
            }
        );

        assert_matches!(
            ParsedPattern::<TargetPatternExtra>::parse_relaxed(
                &config,
                package.as_ref(),
                "badalias",
                &resolver(),
            ),
            Err(e) => {
                assert_matches!(
                    e.downcast_ref::<ResolveTargetAliasError>(),
                    Some(ResolveTargetAliasError::AliasIsNotATarget { .. })
                );
            }
        );

        Ok(())
    }

    #[test]
    fn parse_providers_pattern() -> anyhow::Result<()> {
        assert_eq!(
            mk_providers("root", "package/path", "target", None),
            ParsedPattern::parse_precise(
                "//package/path:target",
                CellName::testing_new("root"),
                &resolver()
            )?
        );
        assert_eq!(
            mk_providers("root", "package/path", "target", Some(&["java-output"])),
            ParsedPattern::parse_precise(
                "//package/path:target[java-output]",
                CellName::testing_new("root"),
                &resolver()
            )?
        );
        assert_eq!(
            mk_providers(
                "root",
                "package/path",
                "target",
                Some(&["FDSIcon+FDSInternal.h"]),
            ),
            ParsedPattern::parse_precise(
                "//package/path:target[FDSIcon+FDSInternal.h]",
                CellName::testing_new("root"),
                &resolver()
            )?
        );

        let (package, target_name, providers) = ParsedPattern::parse_precise(
            "//package/path:target#flavor",
            CellName::testing_new("root"),
            &resolver(),
        )?
        .as_literal("")?;
        assert_eq!(
            "root//package/path:target#flavor",
            ProvidersPatternExtra::into_providers_label(providers, package, target_name.as_ref())
                .to_string(),
        );
        Ok(())
    }

    #[test]
    fn parse_providers_pattern_with_alias() -> anyhow::Result<()> {
        let package = CellPath::new(
            CellName::testing_new("root"),
            CellRelativePath::unchecked_new("package").to_owned(),
        );

        let config = aliases(&[("foo", "cell1//foo/bar:target")]);

        assert_eq!(
            mk_providers("cell1", "foo/bar", "target", Some(&["qux"])),
            ParsedPattern::parse_relaxed(&config, package.as_ref(), "foo[qux]", &resolver(),)?
        );

        Ok(())
    }

    #[test]
    fn test_parse_configured_providers_pattern() -> anyhow::Result<()> {
        assert_eq!(
            mk_configured_providers(
                "root",
                "package/path",
                "target",
                None,
                ConfigurationPredicate::Any
            ),
            ParsedPattern::parse_precise(
                "//package/path:target",
                CellName::testing_new("root"),
                &resolver()
            )?
        );
        assert_eq!(
            mk_configured_providers(
                "root",
                "package/path",
                "target",
                None,
                ConfigurationPredicate::Builtin(BuiltinPlatform::Unspecified)
            ),
            ParsedPattern::parse_precise(
                "//package/path:target (<unspecified>)",
                CellName::testing_new("root"),
                &resolver()
            )?
        );
        assert_eq!(
            mk_configured_providers(
                "root",
                "package/path",
                "target",
                Some(&["P"]),
                ConfigurationPredicate::Bound(
                    BoundConfigurationLabel::new("<foo>".to_owned()).unwrap(),
                    None
                ),
            ),
            ParsedPattern::parse_precise(
                "//package/path:target[P] (<foo>)",
                CellName::testing_new("root"),
                &resolver()
            )?
        );
        assert_eq!(
            mk_configured_providers(
                "root",
                "package/path",
                "target",
                Some(&["P"]),
                ConfigurationPredicate::Bound(
                    BoundConfigurationLabel::new("<foo>".to_owned()).unwrap(),
                    Some(ConfigurationHash::from_str("0123456789abcdef").unwrap()),
                ),
            ),
            ParsedPattern::parse_precise(
                "//package/path:target[P] (<foo>#0123456789abcdef)",
                CellName::testing_new("root"),
                &resolver(),
            )?
        );
        Ok(())
    }

    #[test_case(PhantomData::< TargetPatternExtra >; "parsing TargetPattern")]
    #[test_case(PhantomData::< ProvidersPatternExtra >; "parsing ProvidersPattern")]
    #[test_case(PhantomData::< ConfiguredTargetPatternExtra >; "parsing ConfiguredTargetPatternExtra")]
    #[test_case(PhantomData::< ConfiguredProvidersPatternExtra >; "parsing ConfiguredProvidersPatternExtra")]
    fn parse_pattern_failure<T: PatternType>(_: PhantomData<T>) {
        fails(
            ParsedPattern::<T>::parse_precise("", CellName::testing_new("root"), &resolver()),
            &[],
        );
        fails(
            ParsedPattern::<T>::parse_precise(
                "//package/path",
                CellName::testing_new("root"),
                &resolver(),
            ),
            &[],
        );
        fails(
            ParsedPattern::<T>::parse_precise(
                "//package...",
                CellName::testing_new("root"),
                &resolver(),
            ),
            &[],
        );
        fails(
            ParsedPattern::<T>::parse_precise(
                "package",
                CellName::testing_new("root"),
                &resolver(),
            ),
            &[],
        );
        fails(
            ParsedPattern::<T>::parse_precise(
                "bad_alias//package/path:",
                CellName::testing_new("root"),
                &resolver(),
            ),
            &[
                "bad_alias//package/path:",
                "unknown cell alias: `bad_alias`.",
            ],
        );
        fails(
            ParsedPattern::<T>::parse_precise(
                "//package/path/:target",
                CellName::testing_new("root"),
                &resolver(),
            ),
            &[],
        );
        fails(
            ParsedPattern::<T>::parse_precise(
                "//package/path/",
                CellName::testing_new("root"),
                &resolver(),
            ),
            &[],
        );
        fails(
            ParsedPattern::<T>::parse_precise(
                "$(exe my macro)",
                CellName::testing_new("root"),
                &resolver(),
            ),
            &[
                "$(exe my macro)",
                "You may be trying to use a macro instead of a target pattern. Macro usage is invalid here",
            ],
        );
    }

    #[test]
    fn bad_providers_label() {
        fails(
            ParsedPattern::<ProvidersPatternExtra>::parse_precise(
                "//package/path:target[unclosed",
                CellName::testing_new("root"),
                &resolver(),
            ),
            &[
                "//package/path:target[unclosed",
                "target pattern with `[` must end with `]` to mark end of providers set label",
            ],
        );
        fails(
            ParsedPattern::<ProvidersPatternExtra>::parse_precise(
                "//package/path:target[out]wrong",
                CellName::testing_new("root"),
                &resolver(),
            ),
            &[
                "//package/path:target[out]wrong",
                "target pattern with `[` must end with `]` to mark end of providers set label",
            ],
        );
        fails(
            ParsedPattern::<ProvidersPatternExtra>::parse_precise(
                "$(exe my macro)",
                CellName::testing_new("root"),
                &resolver(),
            ),
            &[
                "$(exe my macro)",
                "You may be trying to use a macro instead of a target pattern. Macro usage is invalid here",
            ],
        );
    }

    #[test]
    fn parsed_pattern_contains() -> anyhow::Result<()> {
        let pkg1 = PackageLabel::new(
            CellName::testing_new("root"),
            CellRelativePath::unchecked_new("package/path"),
        );
        let pkg2 = PackageLabel::new(
            CellName::testing_new("root"),
            CellRelativePath::unchecked_new("package"),
        );
        let pkg3 = PackageLabel::new(
            CellName::testing_new("root"),
            CellRelativePath::unchecked_new("package2"),
        );
        let pkg_in_different_cell = PackageLabel::new(
            CellName::testing_new("cell1"),
            CellRelativePath::unchecked_new("package/path"),
        );

        let target_in_pkg1 = TargetLabel::new(pkg1.dupe(), TargetNameRef::new("target")?);
        let another_target_in_pkg1 = TargetLabel::new(pkg1, TargetNameRef::new("target2")?);
        let target_in_pkg2 = TargetLabel::new(pkg2, TargetNameRef::new("target")?);
        let target_in_pkg3 = TargetLabel::new(pkg3, TargetNameRef::new("target")?);
        let target_in_different_cell =
            TargetLabel::new(pkg_in_different_cell, TargetNameRef::new("target")?);

        // Testing ParsedPattern::Target

        let pattern = ParsedPattern::parse_precise(
            "//package/path:target",
            CellName::testing_new("root"),
            &resolver(),
        )?;
        assert!(pattern.matches(&target_in_pkg1));
        assert!(!pattern.matches(&another_target_in_pkg1));
        assert!(!pattern.matches(&target_in_pkg2));
        assert!(!pattern.matches(&target_in_pkg3));
        assert!(!pattern.matches(&target_in_different_cell));

        // Testing ParsedPattern::Package

        let pattern = ParsedPattern::parse_precise(
            "//package/path:",
            CellName::testing_new("root"),
            &resolver(),
        )?;
        assert!(pattern.matches(&target_in_pkg1));
        assert!(pattern.matches(&another_target_in_pkg1));
        assert!(!pattern.matches(&target_in_pkg2));
        assert!(!pattern.matches(&target_in_pkg3));
        assert!(!pattern.matches(&target_in_different_cell));

        let pattern =
            ParsedPattern::parse_precise("//package:", CellName::testing_new("root"), &resolver())?;
        assert!(!pattern.matches(&target_in_pkg1));
        assert!(!pattern.matches(&another_target_in_pkg1));
        assert!(pattern.matches(&target_in_pkg2));
        assert!(!pattern.matches(&target_in_pkg3));
        assert!(!pattern.matches(&target_in_different_cell));

        // Testing ParsedPattern::Recursive

        let pattern = ParsedPattern::parse_precise(
            "//package/path/...",
            CellName::testing_new("root"),
            &resolver(),
        )?;
        assert!(pattern.matches(&target_in_pkg1));
        assert!(pattern.matches(&another_target_in_pkg1));
        assert!(!pattern.matches(&target_in_pkg2));
        assert!(!pattern.matches(&target_in_pkg3));
        assert!(!pattern.matches(&target_in_different_cell));

        let pattern = ParsedPattern::parse_precise(
            "//package/...",
            CellName::testing_new("root"),
            &resolver(),
        )?;
        assert!(pattern.matches(&target_in_pkg1));
        assert!(pattern.matches(&another_target_in_pkg1));
        assert!(pattern.matches(&target_in_pkg2));
        assert!(!pattern.matches(&target_in_pkg3));
        assert!(!pattern.matches(&target_in_different_cell));

        let pattern =
            ParsedPattern::parse_precise("//...", CellName::testing_new("root"), &resolver())?;
        assert!(pattern.matches(&target_in_pkg1));
        assert!(pattern.matches(&another_target_in_pkg1));
        assert!(pattern.matches(&target_in_pkg2));
        assert!(pattern.matches(&target_in_pkg3));
        assert!(!pattern.matches(&target_in_different_cell));

        let pattern =
            ParsedPattern::parse_precise("cell1//...", CellName::testing_new("root"), &resolver())?;
        assert!(!pattern.matches(&target_in_pkg1));
        assert!(!pattern.matches(&another_target_in_pkg1));
        assert!(!pattern.matches(&target_in_pkg2));
        assert!(!pattern.matches(&target_in_pkg3));
        assert!(pattern.matches(&target_in_different_cell));

        Ok(())
    }

    #[test]
    fn test_parsed_pattern_display() {
        assert_eq!(
            "foo//bar:baz",
            ParsedPattern::<TargetPatternExtra>::testing_parse("foo//bar:baz").to_string()
        );
        assert_eq!(
            "foo//bar:",
            ParsedPattern::<TargetPatternExtra>::testing_parse("foo//bar:").to_string()
        );
        assert_eq!(
            "foo//bar/...",
            ParsedPattern::<TargetPatternExtra>::testing_parse("foo//bar/...").to_string()
        );
        assert_eq!(
            "foo//:",
            ParsedPattern::<TargetPatternExtra>::testing_parse("foo//:").to_string()
        );
        assert_eq!(
            "foo//...",
            ParsedPattern::<TargetPatternExtra>::testing_parse("foo//...").to_string()
        );
    }

    #[test]
    fn test_cross_cell_boundary() {
        let cell_resolver = CellResolver::testing_with_names_and_paths(&[
            (
                CellName::testing_new("root"),
                CellRootPathBuf::testing_new(""),
            ),
            (
                CellName::testing_new("cell1"),
                CellRootPathBuf::testing_new("cell1"),
            ),
            (
                CellName::testing_new("cell2"),
                CellRootPathBuf::testing_new("cell1/xx/cell2"),
            ),
        ]);

        let err = ParsedPattern::<TargetPatternExtra>::parse_precise(
            "root//cell1/xx/cell2/yy/...",
            CellName::testing_new("root"),
            &cell_resolver,
        )
        .unwrap_err();
        let err = format!("{:?}", err);
        assert!(
            err.contains("Pattern `root//cell1/xx/cell2/yy/...` is parsed as `root//cell1/xx/cell2/yy/...` which crosses cell boundaries. Try `cell2//yy/...`"),
            "Error 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 crate::cells::paths::CellRelativePath;
use crate::cells::CellAliasResolver;
use crate::fs::paths::forward_rel_path::ForwardRelativePath;
use crate::package::PackageLabel;

#[derive(Debug, thiserror::Error)]
enum ParsePackageError {
    #[error("Package should contain `//`: `{0}`")]
    NoSlashSlash(String),
}

/// Parse package without `@` in cell name.
pub fn parse_package(
    package: &str,
    cell_alias_resolver: &CellAliasResolver,
) -> anyhow::Result<PackageLabel> {
    // There's no ready to use parser for package, so create simple one here.
    let (cell, cell_relative) = package
        .split_once("//")
        .ok_or_else(|| ParsePackageError::NoSlashSlash(package.to_owned()))?;

    let cell = cell_alias_resolver.resolve(cell)?;
    let cell_relative =
        ForwardRelativePath::new(cell_relative).context("Parsing package argument")?;
    let cell_relative = CellRelativePath::new(cell_relative);

    Ok(PackageLabel::new(cell, cell_relative))
}

#[cfg(test)]
mod tests {

    use crate::cells::alias::NonEmptyCellAlias;
    use crate::cells::name::CellName;
    use crate::cells::CellAliasResolver;
    use crate::pattern::parse_package::parse_package;

    #[test]
    fn test_parse_package() {
        let package = parse_package(
            "foo//bar/baz",
            &CellAliasResolver::new(
                CellName::testing_new("oof"),
                [(
                    NonEmptyCellAlias::new("foo".to_owned()).unwrap(),
                    CellName::testing_new("oof"),
                )]
                .into_iter()
                .collect(),
            )
            .unwrap(),
        )
        .unwrap();
        assert_eq!("oof", package.cell_name().as_str());
        assert_eq!("bar/baz", package.cell_relative_path().as_str());
    }
}


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

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

use allocative::Allocative;

use crate::configuration::bound_label::BoundConfigurationLabel;
use crate::configuration::builtin::BuiltinPlatform;
use crate::configuration::data::ConfigurationData;
use crate::configuration::hash::ConfigurationHash;
use crate::package::PackageLabel;
use crate::provider::label::ProvidersLabel;
use crate::provider::label::ProvidersName;
use crate::target::label::TargetLabel;
use crate::target::name::TargetNameRef;

#[derive(Debug, thiserror::Error)]
enum PatternTypeError {
    #[error("Expecting target pattern, without providers")]
    ExpectingTargetNameWithoutProviders,
    #[error("Expecting target pattern, without configuration")]
    ExpectingTargetPatternWithoutConfiguration,
    #[error("Expecting provider pattern, without configuration")]
    ExpectingProviderPatternWithoutConfiguration,
}

/// The pattern type to be parsed from the command line target patterns.
///
/// This is either 'TargetLabel', 'ConfiguredTargetLabel', or
/// 'ConfiguredProvidersLabel'
pub trait PatternType:
    Sized + Clone + Default + Display + Debug + PartialEq + Eq + Ord + Allocative + 'static
{
    const NAME: &'static str;

    /// Construct this from a configured providers pattern.
    /// Return error if configured providers pattern extra contains parts
    /// that are not allowed for this pattern type.
    fn from_configured_providers(
        providers: ConfiguredProvidersPatternExtra,
    ) -> anyhow::Result<Self>;

    /// This pattern matches the configuration.
    ///
    /// Ignore providers.
    fn matches_cfg(&self, cfg: &ConfigurationData) -> bool;

    fn into_providers(self) -> ProvidersName;
}

/// Pattern that matches an explicit target without any inner providers label.
/// This is useful for 'query's where we do not expect any provider specifiers.
///
/// Ex. `//some/package:target`
#[derive(
    derive_more::Display,
    Clone,
    Default,
    Debug,
    Eq,
    PartialEq,
    Hash,
    Ord,
    PartialOrd,
    Allocative
)]
#[display(fmt = "")]
pub struct TargetPatternExtra;

impl PatternType for TargetPatternExtra {
    const NAME: &'static str = "target";

    fn from_configured_providers(
        providers: ConfiguredProvidersPatternExtra,
    ) -> anyhow::Result<Self> {
        let ConfiguredProvidersPatternExtra { providers, cfg } = providers;
        if providers != ProvidersName::Default {
            return Err(PatternTypeError::ExpectingTargetNameWithoutProviders.into());
        }
        if !cfg.is_any() {
            return Err(PatternTypeError::ExpectingTargetPatternWithoutConfiguration.into());
        }
        Ok(TargetPatternExtra)
    }

    fn matches_cfg(&self, _cfg: &ConfigurationData) -> bool {
        true
    }

    fn into_providers(self) -> ProvidersName {
        ProvidersName::Default
    }
}

/// Pattern that matches an inner providers label that refers to a specific
/// set of providers from a rule.
/// This is useful for builds, and provider or action queries where provider
/// specifiers makes sense
///
/// Ex. `//some/package:target[java-group]`
#[derive(
    derive_more::Display,
    Clone,
    Default,
    Debug,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Allocative
)]
pub struct ProvidersPatternExtra {
    pub providers: ProvidersName,
}

impl ProvidersPatternExtra {
    pub fn into_providers_label(
        self,
        package: PackageLabel,
        target_name: &TargetNameRef,
    ) -> ProvidersLabel {
        ProvidersLabel::new(TargetLabel::new(package, target_name), self.providers)
    }
}

impl PatternType for ProvidersPatternExtra {
    const NAME: &'static str = "providers";

    fn from_configured_providers(
        providers: ConfiguredProvidersPatternExtra,
    ) -> anyhow::Result<Self> {
        let ConfiguredProvidersPatternExtra { providers, cfg } = providers;
        if !cfg.is_any() {
            return Err(PatternTypeError::ExpectingProviderPatternWithoutConfiguration.into());
        }
        Ok(ProvidersPatternExtra { providers })
    }

    fn matches_cfg(&self, _cfg: &ConfigurationData) -> bool {
        true
    }

    fn into_providers(self) -> ProvidersName {
        self.providers
    }
}

#[derive(Default, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Allocative)]
pub enum ConfigurationPredicate {
    /// Matches any configuration.
    #[default]
    Any,
    /// Matches builtin platform.
    Builtin(BuiltinPlatform),
    /// Matches user defined configuration.
    Bound(
        BoundConfigurationLabel,
        /// None means match any configuration with given label.
        Option<ConfigurationHash>,
    ),
}

impl ConfigurationPredicate {
    fn is_any(&self) -> bool {
        matches!(self, ConfigurationPredicate::Any)
    }

    fn matches_cfg(&self, cfg: &ConfigurationData) -> bool {
        match self {
            ConfigurationPredicate::Any => true,
            ConfigurationPredicate::Builtin(builtin) => {
                cfg == &ConfigurationData::builtin(*builtin)
            }
            ConfigurationPredicate::Bound(label, hash) => match cfg.bound() {
                None => false,
                Some(cfg_label) => {
                    label == cfg_label
                        && (hash.is_none() || hash.as_ref() == Some(cfg.output_hash()))
                }
            },
        }
    }
}

impl ConfigurationPredicate {
    fn display_suffix(&self) -> impl Display + '_ {
        struct Impl<'a>(&'a ConfigurationPredicate);

        impl Display for Impl<'_> {
            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
                match self.0 {
                    ConfigurationPredicate::Any => Ok(()),
                    ConfigurationPredicate::Builtin(builtin) => {
                        write!(f, " ({})", builtin)
                    }
                    ConfigurationPredicate::Bound(label, hash) => {
                        write!(f, " ({}", label)?;
                        if let Some(hash) = hash {
                            write!(f, "#{}", hash)?;
                        }
                        write!(f, ")")?;
                        Ok(())
                    }
                }
            }
        }

        Impl(self)
    }
}

#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Allocative)]
pub struct ConfiguredTargetPatternExtra {
    /// Configuration part of pattern `foo//bar:baz (cfg#ab01)`.
    pub cfg: ConfigurationPredicate,
}

impl Display for ConfiguredTargetPatternExtra {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.cfg.display_suffix())
    }
}

impl PatternType for ConfiguredTargetPatternExtra {
    const NAME: &'static str = "configured target";

    fn from_configured_providers(
        providers: ConfiguredProvidersPatternExtra,
    ) -> anyhow::Result<Self> {
        let ConfiguredProvidersPatternExtra { providers, cfg } = providers;
        if providers != ProvidersName::Default {
            return Err(PatternTypeError::ExpectingTargetNameWithoutProviders.into());
        }
        Ok(ConfiguredTargetPatternExtra { cfg })
    }

    fn matches_cfg(&self, cfg: &ConfigurationData) -> bool {
        self.cfg.matches_cfg(cfg)
    }

    fn into_providers(self) -> ProvidersName {
        ProvidersName::Default
    }
}

#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Allocative)]
pub struct ConfiguredProvidersPatternExtra {
    pub providers: ProvidersName,
    /// Configuration part of pattern `foo//bar:baz[Provider] (cfg#ab01)`.
    pub cfg: ConfigurationPredicate,
}

impl Display for ConfiguredProvidersPatternExtra {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}{}", self.providers, self.cfg.display_suffix())
    }
}

impl PatternType for ConfiguredProvidersPatternExtra {
    const NAME: &'static str = "configured providers";

    fn from_configured_providers(extra: ConfiguredProvidersPatternExtra) -> anyhow::Result<Self> {
        Ok(extra)
    }

    fn matches_cfg(&self, cfg: &ConfigurationData) -> bool {
        self.cfg.matches_cfg(cfg)
    }

    fn into_providers(self) -> ProvidersName {
        self.providers
    }
}

#[cfg(test)]
mod tests {
    use crate::configuration::bound_label::BoundConfigurationLabel;
    use crate::configuration::builtin::BuiltinPlatform;
    use crate::configuration::data::ConfigurationData;
    use crate::configuration::data::ConfigurationDataData;
    use crate::configuration::hash::ConfigurationHash;
    use crate::pattern::pattern_type::ConfigurationPredicate;
    use crate::pattern::pattern_type::ConfiguredProvidersPatternExtra;
    use crate::pattern::pattern_type::PatternType;
    use crate::provider::label::ProvidersName;

    #[test]
    fn test_configured_providers_matches_cfg() {
        // Possible configurations.
        let unbound = ConfigurationData::unbound();
        let unspecified = ConfigurationData::unspecified();
        let foo =
            ConfigurationData::from_platform("<foo>".to_owned(), ConfigurationDataData::empty())
                .unwrap();
        let bar =
            ConfigurationData::from_platform("<bar>".to_owned(), ConfigurationDataData::empty())
                .unwrap();

        // Possible matchers.
        let catch_all = ConfiguredProvidersPatternExtra {
            providers: ProvidersName::Default,
            cfg: ConfigurationPredicate::Any,
        };
        let catch_unbound = ConfiguredProvidersPatternExtra {
            providers: ProvidersName::Default,
            cfg: ConfigurationPredicate::Builtin(BuiltinPlatform::Unbound),
        };
        let catch_foo_any = ConfiguredProvidersPatternExtra {
            providers: ProvidersName::Default,
            cfg: ConfigurationPredicate::Bound(
                BoundConfigurationLabel::new("<foo>".to_owned()).unwrap(),
                None,
            ),
        };
        let catch_foo_with_hash = ConfiguredProvidersPatternExtra {
            providers: ProvidersName::Default,
            cfg: ConfigurationPredicate::Bound(
                BoundConfigurationLabel::new("<foo>".to_owned()).unwrap(),
                Some(foo.output_hash().clone()),
            ),
        };
        let catch_foo_wrong_hash = ConfiguredProvidersPatternExtra {
            providers: ProvidersName::Default,
            cfg: ConfigurationPredicate::Bound(
                BoundConfigurationLabel::new("<foo>".to_owned()).unwrap(),
                Some(ConfigurationHash::new(17)),
            ),
        };

        // Now the tests.
        assert!(catch_all.matches_cfg(&unbound));
        assert!(catch_all.matches_cfg(&unspecified));
        assert!(catch_all.matches_cfg(&foo));
        assert!(catch_all.matches_cfg(&bar));

        assert!(catch_unbound.matches_cfg(&unbound));
        assert!(!catch_unbound.matches_cfg(&unspecified));
        assert!(!catch_unbound.matches_cfg(&foo));
        assert!(!catch_unbound.matches_cfg(&bar));

        assert!(!catch_foo_any.matches_cfg(&unbound));
        assert!(!catch_foo_any.matches_cfg(&unspecified));
        assert!(catch_foo_any.matches_cfg(&foo));
        assert!(!catch_foo_any.matches_cfg(&bar));

        assert!(!catch_foo_with_hash.matches_cfg(&unbound));
        assert!(!catch_foo_with_hash.matches_cfg(&unspecified));
        assert!(catch_foo_with_hash.matches_cfg(&foo));
        assert!(!catch_foo_with_hash.matches_cfg(&bar));

        assert!(!catch_foo_wrong_hash.matches_cfg(&unbound));
        assert!(!catch_foo_wrong_hash.matches_cfg(&unspecified));
        assert!(!catch_foo_wrong_hash.matches_cfg(&foo));
        assert!(!catch_foo_wrong_hash.matches_cfg(&bar));
    }
}


/*
 * 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::path::Path;

use crate::cells::cell_path::CellPath;
use crate::cells::paths::CellRelativePath;
use crate::cells::CellAliasResolver;
use crate::cells::CellResolver;
use crate::fs::paths::abs_norm_path::AbsNormPath;
use crate::fs::project::ProjectRoot;
use crate::pattern::maybe_split_cell_alias_and_relative_path;

pub fn parse_query_file_literal(
    literal: &str,
    cell_alias_resolver: &CellAliasResolver,
    cell_resolver: &CellResolver,
    working_dir_abs: &AbsNormPath,
    project_root: &ProjectRoot,
) -> anyhow::Result<CellPath> {
    match maybe_split_cell_alias_and_relative_path(literal)? {
        Some((alias, path)) => {
            let cell_name = cell_alias_resolver.resolve(alias.as_str())?;

            let cell_relative_path = CellRelativePath::new(path);

            Ok(CellPath::new(cell_name, cell_relative_path.to_buf()))
        }
        None => {
            let path = Path::new(literal);
            // Note if the path is absolute, this `join` is a no-op.
            let path_abs = working_dir_abs.as_abs_path().join(path);
            let project_path = project_root.relativize_any(path_abs)?;
            cell_resolver.get_cell_path(&project_path)
        }
    }
}


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

use once_cell::sync::OnceCell;
use regex::RegexSet;

use crate::provider::label::NonDefaultProvidersName;
use crate::provider::label::ProviderName;
use crate::provider::label::ProvidersName;
use crate::soft_error;

static PLATFORM_REGEX_SET: OnceCell<RegexSet> = OnceCell::new();

fn is_platform_flavor(flavor: &str) -> bool {
    let regex_set = PLATFORM_REGEX_SET.get_or_init(|| {
        // copied from https://fburl.com/code/sgxwq68n and modified for our needs
        RegexSet::new([
            r"^android-.*$",
            r"^(linux-.*|platform[0-9]{3}-(clang|clang-12|gcc)(-nosan)?(-split-dwarf)?)$",
            r"^macosx-(x86_64|arm64)(_minimal_xcode)?$",
            // Too broad pattern, so ignoring it
            // "^(default|host)$"
            r"^windows-x86_64$",
            //  an original data has the following pattern: FBOBJC_REGEX = "^(macosx|iphone|watch|appletv|osmeta-).*$"
            // we use modified version w/o macosx prefix already checked in the pattern above
            r"^(iphone|watch|appletv|osmeta-).*$",
        ])
        .unwrap()
    });

    regex_set.is_match(flavor)
}

/// Buck1 uses flavors for a couple different purposes. Some of those flavors have ended
/// up being used by users. In v2, the functionality of most user-visible flavors ends up
/// being handled by either configurations or subtargets. One common use of flavors in
/// v1 was to have references to secondary or intermediate outputs, in v2 we can do that
/// via subtargets.
///
/// This mapping is a hardcoded map of flavors that we know can be handled simply as subtargets.
pub fn map_flavors(flavors: &str, full_target: &str) -> anyhow::Result<ProvidersName> {
    let mut flavors_parts: Vec<&str> = flavors.split(',').collect();
    assert!(!flavors_parts.is_empty());

    match flavors_parts.iter().position(|x| is_platform_flavor(x)) {
        Some(index) => {
            // remove platform flavor from the vector of flavors
            // We log the target with the offending flavor here, though in practice we'll have to
            // rely on the wrapping span in order to find
            soft_error!(
                "platform_flavor",
                anyhow::anyhow!("Platform flavor found in target: {}", full_target),
                quiet: true
            )?;
            flavors_parts.remove(index);
        }
        None => {}
    }

    // sort a flavors list to have a deterministic order.
    flavors_parts.sort_unstable();
    Ok(ProvidersName::NonDefault(Box::new(
        NonDefaultProvidersName::Named(Box::new([ProviderName::new_unchecked(
            match flavors_parts.len() {
                // If we only had one flavor that represents some specific platform then return a default provider name.
                0 => {
                    // Some targets specifically ask for a given platform, we just ignore them
                    return Ok(ProvidersName::Default);
                }

                1 => match flavors_parts[0] {
                    // android_binary intermediate/secondary outputs. See https://fburl.com/diffusion/jd3cmnfw
                    "package_string_assets" => "package_string_assets".to_owned(),
                    "aapt2_link" => "aapt2_link".to_owned(),
                    "unstripped_native_libraries" => "unstripped_native_libraries".to_owned(),
                    "proguard_text_output" => "proguard_text_output".to_owned(),
                    "generate_string_resources" => "generate_string_resources".to_owned(),
                    "generate_voltron_string_resources" => {
                        "generate_voltron_string_resources".to_owned()
                    }
                    "exo_symlink_tree" => "exo_symlink_tree".to_owned(),

                    // android_library secondary outputs
                    "dummy_r_dot_java" => "dummy_r_dot_java".to_owned(),

                    // Rules depend on `#headers` flavor of C++ libraries to use a
                    // dep's headers without linking against it.
                    "headers" => "headers".to_owned(),

                    // This is used by Rust quite a bit
                    "check" => "check".to_owned(),

                    // FIXME(ndmitchell): Most users shouldn't be using strip-debug.
                    // We currently can't handle strip-debug, and it's a dependency of Eden,
                    // so just ignore it for now. D27984137 aims to add it back properly.
                    "strip-debug" => return Ok(ProvidersName::Default),

                    // Used in JEX builder script (https://fburl.com/code/2w2gjkey)
                    "shared" => "shared".to_owned(),

                    // Used by Nullsafe for (android|java)_libraries
                    "nullsafex-json" => "nullsafex-json".to_owned(),

                    // This is for js_bundle. We strip it and let the configuration handle it instead.
                    "android" => return Ok(ProvidersName::Default),

                    // Java/Kotlin sub-targets
                    "class-abi" => "class-abi".to_owned(),
                    "source-abi" => "source-abi".to_owned(),
                    "source-only-abi" => "source-only-abi".to_owned(),

                    _ => {
                        return Ok(ProvidersName::NonDefault(Box::new(
                            NonDefaultProvidersName::UnrecognizedFlavor(flavors.into()),
                        )));
                    }
                },

                // For js_bundle rules. The platform and optimization ("release") flavors are stripped
                // and handled by the configuration. The other flavors are mapped to named outputs.
                2 => match (flavors_parts[0], flavors_parts[1]) {
                    ("android", "dependencies") => "dependencies".to_owned(),
                    ("android", "misc") => "misc".to_owned(),
                    ("android", "source_map") => "source_map".to_owned(),
                    ("android", "release") => return Ok(ProvidersName::Default),
                    _ => {
                        return Ok(ProvidersName::NonDefault(Box::new(
                            NonDefaultProvidersName::UnrecognizedFlavor(flavors.into()),
                        )));
                    }
                },

                3 => match (flavors_parts[0], flavors_parts[1], flavors_parts[2]) {
                    ("android", "dependencies", "release") => "dependencies".to_owned(),
                    ("android", "misc", "release") => "misc".to_owned(),
                    ("android", "release", "source_map") => "source_map".to_owned(),
                    _ => {
                        return Ok(ProvidersName::NonDefault(Box::new(
                            NonDefaultProvidersName::UnrecognizedFlavor(flavors.into()),
                        )));
                    }
                },

                4 => match (
                    flavors_parts[0],
                    flavors_parts[1],
                    flavors_parts[2],
                    flavors_parts[3],
                ) {
                    ("android", "misc", "rambundle-indexed", "release") => {
                        "rambundle-indexed-misc".to_owned()
                    }
                    _ => {
                        return Ok(ProvidersName::NonDefault(Box::new(
                            NonDefaultProvidersName::UnrecognizedFlavor(flavors.into()),
                        )));
                    }
                },

                // This allows us to pass parsing for this thing.
                _ => {
                    return Ok(ProvidersName::NonDefault(Box::new(
                        NonDefaultProvidersName::UnrecognizedFlavor(flavors.into()),
                    )));
                }
            },
        )])),
    )))
}


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

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

use allocative::Allocative;

use crate::cells::cell_path::CellPath;

/// A unique identity for a given provider. Allows correlating `ProviderCallable` objects with `UserProvider` objects.
///
/// For example:
/// ```ignore
/// FooInfo = provider(fields=["foo", "bar"])
///
/// def impl(ctx):
///     # We can guarantee when setting up the context that there
///     # is a provider that came from FooInfo
///     ctx.actions.write("out.txt", ctx.attrs.dep[FooInfo].bar)
/// foo_binary = rule(impl=impl, attrs={"dep": attrs.dep(providers=[FooInfo])})
/// ```
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative)]
pub struct ProviderId {
    /// This is present for all user-specified providers. This is only None if it is a
    /// native provider, which has no affiliated .bzl file
    pub path: Option<CellPath>,
    pub name: String,
}

impl Display for ProviderId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.name)
    }
}

pub struct ProviderIdWithType<T> {
    id: Arc<ProviderId>,
    t: PhantomData<T>,
}

impl<T> ProviderIdWithType<T> {
    pub fn id(&self) -> &Arc<ProviderId> {
        &self.id
    }

    pub fn new(path: Option<CellPath>, name: String) -> Self {
        Self {
            id: Arc::new(ProviderId { path, name }),
            t: Default::default(),
        }
    }
}

impl ProviderId {
    pub fn name(&self) -> &str {
        &self.name
    }
}

pub mod testing {
    use crate::cells::cell_path::CellPath;
    use crate::provider::id::ProviderId;

    pub trait ProviderIdExt {
        fn testing_new(path: CellPath, name: &str) -> Self;
    }

    impl ProviderIdExt for ProviderId {
        fn testing_new(path: CellPath, name: &str) -> Self {
            ProviderId {
                path: Some(path),
                name: name.to_owned(),
            }
        }
    }
}