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

//! Paths relative to 'Cell's
//! 'CellRelativePath' and the owned 'CellRelativePathBuf' are used to represent paths that are
//! relative to a cell.
//! There's also a 'CellPath', which is a 'CellName' and 'CellRelativePathBuf' pair that
//! represents a resolvable path, which can be resolved into a 'ProjectRelativePath'.

use std::borrow::Borrow;
use std::ops::Deref;
use std::path::Path;
use std::path::PathBuf;

use allocative::Allocative;
use derivative::Derivative;
use derive_more::Display;
use gazebo::transmute;
use ref_cast::RefCast;
use relative_path::RelativePath;
use serde::Serialize;

use crate::fs::paths::file_name::FileName;
use crate::fs::paths::fmt::quoted_display;
use crate::fs::paths::forward_rel_path::ForwardRelativePath;
use crate::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use crate::fs::paths::forward_rel_path::ForwardRelativePathIter;
use crate::fs::paths::RelativePathBuf;

/// A un-owned forward pointing, fully normalized path that is relative to the cell
#[derive(
    Display, Derivative, Hash, PartialEq, Eq, RefCast, PartialOrd, Ord, Allocative
)]
#[derivative(Debug)]
#[repr(transparent)]
pub struct CellRelativePath(
    #[derivative(Debug(format_with = "quoted_display"))] ForwardRelativePath,
);

/// The owned version of the 'CellRelativePath'
#[derive(Clone, Display, Derivative)]
// split in two lines because formatters disagree
#[derive(Hash, PartialEq, Eq, Ord, PartialOrd, Serialize, Allocative)]
#[derivative(Debug)]
pub struct CellRelativePathBuf(
    #[derivative(Debug(format_with = "quoted_display"))] ForwardRelativePathBuf,
);

impl Clone for Box<CellRelativePath> {
    fn clone(&self) -> Self {
        self.to_box()
    }
}

impl AsRef<ForwardRelativePath> for CellRelativePath {
    fn as_ref(&self) -> &ForwardRelativePath {
        &self.0
    }
}

impl AsRef<RelativePath> for CellRelativePath {
    fn as_ref(&self) -> &RelativePath {
        self.0.as_ref()
    }
}

impl AsRef<ForwardRelativePath> for CellRelativePathBuf {
    fn as_ref(&self) -> &ForwardRelativePath {
        &self.0
    }
}

impl AsRef<RelativePath> for CellRelativePathBuf {
    fn as_ref(&self) -> &RelativePath {
        self.0.as_ref()
    }
}

impl AsRef<ForwardRelativePathBuf> for CellRelativePathBuf {
    fn as_ref(&self) -> &ForwardRelativePathBuf {
        &self.0
    }
}

impl CellRelativePath {
    pub fn unchecked_new<S: ?Sized + AsRef<str>>(s: &S) -> &Self {
        CellRelativePath::ref_cast(ForwardRelativePath::unchecked_new(s))
    }

    pub fn unchecked_new_box(p: Box<ForwardRelativePath>) -> Box<CellRelativePath> {
        unsafe {
            // SAFETY: `CellRelativePath` is a transparent wrapper around `ForwardRelativePath`.
            transmute!(Box<ForwardRelativePath>, Box<CellRelativePath>, p)
        }
    }

    pub fn testing_new(path: &str) -> &CellRelativePath {
        CellRelativePath::new(ForwardRelativePath::new(path).unwrap())
    }

    pub fn empty() -> &'static Self {
        CellRelativePath::unchecked_new("")
    }

    /// Creates a 'CellRelativePath' if the given string represents a
    /// forward, normalized relative path, otherwise error.
    ///
    /// ```
    /// use buck2_core::cells::paths::CellRelativePath;
    /// use std::path::Path;
    ///
    /// assert!(CellRelativePath::from_path("foo/bar").is_ok());
    /// assert!(CellRelativePath::from_path("").is_ok());
    /// assert!(CellRelativePath::from_path("/abs/bar").is_err());
    /// assert!(CellRelativePath::from_path("normalize/./bar").is_err());
    /// assert!(CellRelativePath::from_path("normalize/../bar").is_err());
    ///
    /// assert!(CellRelativePath::from_path(Path::new("foo/bar")).is_ok());
    /// assert!(CellRelativePath::from_path(Path::new("")).is_ok());
    /// assert!(CellRelativePath::from_path(Path::new("/abs/bar")).is_err());
    /// assert!(CellRelativePath::from_path(Path::new("normalize/./bar")).is_err());
    /// assert!(CellRelativePath::from_path(Path::new("normalize/../bar")).is_err());
    /// ```
    pub fn from_path<P: ?Sized + AsRef<Path>>(p: &P) -> anyhow::Result<&CellRelativePath> {
        Ok(CellRelativePath::ref_cast(ForwardRelativePath::new(p)?))
    }

    pub fn new(path: &ForwardRelativePath) -> &CellRelativePath {
        CellRelativePath::ref_cast(path)
    }

    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    pub fn as_forward_relative_path(&self) -> &ForwardRelativePath {
        &self.0
    }

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

    /// Creates an owned 'CellRelativePathBuf' with path adjoined to self.
    ///
    /// ```
    /// use std::path::Path;
    /// use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
    /// use buck2_core::cells::paths::{CellRelativePathBuf, CellRelativePath};
    ///
    /// let path = CellRelativePath::from_path("foo/bar")?;
    /// let other = ForwardRelativePath::new("baz")?;
    /// assert_eq!(CellRelativePathBuf::unchecked_new("foo/bar/baz".to_owned()), path.join(other));
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn join<P: AsRef<ForwardRelativePath>>(&self, path: P) -> CellRelativePathBuf {
        CellRelativePathBuf(self.0.join(path.as_ref()))
    }

    /// Returns a relative path of the parent directory
    ///
    /// ```
    /// use buck2_core::cells::paths::CellRelativePath;
    ///
    /// assert_eq!(
    ///     Some(CellRelativePath::from_path("foo")?),
    ///     CellRelativePath::from_path("foo/bar")?.parent()
    /// );
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn parent(&self) -> Option<&CellRelativePath> {
        self.0.parent().map(CellRelativePath::ref_cast)
    }

    /// Returns the final component of the `CellRelativePath`, if there is
    /// one.
    ///
    /// If the path is a normal file, this is the file name. If it's the path of
    /// a directory, this is the directory name.
    ///
    /// ```
    /// use buck2_core::cells::paths::CellRelativePath;
    /// use buck2_core::fs::paths::file_name::FileName;
    ///
    /// assert_eq!(Some(FileName::unchecked_new("bin")), CellRelativePath::from_path("usr/bin")?.file_name());
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn file_name(&self) -> Option<&FileName> {
        self.0.file_name()
    }

    /// Returns a 'ForwardRelativePath' that, when joined onto `base`, yields
    /// `self`.
    ///
    /// Error if `base` is not a prefix of `self` or the returned
    /// path is not a 'ForwardRelativePath'
    ///
    /// ```
    /// use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
    /// use buck2_core::cells::paths::CellRelativePath;
    ///
    /// let path = CellRelativePath::from_path("test/haha/foo.txt")?;
    ///
    /// assert_eq!(
    ///     path.strip_prefix(CellRelativePath::from_path("test")?)?,
    ///     ForwardRelativePath::new("haha/foo.txt")?
    /// );
    /// assert_eq!(path.strip_prefix(CellRelativePath::from_path("asdf")?).is_err(), true);
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn strip_prefix<P: ?Sized>(&self, base: &P) -> anyhow::Result<&ForwardRelativePath>
    where
        P: AsRef<CellRelativePath>,
    {
        self.0.strip_prefix(&base.as_ref().0)
    }

    /// Determines whether `base` is a prefix of `self`.
    ///
    /// ```
    ///
    /// use buck2_core::cells::paths::CellRelativePath;
    ///
    /// let path = CellRelativePath::from_path("some/foo")?;
    ///
    /// assert!(path.starts_with(CellRelativePath::from_path("some")?));
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn starts_with<P: AsRef<CellRelativePath>>(&self, base: P) -> bool {
        self.0.starts_with(&base.as_ref().0)
    }

    /// Determines whether `child` is a suffix of `self`.
    /// Only considers whole path components to match.
    ///
    /// ```
    /// use std::path::Path;
    /// use buck2_core::cells::paths::CellRelativePath;
    /// use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
    ///
    /// let path = CellRelativePath::from_path("some/foo")?;
    ///
    /// assert!(path.ends_with(ForwardRelativePath::new("foo").unwrap()));
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn ends_with<P: AsRef<ForwardRelativePath>>(&self, child: P) -> bool {
        self.0.ends_with(child.as_ref())
    }

    /// Extracts the stem (non-extension) portion of [`self.file_name`].
    ///
    /// The stem is:
    ///
    /// * [`None`], if there is no file name;
    /// * The entire file name if there is no embedded `.`;
    /// * The entire file name if the file name begins with `.` and has no other
    ///   `.`s within;
    /// * Otherwise, the portion of the file name before the final `.`
    ///
    /// ```
    /// use buck2_core::cells::paths::CellRelativePath;
    ///
    /// let path = CellRelativePath::from_path("foo.rs")?;
    ///
    /// assert_eq!(Some("foo"), path.file_stem());
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn file_stem(&self) -> Option<&str> {
        self.0.file_stem()
    }

    /// Extracts the extension of [`self.file_name`], if possible.
    ///
    /// ```
    ///
    /// use buck2_core::cells::paths::CellRelativePath;
    ///
    /// assert_eq!(Some("rs"), CellRelativePath::from_path("hi/foo.rs")?.extension());
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn extension(&self) -> Option<&str> {
        self.0.extension()
    }

    /// Build an owned `CellRelativePathBuf`, joined with the given path and
    /// normalized.
    ///
    /// ```
    ///
    /// use buck2_core::cells::paths::{CellRelativePath, CellRelativePathBuf};
    /// use std::convert::TryFrom;
    ///
    /// assert_eq!(
    ///     CellRelativePath::from_path("foo/bar")?.join_normalized("../baz.txt")?,
    ///     CellRelativePathBuf::unchecked_new("foo/baz.txt".into()),
    /// );
    ///
    /// assert_eq!(
    ///     CellRelativePath::from_path("foo")?.join_normalized("../../baz.txt").is_err(),
    ///     true
    /// );
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn join_normalized<P: AsRef<RelativePath>>(
        &self,
        path: P,
    ) -> anyhow::Result<CellRelativePathBuf> {
        let inner = self.0.join_normalized(path)?;
        // TODO need verify?
        Ok(CellRelativePathBuf(inner))
    }

    /// Iterator over the components of this path
    ///
    /// ```
    /// use buck2_core::cells::paths::CellRelativePath;
    /// use buck2_core::fs::paths::file_name::FileName;
    ///
    /// let p = CellRelativePath::from_path("foo/bar/baz")?;
    /// let mut it = p.iter();
    ///
    /// assert_eq!(
    ///     it.next(),
    ///     Some(FileName::unchecked_new("foo"))
    /// );
    /// assert_eq!(
    ///     it.next(),
    ///     Some(FileName::unchecked_new("bar"))
    /// );
    /// assert_eq!(
    ///     it.next(),
    ///     Some(FileName::unchecked_new("baz"))
    /// );
    /// assert_eq!(
    ///     it.next(),
    ///     None
    /// );
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn iter(&self) -> ForwardRelativePathIter {
        self.0.iter()
    }

    pub fn to_buf(&self) -> CellRelativePathBuf {
        self.to_owned()
    }

    pub fn to_box(&self) -> Box<CellRelativePath> {
        self.to_buf().into_box()
    }
}

impl<'a> From<&'a ForwardRelativePath> for &'a CellRelativePath {
    ///
    /// ```
    ///
    /// use buck2_core::cells::paths::CellRelativePath;
    /// use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
    /// use std::convert::From;
    ///
    /// let f = ForwardRelativePath::new("foo")?;
    ///
    /// assert_eq!(<&CellRelativePath>::from(f), CellRelativePath::from_path("foo")?);
    ///
    /// # anyhow::Ok(())
    /// ```
    fn from(p: &'a ForwardRelativePath) -> &'a CellRelativePath {
        CellRelativePath::ref_cast(p)
    }
}

impl CellRelativePathBuf {
    pub fn unchecked_new(s: String) -> Self {
        Self(ForwardRelativePathBuf::unchecked_new(s))
    }

    pub fn testing_new(s: &str) -> CellRelativePathBuf {
        CellRelativePathBuf::try_from(s.to_owned()).unwrap()
    }

    /// Creates a new 'CellRelativePathBuf' with a given capacity used to create the internal
    /// 'String'. See 'with_capacity' defined on 'ForwardRelativePathBuf'
    pub fn with_capacity(cap: usize) -> Self {
        Self(ForwardRelativePathBuf::with_capacity(cap))
    }

    /// Returns the capacity of the underlying 'ForwardRelativePathBuf'
    pub fn capacity(&self) -> usize {
        self.0.capacity()
    }

    /// Invokes 'reserve' on the underlying 'ForwardRelativePathBuf'
    pub fn reserve(&mut self, additional: usize) {
        self.0.reserve(additional)
    }

    /// Invokes 'shrink_to_fit' on the underlying 'ForwardRelativePathBuf'
    pub fn shrink_to_fit(&mut self) {
        self.0.shrink_to_fit()
    }

    /// Invokes 'shrink_to' on the underlying 'String'
    pub fn shrink_to(&mut self, min_capacity: usize) {
        self.0.shrink_to(min_capacity)
    }

    /// Pushes a `ForwardRelativePath` to the existing buffer
    pub fn push<P: AsRef<ForwardRelativePath>>(&mut self, path: P) {
        self.0.push(path)
    }

    /// Pushes a `RelativePath` to the existing buffer, normalizing it
    pub fn push_normalized<P: AsRef<RelativePath>>(&mut self, path: P) -> anyhow::Result<()> {
        self.0.push_normalized(path)
    }

    pub fn into_box(self) -> Box<CellRelativePath> {
        CellRelativePath::unchecked_new_box(self.0.into_box())
    }
}

impl From<ForwardRelativePathBuf> for CellRelativePathBuf {
    fn from(p: ForwardRelativePathBuf) -> Self {
        Self(p)
    }
}

impl From<CellRelativePathBuf> for ForwardRelativePathBuf {
    fn from(p: CellRelativePathBuf) -> Self {
        p.0
    }
}

impl<'a> TryFrom<&'a str> for &'a CellRelativePath {
    type Error = anyhow::Error;

    /// no allocation conversion
    ///
    /// ```
    ///
    /// use buck2_core::cells::paths::CellRelativePath;
    /// use std::convert::TryFrom;
    /// use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
    ///
    /// assert!(<&CellRelativePath>::try_from("foo/bar").is_ok());
    /// assert!(<&CellRelativePath>::try_from("").is_ok());
    /// assert!(<&CellRelativePath>::try_from("/abs/bar").is_err());
    /// assert!(<&CellRelativePath>::try_from("normalize/./bar").is_err());
    /// assert!(<&CellRelativePath>::try_from("normalize/../bar").is_err());
    /// ```
    fn try_from(s: &'a str) -> anyhow::Result<&'a CellRelativePath> {
        Ok(CellRelativePath::ref_cast(ForwardRelativePath::new(s)?))
    }
}

impl<'a> TryFrom<&'a RelativePath> for &'a CellRelativePath {
    type Error = anyhow::Error;

    /// no allocation conversion
    ///
    /// ```
    ///
    /// use buck2_core::cells::paths::CellRelativePath;
    /// use std::convert::TryFrom;
    /// use buck2_core::fs::paths::RelativePath;
    ///
    /// assert!(<&CellRelativePath>::try_from(RelativePath::new("foo/bar")).is_ok());
    /// assert!(<&CellRelativePath>::try_from(RelativePath::new("")).is_ok());
    /// assert!(<&CellRelativePath>::try_from(RelativePath::new("normalize/./bar")).is_err());
    /// assert!(<&CellRelativePath>::try_from(RelativePath::new("normalize/../bar")).is_err());
    /// ```
    fn try_from(s: &'a RelativePath) -> anyhow::Result<&'a CellRelativePath> {
        Ok(CellRelativePath::ref_cast(ForwardRelativePath::new(
            s.as_str(),
        )?))
    }
}

impl TryFrom<String> for CellRelativePathBuf {
    type Error = anyhow::Error;

    /// no allocation conversion
    ///
    /// ```
    ///
    /// use buck2_core::cells::paths::CellRelativePathBuf;
    /// use std::convert::TryFrom;
    ///
    /// assert!(CellRelativePathBuf::try_from("foo/bar".to_owned()).is_ok());
    /// assert!(CellRelativePathBuf::try_from("".to_owned()).is_ok());
    /// assert!(CellRelativePathBuf::try_from("/abs/bar".to_owned()).is_err());
    /// assert!(CellRelativePathBuf::try_from("normalize/./bar".to_owned()).is_err());
    /// assert!(CellRelativePathBuf::try_from("normalize/../bar".to_owned()).is_err());
    /// ```
    fn try_from(s: String) -> anyhow::Result<CellRelativePathBuf> {
        Ok(CellRelativePathBuf::from(ForwardRelativePathBuf::try_from(
            s,
        )?))
    }
}

impl TryFrom<RelativePathBuf> for CellRelativePathBuf {
    type Error = anyhow::Error;

    /// no allocation conversion (TODO make ForwardRelativePath a no allocation
    /// conversion)
    ///
    /// ```
    /// use buck2_core::cells::paths::CellRelativePathBuf;
    /// use buck2_core::fs::paths::RelativePathBuf;
    /// use std::convert::TryFrom;
    ///
    /// assert!(CellRelativePathBuf::try_from(RelativePathBuf::from("foo/bar")).is_ok());
    /// assert!(CellRelativePathBuf::try_from(RelativePathBuf::from("")).is_ok());
    /// assert!(CellRelativePathBuf::try_from(RelativePathBuf::from("normalize/./bar")).is_err());
    /// assert!(CellRelativePathBuf::try_from(RelativePathBuf::from("normalize/../bar")).is_err());
    /// ```
    fn try_from(p: RelativePathBuf) -> anyhow::Result<CellRelativePathBuf> {
        Ok(CellRelativePathBuf::from(ForwardRelativePathBuf::try_from(
            p,
        )?))
    }
}

impl TryFrom<PathBuf> for CellRelativePathBuf {
    type Error = anyhow::Error;

    /// no allocation conversion
    ///
    /// ```
    ///
    /// use buck2_core::cells::paths::CellRelativePathBuf;
    /// use std::convert::TryFrom;
    /// use std::path::PathBuf;
    ///
    /// assert!(CellRelativePathBuf::try_from(PathBuf::from("foo/bar")).is_ok());
    /// assert!(CellRelativePathBuf::try_from(PathBuf::from("")).is_ok());
    /// assert!(CellRelativePathBuf::try_from(PathBuf::from("/abs/bar")).is_err());
    /// assert!(CellRelativePathBuf::try_from(PathBuf::from("normalize/./bar")).is_err());
    /// assert!(CellRelativePathBuf::try_from(PathBuf::from("normalize/../bar")).is_err());
    /// ```
    fn try_from(p: PathBuf) -> anyhow::Result<CellRelativePathBuf> {
        Ok(CellRelativePathBuf(ForwardRelativePathBuf::try_from(p)?))
    }
}

impl ToOwned for CellRelativePath {
    type Owned = CellRelativePathBuf;

    fn to_owned(&self) -> CellRelativePathBuf {
        CellRelativePathBuf(self.0.to_owned())
    }
}

impl AsRef<CellRelativePath> for CellRelativePath {
    fn as_ref(&self) -> &CellRelativePath {
        self
    }
}

impl AsRef<CellRelativePath> for CellRelativePathBuf {
    fn as_ref(&self) -> &CellRelativePath {
        CellRelativePath::ref_cast(&self.0)
    }
}

impl Borrow<CellRelativePath> for CellRelativePathBuf {
    fn borrow(&self) -> &CellRelativePath {
        self.as_ref()
    }
}

impl Deref for CellRelativePathBuf {
    type Target = CellRelativePath;

    fn deref(&self) -> &CellRelativePath {
        CellRelativePath::ref_cast(&self.0)
    }
}


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

use std::hash::Hash;

use allocative::Allocative;
use allocative::Key;
use allocative::Visitor;
use sequence_trie::SequenceTrie;

pub(crate) fn visit_sequence_trie<K: Allocative + Eq + Hash, V: Allocative>(
    t: &SequenceTrie<K, V>,
    visitor: &mut Visitor<'_>,
) {
    let mut visitor = visitor.enter_self_sized::<SequenceTrie<K, V>>();
    if let Some(root) = t.get([]) {
        visitor.visit_field(Key::new("root"), root);
    }
    visitor.visit_generic_map_fields(t.iter().filter_map(|(mut k, v)| Some((k.pop()?, v))));
    visitor.exit();
}


/*
 * 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::cells::paths::CellRelativePath;
use crate::fs::paths::path_util::path_remove_prefix;

/// This is `CellRelativePath` which may contain incorrect path elements.
#[repr(transparent)]
pub struct UncheckedCellRelativePath(str);

impl UncheckedCellRelativePath {
    #[inline]
    pub fn new(path: &CellRelativePath) -> &UncheckedCellRelativePath {
        Self::unchecked_new(path.as_str())
    }

    #[inline]
    pub fn unchecked_new(path: &str) -> &UncheckedCellRelativePath {
        unsafe {
            // SAFETY: `repr(transparent)`.
            &*(path as *const str as *const UncheckedCellRelativePath)
        }
    }

    #[inline]
    pub fn as_str(&self) -> &str {
        &self.0
    }

    #[inline]
    pub fn remove_prefix<'a>(
        &'a self,
        prefix: &CellRelativePath,
    ) -> Option<&'a UncheckedCellRelativePath> {
        path_remove_prefix(self.as_str(), prefix.as_str())
            .map(UncheckedCellRelativePath::unchecked_new)
    }
}


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

use anyhow::Context;

use crate::configuration::bound_label::BoundConfigurationLabel;
use crate::configuration::hash::ConfigurationHash;

#[derive(Debug, thiserror::Error)]
enum BoundConfigurationIdError {
    #[error("Bound configuration id must contain a hash, got: `{0}`")]
    MissingHash(String),
    #[error("Error parsing bound configuration id: `{0}`")]
    Error(String),
}

#[derive(derive_more::Display, Eq, PartialEq, Clone, Debug)]
#[display(fmt = "{}#{}", label, hash)]
pub struct BoundConfigurationId {
    pub label: BoundConfigurationLabel,
    pub hash: ConfigurationHash,
}

impl BoundConfigurationId {
    pub fn parse(id: &str) -> anyhow::Result<BoundConfigurationId> {
        let (label, hash) = id
            .split_once('#')
            .with_context(|| BoundConfigurationIdError::MissingHash(id.to_owned()))?;
        let label = BoundConfigurationLabel::new(label.to_owned())
            .with_context(|| BoundConfigurationIdError::Error(id.to_owned()))?;
        let hash = ConfigurationHash::from_str(hash)
            .with_context(|| BoundConfigurationIdError::Error(id.to_owned()))?;
        Ok(BoundConfigurationId { label, hash })
    }
}

#[cfg(test)]
mod tests {
    use crate::configuration::bound_id::BoundConfigurationId;

    #[test]
    fn test_parse() {
        assert_eq!(
            "foo//:bar#0123456789abcdef",
            BoundConfigurationId::parse("foo//:bar#0123456789abcdef")
                .unwrap()
                .to_string()
        );
    }
}


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

use allocative::Allocative;

use crate::configuration::builtin::BuiltinPlatform;

#[derive(Debug, thiserror::Error)]
enum BoundConfigurationLabelError {
    #[error("Configuration label is empty")]
    LabelIsEmpty,
    #[error("Configuration label is too long: {0:?}")]
    LabelIsTooLong(String),
    #[error("Invalid characters in configuration label: {0:?}")]
    InvalidCharactersInLabel(String),
    #[error("Configuration label must not be equal to builtin configuration label: {0}")]
    Builtin(String),
}

/// Label of regular configuration.
#[derive(
    Clone,
    derive_more::Display,
    Debug,
    Eq,
    PartialEq,
    Hash,
    Ord,
    PartialOrd,
    Allocative
)]
pub struct BoundConfigurationLabel(String);

impl BoundConfigurationLabel {
    pub fn new(label: String) -> anyhow::Result<BoundConfigurationLabel> {
        if label.is_empty() {
            return Err(BoundConfigurationLabelError::LabelIsEmpty.into());
        }
        if label.len() > 1000 {
            // Sanity check.
            return Err(BoundConfigurationLabelError::LabelIsTooLong(label).into());
        }
        if label.chars().any(|c| {
            // TODO(nga): restrict more: label should be either:
            // - a valid target name when a label is created by a `platform()` rule
            // - something like a target name (but not a target label) when created by a transition
            // For example we should prohibit strings like `////` or `[foo//bar]`.
            !c.is_ascii() || c == '#' || c.is_ascii_control() || c == '\t'
        }) {
            return Err(BoundConfigurationLabelError::InvalidCharactersInLabel(label).into());
        }
        if label
            .chars()
            .any(|c| c.is_ascii_whitespace() || c == '(' || c == ')')
        {
            soft_error!(
                "space_parens_in_configuration_label",
                BoundConfigurationLabelError::InvalidCharactersInLabel(label.clone()).into()
            )?;
        }
        if BuiltinPlatform::from_label(&label).is_some() {
            return Err(BoundConfigurationLabelError::Builtin(label).into());
        }
        Ok(BoundConfigurationLabel(label))
    }

    pub(crate) fn as_str(&self) -> &str {
        &self.0
    }
}


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

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

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

#[derive(
    Debug, Copy, Clone, Dupe, Eq, PartialEq, Hash, Ord, PartialOrd, Allocative
)]
pub enum BuiltinPlatform {
    /// The unbound platform is used when we don't yet have a platform bound. This is to support initialization
    /// and is used when analyzing a platform target itself (since we clearly can't have a platform yet bound
    /// at that point).
    /// That leads to a slight oddity that the platform's deps are processed with an empty platform. And so,
    /// the ConfigurationInfo it receives from a constraint that it itself sets, will indicate that the constraint
    /// doesn't match.
    Unbound,

    /// In some cases, a build does not have a default platform. In this case, we will use the "unspecified"
    /// platform. Any attempt to access the ConfigurationData for an "unspecified" platform will fail. This means
    /// that any use of constraints or selects or any other "configuration" feature will fail.
    ///
    /// This is generally only the case for users that haven't yet adopted configurations.
    Unspecified,
    UnspecifiedExec,

    /// The unbound_exec platform is used when we don't yet have an execution platform bound. This is used so that
    /// we can get the exec deps of a "configured" attr, which we need to resolve the execution platform.
    UnboundExec,
}

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

impl BuiltinPlatform {
    pub(crate) fn label(&self) -> &str {
        match self {
            BuiltinPlatform::Unbound => "<unbound>",
            BuiltinPlatform::UnboundExec => "<unbound_exec>",
            BuiltinPlatform::Unspecified => "<unspecified>",
            BuiltinPlatform::UnspecifiedExec => "<unspecified_exec>",
        }
    }

    pub(crate) fn from_label(label: &str) -> Option<BuiltinPlatform> {
        match label {
            "<unbound>" => Some(BuiltinPlatform::Unbound),
            "<unbound_exec>" => Some(BuiltinPlatform::UnboundExec),
            "<unspecified>" => Some(BuiltinPlatform::Unspecified),
            "<unspecified_exec>" => Some(BuiltinPlatform::UnspecifiedExec),
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::configuration::builtin::BuiltinPlatform;

    #[test]
    fn test_from_label() {
        for platform in [
            BuiltinPlatform::Unbound,
            BuiltinPlatform::UnboundExec,
            BuiltinPlatform::Unspecified,
            BuiltinPlatform::UnspecifiedExec,
        ] {
            assert_eq!(
                Some(platform),
                BuiltinPlatform::from_label(platform.label())
            );
        }
    }
}


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

use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt::Write;

use crate::configuration::constraints::ConstraintKey;
use crate::configuration::constraints::ConstraintValue;
use crate::configuration::data::ConfigurationData;
use crate::configuration::data::ConfigurationDataData;

/// If configurations are not equal, return difference.
pub fn cfg_diff(a: &ConfigurationData, b: &ConfigurationData) -> Result<(), String> {
    if a == b {
        return Ok(());
    }

    #[derive(Default)]
    struct DiffPrinter {
        s: String,
    }

    impl DiffPrinter {
        fn print_diff_line(&mut self, sign: char, line: &str) {
            writeln!(self.s, "{} {}", sign, line).unwrap();
        }

        fn print_label_line(&mut self, sign: char, label: &str) {
            self.print_diff_line(sign, &format!("label: {}", label));
        }

        fn print_label_result_line(&mut self, sign: char, label: anyhow::Result<&str>) {
            match label {
                Ok(label) => self.print_label_line(sign, label),
                Err(e) => self.print_diff_line(sign, &format!("label error: {}", e)),
            }
        }

        fn print_cfg_data_result_line(
            &mut self,
            sign: char,
            data: anyhow::Result<&ConfigurationDataData>,
        ) {
            match data {
                Ok(_) => self.print_diff_line(sign, "data"),
                Err(e) => self.print_diff_line(sign, &format!("data error: {}", e)),
            }
        }

        fn diff_label_result(&mut self, a: anyhow::Result<&str>, b: anyhow::Result<&str>) {
            match (a, b) {
                (Ok(a), Ok(b)) => {
                    if a != b {
                        self.print_label_line('-', a);
                        self.print_label_line('+', b);
                    }
                }
                (a, b) => {
                    // There should be no errors because configurations are bound, but better than panic.
                    self.print_label_result_line('-', a);
                    self.print_label_result_line('+', b);
                }
            }
        }

        fn diff_btree_map<K: Ord, V: PartialEq>(
            &mut self,
            a: &BTreeMap<K, V>,
            b: &BTreeMap<K, V>,
            fmt: impl Fn(&K, &V) -> String,
        ) {
            let mut a = a.iter().peekable();
            let mut b = b.iter().peekable();
            loop {
                match (a.peek(), b.peek()) {
                    (Some(an), Some(bn)) => match an.0.cmp(bn.0) {
                        Ordering::Equal => {
                            if an.1 != bn.1 {
                                self.print_diff_line('-', &fmt(an.0, an.1));
                                self.print_diff_line('+', &fmt(bn.0, bn.1));
                            }
                            a.next().unwrap();
                            b.next().unwrap();
                        }
                        Ordering::Less => {
                            self.print_diff_line('-', &fmt(an.0, an.1));
                            a.next().unwrap();
                        }
                        Ordering::Greater => {
                            self.print_diff_line('+', &fmt(bn.0, bn.1));
                            b.next().unwrap();
                        }
                    },
                    (Some(an), None) => {
                        self.print_diff_line('-', &fmt(an.0, an.1));
                        a.next().unwrap();
                    }
                    (None, Some(bn)) => {
                        self.print_diff_line('+', &fmt(bn.0, bn.1));
                        b.next().unwrap();
                    }
                    (None, None) => break,
                }
            }
        }

        fn diff_constraints(
            &mut self,
            a: &BTreeMap<ConstraintKey, ConstraintValue>,
            b: &BTreeMap<ConstraintKey, ConstraintValue>,
        ) {
            self.diff_btree_map(a, b, |k, v| format!("constraint: {} -> {}", k, v))
        }

        fn diff_cfg_data(&mut self, a: &ConfigurationDataData, b: &ConfigurationDataData) {
            self.diff_constraints(&a.constraints, &b.constraints);
        }

        fn diff_cfg_data_result(
            &mut self,
            a: anyhow::Result<&ConfigurationDataData>,
            b: anyhow::Result<&ConfigurationDataData>,
        ) {
            match (a, b) {
                (Ok(a), Ok(b)) => self.diff_cfg_data(a, b),
                (a, b) => {
                    self.print_cfg_data_result_line('-', a);
                    self.print_cfg_data_result_line('+', b);
                }
            }
        }
    }

    let mut diff = DiffPrinter::default();
    diff.diff_label_result(a.label(), b.label());
    diff.diff_cfg_data_result(a.data(), b.data());

    Err(diff.s)
}

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

    use crate::configuration::cfg_diff::cfg_diff;
    use crate::configuration::constraints::ConstraintKey;
    use crate::configuration::constraints::ConstraintValue;
    use crate::configuration::data::ConfigurationData;
    use crate::configuration::data::ConfigurationDataData;
    use crate::target::label::TargetLabel;

    #[test]
    fn test_diff() {
        let x = ConfigurationData::from_platform(
            "xx".to_owned(),
            ConfigurationDataData::new(BTreeMap::from_iter([
                (
                    ConstraintKey(TargetLabel::testing_parse("foo//bar:c")),
                    ConstraintValue(TargetLabel::testing_parse("foo//bar:v")),
                ),
                (
                    ConstraintKey(TargetLabel::testing_parse("foo//qux:c")),
                    ConstraintValue(TargetLabel::testing_parse("foo//qux:vx")),
                ),
            ])),
        )
        .unwrap();
        let y = ConfigurationData::from_platform(
            "yy".to_owned(),
            ConfigurationDataData::new(BTreeMap::from_iter([
                (
                    ConstraintKey(TargetLabel::testing_parse("foo//bar:c")),
                    ConstraintValue(TargetLabel::testing_parse("foo//bar:v")),
                ),
                (
                    ConstraintKey(TargetLabel::testing_parse("foo//baz:c")),
                    ConstraintValue(TargetLabel::testing_parse("foo//baz:vy")),
                ),
                (
                    ConstraintKey(TargetLabel::testing_parse("foo//qux:c")),
                    ConstraintValue(TargetLabel::testing_parse("foo//qux:vy")),
                ),
            ])),
        )
        .unwrap();
        let diff = cfg_diff(&x, &y).unwrap_err();
        assert_eq!(
            "\
            - label: xx\n\
            + label: yy\n\
            + constraint: foo//baz:c -> foo//baz:vy\n\
            - constraint: foo//qux:c -> foo//qux:vx\n\
            + constraint: foo//qux:c -> foo//qux:vy\n\
            ",
            diff
        );
    }
}


/*
 * 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 std::fmt::Formatter;
use std::fmt::Write;
use std::hash::Hash;
use std::sync::Arc;

use allocative::Allocative;
use dupe::Dupe;
use thiserror::Error;

use crate::target::label::ConfiguredTargetLabel;
use crate::target::label::TargetLabel;

#[derive(Debug, Error)]
enum CompatibilityErrors {
    #[error("{0}")]
    TargetIncompatible(IncompatiblePlatformReason),
}

/// MaybeCompatible is used to gracefully deal with things that are incompatible
/// with the target platform. The main place this comes up is that targets provided on the
/// cli may be incompatible with the default or requested platform and we want to skip
/// building those rather than have it be an error.
#[derive(Clone, Dupe, Debug, Eq, PartialEq, Hash, Allocative)]
pub enum MaybeCompatible<T> {
    Incompatible(Arc<IncompatiblePlatformReason>),
    Compatible(T),
}

impl<T> MaybeCompatible<T> {
    /// Converts to a result. Incompatible values get converted to an error.
    ///
    /// This is just a convencience for treating incompatibility as an error.
    pub fn require_compatible(self) -> anyhow::Result<T> {
        match self {
            MaybeCompatible::Incompatible(reason) => Err(reason.to_err()),
            MaybeCompatible::Compatible(result) => Ok(result),
        }
    }

    pub fn is_compatible(&self) -> bool {
        matches!(self, Self::Compatible(..))
    }

    pub fn map<U>(self, func: impl FnOnce(T) -> U) -> MaybeCompatible<U> {
        match self {
            MaybeCompatible::Incompatible(e) => MaybeCompatible::Incompatible(e),
            MaybeCompatible::Compatible(t) => MaybeCompatible::Compatible(func(t)),
        }
    }

    pub fn try_map<E, U>(
        self,
        func: impl FnOnce(T) -> Result<U, E>,
    ) -> Result<MaybeCompatible<U>, E> {
        match self {
            MaybeCompatible::Incompatible(e) => Ok(MaybeCompatible::Incompatible(e)),
            MaybeCompatible::Compatible(t) => Ok(MaybeCompatible::Compatible(func(t)?)),
        }
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Dupe, Allocative)]
pub enum IncompatiblePlatformReasonCause {
    /// Target is incompatible because of unsatisfied config setting.
    UnsatisfiedConfig(TargetLabel),
    /// Target is incompatible because dependency is incompatible.
    Dependency(Arc<IncompatiblePlatformReason>),
}

#[derive(Debug, Eq, PartialEq, Hash, Clone, Allocative)]
pub struct IncompatiblePlatformReason {
    pub target: ConfiguredTargetLabel,
    pub cause: IncompatiblePlatformReasonCause,
}

impl IncompatiblePlatformReason {
    pub fn to_err(&self) -> anyhow::Error {
        CompatibilityErrors::TargetIncompatible(self.clone()).into()
    }

    pub fn skipping_message(&self, target: &ConfiguredTargetLabel) -> String {
        format!("Skipping target incompatible node `{}`", target)
    }

    pub fn skipping_message_for_multiple<'t>(
        incompatible_targets: impl IntoIterator<Item = &'t ConfiguredTargetLabel>,
    ) -> String {
        let mut incompatible_targets = incompatible_targets.into_iter().collect::<Vec<_>>();
        incompatible_targets.sort();
        let mut message = String::new();

        writeln!(
            message,
            "Skipped {} incompatible targets:",
            incompatible_targets.len()
        )
        .unwrap();
        if incompatible_targets.len() < 10 {
            for target in incompatible_targets.iter() {
                writeln!(message, "  {}", target).unwrap();
            }
        } else {
            for target in incompatible_targets.iter().take(3) {
                writeln!(message, "  {}", target).unwrap();
            }
            writeln!(message, "  ...").unwrap();
            for target in incompatible_targets.iter().rev().take(3).rev() {
                writeln!(message, "  {}", target).unwrap();
            }
        }
        message
    }
}

impl Display for IncompatiblePlatformReason {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match &self.cause {
            IncompatiblePlatformReasonCause::UnsatisfiedConfig(unsatisfied_config) => write!(
                f,
                "{} is incompatible with {} ({} unsatisfied), check the target's compatibility attributes",
                self.target.unconfigured(),
                self.target.cfg(),
                unsatisfied_config,
            ),
            IncompatiblePlatformReasonCause::Dependency(previous) => {
                if f.alternate() {
                    write!(f, "{}\n-> {:#}", self.target, previous)
                } else {
                    write!(f, "{} -> {}", self.target, previous)
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::configuration::compatibility::IncompatiblePlatformReason;
    use crate::configuration::data::ConfigurationData;
    use crate::target::label::TargetLabel;

    #[test]
    fn test_skipping_message_for_multiple() {
        let set = vec![
            TargetLabel::testing_parse("glass//foo:bar")
                .configure(ConfigurationData::testing_new()),
        ];
        assert_eq!(
            format!(
                "Skipped 1 incompatible targets:\n  glass//foo:bar ({})\n",
                ConfigurationData::testing_new()
            ),
            IncompatiblePlatformReason::skipping_message_for_multiple(&set),
        );

        let mut set = Vec::new();
        for i in 0..20 {
            // Test it doesn't crash
            IncompatiblePlatformReason::skipping_message_for_multiple(&set);

            set.push(
                TargetLabel::testing_parse(&format!("plate//foo:bar{}", i))
                    .configure(ConfigurationData::testing_new()),
            );
        }
        assert_eq!(
            format!(
                r"Skipped 20 incompatible targets:
  plate//foo:bar0 ({c})
  plate//foo:bar1 ({c})
  plate//foo:bar10 ({c})
  ...
  plate//foo:bar7 ({c})
  plate//foo:bar8 ({c})
  plate//foo:bar9 ({c})
",
                c = ConfigurationData::testing_new()
            ),
            IncompatiblePlatformReason::skipping_message_for_multiple(&set)
        );
    }
}


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

use std::collections::BTreeMap;

use allocative::Allocative;

use crate::configuration::constraints::ConstraintKey;
use crate::configuration::constraints::ConstraintValue;

/// Parsed provider returned from `config_setting` rule.
#[derive(Debug, Eq, PartialEq, Hash, Allocative)]
pub struct ConfigSettingData {
    // contains the full specification of the platform configuration
    pub constraints: BTreeMap<ConstraintKey, ConstraintValue>,
    // contains mappings of `section.key` to `value` for buckconfigs
    // TODO(scottcao): Make this into a Vec<ConfigArgumentPair> for more structured data
    // This can't be done right now because ConfigArgumentPair lives in buck2_common
    // and buck2_core cannot depend on buck2_common.
    pub buckconfigs: BTreeMap<String, String>,
}

impl ConfigSettingData {
    fn is_subset<K: Ord, V: Eq>(a: &BTreeMap<K, V>, b: &BTreeMap<K, V>) -> bool {
        // TODO(nga): this can be done in linear time.
        a.len() <= b.len() && a.iter().all(|(k, v)| b.get(k) == Some(v))
    }

    fn len_sum(&self) -> usize {
        self.constraints.len() + self.buckconfigs.len()
    }

    pub fn refines(&self, that: &ConfigSettingData) -> bool {
        self.len_sum() > that.len_sum()
            && Self::is_subset(&that.constraints, &self.constraints)
            && Self::is_subset(&that.buckconfigs, &self.buckconfigs)
    }
}

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

    use dupe::Dupe;

    use crate::configuration::config_setting::ConfigSettingData;
    use crate::configuration::constraints::ConstraintKey;
    use crate::configuration::constraints::ConstraintValue;
    use crate::target::label::TargetLabel;

    #[test]
    fn is_subset() {
        let m_12_34 = BTreeMap::from_iter([(1, 2), (3, 4)]);
        let m_12_35 = BTreeMap::from_iter([(1, 2), (3, 5)]);
        let m_12 = BTreeMap::from_iter([(1, 2)]);
        let empty = BTreeMap::<u32, u32>::new();

        // A set is a subset of itself
        assert!(ConfigSettingData::is_subset(&m_12_34, &m_12_34));
        assert!(ConfigSettingData::is_subset(&m_12, &m_12));
        assert!(ConfigSettingData::is_subset(&empty, &empty));

        assert!(ConfigSettingData::is_subset(&m_12, &m_12_34));
        assert!(!ConfigSettingData::is_subset(&m_12_34, &m_12));

        assert!(!ConfigSettingData::is_subset(&m_12_34, &m_12_35));
    }

    #[test]
    fn refines() {
        fn constraint_key(t: &str) -> ConstraintKey {
            ConstraintKey(TargetLabel::testing_parse(t))
        }

        fn constraint_value(t: &str) -> ConstraintValue {
            ConstraintValue(TargetLabel::testing_parse(t))
        }

        let os = constraint_key("config//:os");
        let linux = constraint_value("config//:linux");
        let cpu = constraint_key("config//:cpu");
        let arm64 = constraint_value("config//:arm64");
        let x86_64 = constraint_value("config//:x86_64");

        let c_linux = ConfigSettingData {
            constraints: BTreeMap::from_iter([(os.dupe(), linux.dupe())]),
            buckconfigs: BTreeMap::new(),
        };
        let c_arm64 = ConfigSettingData {
            constraints: BTreeMap::from_iter([(cpu.dupe(), arm64.dupe())]),
            buckconfigs: BTreeMap::new(),
        };
        let c_linux_arm64 = ConfigSettingData {
            constraints: BTreeMap::from_iter([
                (os.dupe(), linux.dupe()),
                (cpu.dupe(), arm64.dupe()),
            ]),
            buckconfigs: BTreeMap::new(),
        };
        let c_linux_x86_64 = ConfigSettingData {
            constraints: BTreeMap::from_iter([
                (os.dupe(), linux.dupe()),
                (cpu.dupe(), x86_64.dupe()),
            ]),
            buckconfigs: BTreeMap::new(),
        };

        // Config setting does not refines identical config setting.
        assert!(!c_linux.refines(&c_linux));

        assert!(!c_linux.refines(&c_arm64));
        assert!(!c_arm64.refines(&c_linux));

        assert!(c_linux_arm64.refines(&c_linux));
        assert!(c_linux_arm64.refines(&c_arm64));

        assert!(!c_linux_x86_64.refines(&c_linux_arm64));
    }

    #[test]
    fn buckconfig_refines() {
        let c1 = ConfigSettingData {
            constraints: BTreeMap::new(),
            buckconfigs: BTreeMap::from_iter([("foo.bar".to_owned(), "baz".to_owned())]),
        };
        let c11 = ConfigSettingData {
            constraints: BTreeMap::new(),
            buckconfigs: BTreeMap::from_iter([
                ("foo.bar".to_owned(), "baz".to_owned()),
                ("foo.qux".to_owned(), "quux".to_owned()),
            ]),
        };

        assert!(c11.refines(&c1));
        assert!(!c11.refines(&c11));
        assert!(!c1.refines(&c1));
        assert!(!c1.refines(&c11));
    }
}


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

//! Constraints are the building block of buck platforms.
//!
//! A constraint is identified by a "constraint key" defined by a `constraint_setting()`
//! target. There may be multiple possible values for the constraint, each defined by a
//! `constraint_value()` target.
//!
//! Currently, a platform or configuration has at most a single value for each constraint.

use allocative::Allocative;
use derive_more::Display;
use dupe::Dupe;

use crate::target::label::TargetLabel;

/// A ConstraintKey is a label for a `constraint_setting()` target.
#[derive(
    Clone, Dupe, Debug, Display, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative
)]
pub struct ConstraintKey(pub TargetLabel);

#[derive(
    Clone, Dupe, Debug, Display, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative
)]
pub struct ConstraintValue(pub TargetLabel);


/*
 * 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::hash_map::DefaultHasher;
use std::collections::BTreeMap;
use std::hash::Hash;
use std::hash::Hasher;

use allocative::Allocative;
use buck2_data::ToProtoMessage;
use dupe::Dupe;
use internment_tweaks::Equiv;
use internment_tweaks::Intern;
use internment_tweaks::StaticInterner;
use once_cell::sync::Lazy;
use serde::Serialize;
use serde::Serializer;

use crate::configuration::bound_id::BoundConfigurationId;
use crate::configuration::bound_label::BoundConfigurationLabel;
use crate::configuration::builtin::BuiltinPlatform;
use crate::configuration::constraints::ConstraintKey;
use crate::configuration::constraints::ConstraintValue;
use crate::configuration::hash::ConfigurationHash;

#[derive(Debug, thiserror::Error)]
enum ConfigurationError {
    #[error(
        "Attempted to access the configuration data for the {0} platform. \
        This platform is used when the global default platform is unspecified \
        and in that case configuration features (like `select()`) are unsupported."
    )]
    Builtin(BuiltinPlatform),
    #[error("Platform is not bound: {0}")]
    NotBound(String),
    #[error(
        "Attempted to access the configuration data for the \"unspecified_exec\" platform. This platform is used when no execution platform was resolved for a target."
    )]
    UnspecifiedExec,
}

#[derive(Debug, thiserror::Error)]
enum ConfigurationLookupError {
    #[error("
    Could not find configuration `{0}`. Configuration lookup by string requires
    that buck has already loaded the configuration through some other mechanism. You can run `buck2 cquery <some_target>`
    with a target that uses the configuration (somewhere in its graph) to make buck aware of the configuration first.
    ")]
    ConfigNotFound(BoundConfigurationId),
    #[error(
        "Found configuration `{0}` by hash, but label mismatched from what is requested: `{1}`"
    )]
    ConfigFoundByHashLabelMismatch(ConfigurationData, BoundConfigurationId),
}

/// The inner PlatformConfigurationData is interned as the same configuration could be formed through
/// paths (as many transitions are associative).
#[derive(
    Clone,
    Debug,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Allocative,
    derive_more::Display
)]
pub struct ConfigurationData(Intern<HashedConfigurationPlatform>);

/// Intern doesn't implement Hash.
#[allow(clippy::derived_hash_with_manual_eq)] // The derived PartialEq (that uses pointer equality) is still correct.
impl Hash for ConfigurationData {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}

impl Dupe for ConfigurationData {}

#[derive(Hash)]
struct ConfigurationHashRef<'a>(&'a str);

impl<'a> Equiv<HashedConfigurationPlatform> for ConfigurationHashRef<'a> {
    fn equivalent(&self, key: &HashedConfigurationPlatform) -> bool {
        self.0 == key.output_hash.as_str()
    }
}

static INTERNER: StaticInterner<HashedConfigurationPlatform> = StaticInterner::new();

impl ConfigurationData {
    /// Produces a "bound" configuration for a platform. The label should be a unique identifier for the data.
    pub fn from_platform(label: String, data: ConfigurationDataData) -> anyhow::Result<Self> {
        let label = BoundConfigurationLabel::new(label)?;
        Ok(Self::from_data(HashedConfigurationPlatform::new(
            ConfigurationPlatform::Bound(label, data),
        )))
    }

    pub fn unspecified() -> Self {
        static CONFIG: Lazy<ConfigurationData> = Lazy::new(|| {
            ConfigurationData::from_data(HashedConfigurationPlatform::new(
                ConfigurationPlatform::Builtin(BuiltinPlatform::Unspecified),
            ))
        });
        CONFIG.dupe()
    }

    pub fn unspecified_exec() -> Self {
        static CONFIG: Lazy<ConfigurationData> = Lazy::new(|| {
            ConfigurationData::from_data(HashedConfigurationPlatform::new(
                ConfigurationPlatform::Builtin(BuiltinPlatform::UnspecifiedExec),
            ))
        });
        CONFIG.dupe()
    }

    /// Produces the "unbound" configuration. This is used only when performing analysis of platform() targets and
    /// their dependencies (which is done to form the initial "bound" configurations).
    pub fn unbound() -> Self {
        static CONFIG: Lazy<ConfigurationData> = Lazy::new(|| {
            ConfigurationData::from_data(HashedConfigurationPlatform::new(
                ConfigurationPlatform::Builtin(BuiltinPlatform::Unbound),
            ))
        });
        CONFIG.dupe()
    }

    /// Produces the "unbound_exec" configuration. This is used only when getting the exec_deps for a configured node
    /// before we've determined the execution configuration for the node.
    pub fn unbound_exec() -> Self {
        static CONFIG: Lazy<ConfigurationData> = Lazy::new(|| {
            ConfigurationData::from_data(HashedConfigurationPlatform::new(
                ConfigurationPlatform::Builtin(BuiltinPlatform::UnboundExec),
            ))
        });
        CONFIG.dupe()
    }

    pub fn builtin(builtin: BuiltinPlatform) -> Self {
        match builtin {
            BuiltinPlatform::Unspecified => Self::unspecified(),
            BuiltinPlatform::UnspecifiedExec => Self::unspecified_exec(),
            BuiltinPlatform::Unbound => Self::unbound(),
            BuiltinPlatform::UnboundExec => Self::unbound_exec(),
        }
    }

    /// Produces an "invalid" configuration for testing.
    pub fn testing_new() -> Self {
        Self::from_data(HashedConfigurationPlatform::new(
            ConfigurationPlatform::Bound(
                BoundConfigurationLabel::new("<testing>".to_owned()).unwrap(),
                ConfigurationDataData {
                    constraints: BTreeMap::new(),
                },
            ),
        ))
    }

    fn from_data(data: HashedConfigurationPlatform) -> Self {
        Self(INTERNER.intern(data))
    }

    /// Iterates over the existing interned configurations. As these configurations
    /// are never evicted, this may return configurations that aren't present in the
    /// actual current state (for example, if you do a build and then delete everything
    /// this will still iterate over previously existing configurations).
    pub fn iter_existing() -> impl Iterator<Item = Self> {
        INTERNER.iter().map(Self)
    }

    /// Looks up a known configuration from a `Configuration::full_name()` string. Generally
    /// this is a debugging utility that most buck code shouldn't use, it's primarily useful
    /// for resolving configuration strings provided on the command line.
    ///
    /// This can only find configurations that have otherwise already been encountered by
    /// the current daemon process.
    pub fn lookup_bound(cfg: BoundConfigurationId) -> anyhow::Result<Self> {
        match INTERNER.get(ConfigurationHashRef(cfg.hash.as_str())) {
            Some(found_cfg) => {
                let found_cfg = ConfigurationData(found_cfg);
                if found_cfg.bound_id().as_ref() != Some(&cfg) {
                    Err(
                        ConfigurationLookupError::ConfigFoundByHashLabelMismatch(found_cfg, cfg)
                            .into(),
                    )
                } else {
                    Ok(found_cfg)
                }
            }
            None => Err(ConfigurationLookupError::ConfigNotFound(cfg).into()),
        }
    }

    pub fn get_constraint_value(
        &self,
        key: &ConstraintKey,
    ) -> anyhow::Result<Option<&ConstraintValue>> {
        Ok(self.data()?.constraints.get(key))
    }

    pub fn label(&self) -> anyhow::Result<&str> {
        match &self.0.configuration_platform {
            ConfigurationPlatform::Bound(label, _) => Ok(label.as_str()),
            _ => Err(ConfigurationError::NotBound(self.to_string()).into()),
        }
    }

    pub fn data(&self) -> anyhow::Result<&ConfigurationDataData> {
        match &self.0.configuration_platform {
            ConfigurationPlatform::Builtin(BuiltinPlatform::UnspecifiedExec) => {
                Err(ConfigurationError::UnspecifiedExec.into())
            }
            ConfigurationPlatform::Builtin(builtin) => {
                Err(ConfigurationError::Builtin(*builtin).into())
            }
            ConfigurationPlatform::Bound(_, data) => Ok(data),
        }
    }

    pub fn is_unbound(&self) -> bool {
        match &self.0.configuration_platform {
            ConfigurationPlatform::Builtin(BuiltinPlatform::Unbound) => true,
            _ => false,
        }
    }

    pub fn bound(&self) -> Option<&BoundConfigurationLabel> {
        match &self.0.configuration_platform {
            ConfigurationPlatform::Bound(label, _) => Some(label),
            _ => None,
        }
    }

    pub fn bound_id(&self) -> Option<BoundConfigurationId> {
        Some(BoundConfigurationId {
            label: self.bound()?.clone(),
            hash: self.output_hash().clone(),
        })
    }

    pub fn is_bound(&self) -> bool {
        match &self.0.configuration_platform {
            ConfigurationPlatform::Bound(..) => true,
            _ => false,
        }
    }

    pub fn output_hash(&self) -> &ConfigurationHash {
        &self.0.output_hash
    }

    /// Name without hash.
    pub fn short_name(&self) -> &str {
        self.0.configuration_platform.label()
    }

    pub fn full_name(&self) -> &str {
        &self.0.full_name
    }
}

impl Serialize for ConfigurationData {
    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        s.collect_str(self)
    }
}

impl ToProtoMessage for ConfigurationData {
    type Message = buck2_data::Configuration;

    fn as_proto(&self) -> Self::Message {
        buck2_data::Configuration {
            full_name: self.full_name().to_owned(),
        }
    }
}

#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative)]
enum ConfigurationPlatform {
    /// This represents the normal case where a platform has been defined by a `platform()` (or similar) target.
    Bound(BoundConfigurationLabel, ConfigurationDataData),
    Builtin(BuiltinPlatform),
}

impl ConfigurationPlatform {
    fn label(&self) -> &str {
        match self {
            ConfigurationPlatform::Bound(label, _) => label.as_str(),
            ConfigurationPlatform::Builtin(builtin) => builtin.label(),
        }
    }
}

/// A set of values used in configuration-related contexts.
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Allocative)]
pub struct ConfigurationDataData {
    // contains the full specification of the platform configuration
    pub constraints: BTreeMap<ConstraintKey, ConstraintValue>,
}

/// We don't use derive(Hash) here because we build Buck 2 on two different versions of Rustc at
/// the moment, and their hashing disagrees <https://github.com/rust-lang/rust/pull/89443>. In any
/// case, we should control what goes into our hash here.
#[allow(clippy::derived_hash_with_manual_eq)]
impl Hash for ConfigurationDataData {
    fn hash<H: Hasher>(&self, state: &mut H) {
        for elt in self.constraints.iter() {
            elt.hash(state);
        }
    }
}

impl ConfigurationDataData {
    pub fn empty() -> Self {
        Self {
            constraints: Default::default(),
        }
    }

    pub fn new(constraints: BTreeMap<ConstraintKey, ConstraintValue>) -> Self {
        Self { constraints }
    }

    pub fn get_constraint_value(&self, key: &ConstraintKey) -> Option<&ConstraintValue> {
        self.constraints.get(key)
    }

    /// merges this into other, with values in other taking precedence
    pub fn merge(&self, mut other: ConfigurationDataData) -> Self {
        for (k, v) in &self.constraints {
            other
                .constraints
                .entry(k.dupe())
                .or_insert_with(|| v.dupe());
        }
        other
    }
}

#[derive(
    Debug,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Allocative,
    derive_more::Display
)]
#[display(fmt = "{}", full_name)]
pub(crate) struct HashedConfigurationPlatform {
    configuration_platform: ConfigurationPlatform,
    // The remaining fields are computed from `platform_configuration_data`.
    /// The "full name" includes both the platform and a hash of the configuration data.
    full_name: String,
    /// A hash of the configuration data that is used for determining output paths.
    output_hash: ConfigurationHash,
}

impl Equiv<HashedConfigurationPlatform> for HashedConfigurationPlatform {
    fn equivalent(&self, key: &HashedConfigurationPlatform) -> bool {
        self == key
    }
}

/// This will hash just the "output_hash" which should uniquely identify this data.
#[allow(clippy::derived_hash_with_manual_eq)] // The derived PartialEq is still correct.
impl std::hash::Hash for HashedConfigurationPlatform {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.output_hash.hash(state)
    }
}

impl HashedConfigurationPlatform {
    fn new(configuration_platform: ConfigurationPlatform) -> Self {
        // TODO(cjhopman): Should this be a crypto hasher?
        let mut hasher = DefaultHasher::new();
        configuration_platform.hash(&mut hasher);
        let output_hash = hasher.finish();
        let output_hash = ConfigurationHash::new(output_hash);

        let full_name = match &configuration_platform {
            ConfigurationPlatform::Bound(label, _cfg) => {
                format!("{:#}#{}", label, output_hash)
            }
            ConfigurationPlatform::Builtin(builtin) => builtin.label().to_owned(),
        };
        Self {
            configuration_platform,
            full_name,
            output_hash,
        }
    }
}

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

    use crate::configuration::bound_id::BoundConfigurationId;
    use crate::configuration::constraints::ConstraintKey;
    use crate::configuration::constraints::ConstraintValue;
    use crate::configuration::data::ConfigurationData;
    use crate::configuration::data::ConfigurationDataData;
    use crate::target::label::TargetLabel;

    /// We don't want the output hash to change by accident. This test is here to assert that it
    /// doesn't. If we have a legit reason to update the config hash, we can update the hash here,
    /// but this will ensure we a) know and b) don't do it by accident.
    #[test]
    fn test_stable_output_hash() -> anyhow::Result<()> {
        let configuration = ConfigurationData::from_platform(
            "cfg_for//:testing_exec".to_owned(),
            ConfigurationDataData {
                constraints: BTreeMap::from_iter([
                    (
                        ConstraintKey(TargetLabel::testing_parse("foo//bar:c")),
                        ConstraintValue(TargetLabel::testing_parse("foo//bar:v")),
                    ),
                    (
                        ConstraintKey(TargetLabel::testing_parse("foo//qux:c")),
                        ConstraintValue(TargetLabel::testing_parse("foo//qux:vx")),
                    ),
                ]),
            },
        )
        .unwrap();

        assert_eq!(configuration.output_hash().as_str(), "fd698fb05d52efbc");
        assert_eq!(
            configuration.to_string(),
            "cfg_for//:testing_exec#fd698fb05d52efbc"
        );

        Ok(())
    }

    #[test]
    fn test_lookup_from_string() {
        let configuration = ConfigurationData::from_platform(
            "cfg_for//:testing_exec".to_owned(),
            ConfigurationDataData {
                constraints: BTreeMap::from_iter([
                    (
                        ConstraintKey(TargetLabel::testing_parse("foo//bar:c")),
                        ConstraintValue(TargetLabel::testing_parse("foo//bar:v")),
                    ),
                    (
                        ConstraintKey(TargetLabel::testing_parse("foo//qux:c")),
                        ConstraintValue(TargetLabel::testing_parse("foo//qux:vx")),
                    ),
                ]),
            },
        )
        .unwrap();

        assert_eq!(
            "cfg_for//:testing_exec#fd698fb05d52efbc",
            configuration.to_string()
        );

        let looked_up = ConfigurationData::lookup_bound(
            BoundConfigurationId::parse("cfg_for//:testing_exec#fd698fb05d52efbc").unwrap(),
        )
        .unwrap();
        assert_eq!(configuration, looked_up);
    }
}


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

use allocative::Allocative;

#[derive(Debug, thiserror::Error)]
enum ConfigurationHashError {
    #[error("Configuration hash must be 16 hex digits, got: `{0}`")]
    Invalid(String),
}

/// Hash of a configuration, serialized as a hex string.
///
/// Configuration hash is computed by hashing all configuration data,
/// and it distinguishes configurations with different short names.
#[derive(
    Clone,
    Debug,
    Eq,
    PartialEq,
    Hash,
    Allocative,
    derive_more::Display,
    Ord,
    PartialOrd
)]
pub struct ConfigurationHash(pub(crate) String);

impl ConfigurationHash {
    pub fn new(value: u64) -> ConfigurationHash {
        ConfigurationHash(format!("{:0>16x}", value))
    }

    pub(crate) fn from_str(value: &str) -> anyhow::Result<ConfigurationHash> {
        if value.len() != 16 {
            return Err(ConfigurationHashError::Invalid(value.to_owned()).into());
        }
        for c in value.chars() {
            if !c.is_ascii_hexdigit() {
                return Err(ConfigurationHashError::Invalid(value.to_owned()).into());
            }
        }
        Ok(ConfigurationHash(value.to_owned()))
    }

    #[inline]
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

#[cfg(test)]
mod tests {
    use crate::configuration::hash::ConfigurationHash;

    #[test]
    fn test_hash() {
        assert_eq!("0000000000000000", ConfigurationHash::new(0).as_str());
        assert_eq!(
            "ffffffffffffffff",
            ConfigurationHash::new(u64::MAX).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.
 */

//! A 'Configuration' is a set of attributes that are attached to each node in
//! the 'static graph' that affects the behaviour of the build. Examples of
//! these attributes are the target platform, and compiler settings.
//!
//! 'Configuration's are propagated from the top level request node to each of
//! the transitive child nodes. During propagation, the configuration may change
//! under a "transition". Multiple distinct configurations may be applied to the
//! transitive graph, effectively duplicating the graph to create two distinct
//! graphs with different build behaviours (split-transitions).
//!

pub mod bound_id;
pub mod bound_label;
pub(crate) mod builtin;
pub mod cfg_diff;
pub mod compatibility;
pub mod config_setting;
pub mod constraints;
pub mod data;
pub mod hash;
pub mod pair;
pub mod transition;


/*
 * 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;
use fnv::FnvHasher;
use internment_tweaks::Equiv;
use internment_tweaks::Intern;
use internment_tweaks::StaticInterner;
use once_cell::sync::Lazy;

use crate::configuration::data::ConfigurationData;

#[derive(Debug, thiserror::Error)]
enum ConfigurationError {
    #[error("`ConfigurationPair` has unexpected `exec_cfg`")]
    HasExecCfg,
}

#[derive(Debug, Allocative, Hash, Eq, PartialEq, Ord, PartialOrd)]
struct ConfigurationPairData {
    cfg: ConfigurationData,
    /// Usually this is None, but for toolchain deps where the exec_cfg isn't picked it is set
    exec_cfg: Option<ConfigurationData>,
}

impl Equiv<ConfigurationPairData> for ConfigurationPairData {
    fn equivalent(&self, key: &ConfigurationPairData) -> bool {
        self == key
    }
}

/// Pair of `cfg` and `exec_cfg`.
/// These two are added to `TargetLabel` to make `ConfiguredTargetLabel`.
#[derive(Debug, Clone, Dupe, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative)]
pub struct Configuration(Intern<ConfigurationPairData>);

static INTERNER: StaticInterner<ConfigurationPairData, FnvHasher> = StaticInterner::new();

impl Configuration {
    #[inline]
    pub fn new(cfg: ConfigurationData, exec_cfg: Option<ConfigurationData>) -> Configuration {
        Configuration(INTERNER.intern(ConfigurationPairData { cfg, exec_cfg }))
    }

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

    #[inline]
    pub fn exec_cfg(&self) -> Option<&ConfigurationData> {
        self.0.exec_cfg.as_ref()
    }

    #[inline]
    pub fn check_no_exec_cfg(&self) -> anyhow::Result<ConfigurationNoExec> {
        if self.exec_cfg().is_some() {
            return Err(ConfigurationError::HasExecCfg.into());
        }
        Ok(ConfigurationNoExec(self.dupe()))
    }
}

/// `ConfigurationPair` where `exec_cfg` is always `None`.
#[derive(
    Debug,
    Clone,
    Dupe,
    Eq,
    PartialEq,
    Hash,
    Ord,
    PartialOrd,
    Allocative,
    derive_more::Display
)]
#[display(fmt = "{}", "self.cfg()")]
pub struct ConfigurationNoExec(Configuration);

impl ConfigurationNoExec {
    #[inline]
    pub fn new(cfg: ConfigurationData) -> ConfigurationNoExec {
        ConfigurationNoExec(Configuration::new(cfg, None))
    }

    #[inline]
    pub fn unbound() -> ConfigurationNoExec {
        static UNBOUND: Lazy<ConfigurationNoExec> =
            Lazy::new(|| ConfigurationNoExec::new(ConfigurationData::unbound()));
        UNBOUND.dupe()
    }

    #[inline]
    pub fn unspecified_exec() -> Self {
        static UNSPECIFIED_EXEC: Lazy<ConfigurationNoExec> =
            Lazy::new(|| ConfigurationNoExec::new(ConfigurationData::unspecified_exec()));
        UNSPECIFIED_EXEC.dupe()
    }

    #[inline]
    pub fn unbound_exec() -> Self {
        static UNBOUND_EXEC: Lazy<ConfigurationNoExec> =
            Lazy::new(|| ConfigurationNoExec::new(ConfigurationData::unbound_exec()));
        UNBOUND_EXEC.dupe()
    }

    #[inline]
    pub fn unspecified() -> Self {
        static UNSPECIFIED: Lazy<ConfigurationNoExec> =
            Lazy::new(|| ConfigurationNoExec::new(ConfigurationData::unspecified()));
        UNSPECIFIED.dupe()
    }

    #[inline]
    pub fn testing_new() -> Self {
        static TESTING_NEW: Lazy<ConfigurationNoExec> =
            Lazy::new(|| ConfigurationNoExec::new(ConfigurationData::testing_new()));
        TESTING_NEW.dupe()
    }

    #[inline]
    pub fn cfg_pair(&self) -> &Configuration {
        &self.0
    }

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

    #[inline]
    pub fn make_toolchain(&self, exec_cfg: &ConfigurationNoExec) -> ConfigurationWithExec {
        ConfigurationWithExec::new(self.cfg().dupe(), exec_cfg.cfg().dupe())
    }
}

/// `ConfigurationPair` where `exec_cfg` is always `Some`.
#[derive(Debug, Clone, Dupe, Eq, PartialEq, Hash, Ord, PartialOrd, Allocative)]
pub struct ConfigurationWithExec(Configuration);

impl ConfigurationWithExec {
    #[inline]
    pub fn new(cfg: ConfigurationData, exec_cfg: ConfigurationData) -> ConfigurationWithExec {
        ConfigurationWithExec(Configuration::new(cfg, Some(exec_cfg)))
    }

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

    #[inline]
    pub fn exec_cfg(&self) -> &ConfigurationData {
        self.cfg_pair()
            .exec_cfg()
            .expect("`exec_cfg` is always `Some`")
    }

    #[inline]
    pub fn cfg_pair(&self) -> &Configuration {
        &self.0
    }
}


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

use allocative::Allocative;
use buck2_util::collections::sorted_map::SortedMap;

use crate::configuration::data::ConfigurationData;

#[derive(thiserror::Error, Debug)]
enum TransitionAppliedError {
    #[error(
        "Transition object is declared split, but transition to one is needed in this context"
    )]
    SplitWhereSingleExpected,
    #[error(
        "Transition object is declared non-split, but split transition is needed in this context"
    )]
    SingleWhereSplitExpected,
}

/// Result of `transition` function application to a configuration.
#[derive(PartialEq, Eq, Hash, Debug, Allocative)]
pub enum TransitionApplied {
    /// Transition to single configuration.
    Single(ConfigurationData),
    /// Split transition.
    Split(SortedMap<String, ConfigurationData>),
}

impl TransitionApplied {
    pub fn single(&self) -> anyhow::Result<&ConfigurationData> {
        match self {
            TransitionApplied::Single(configuration) => Ok(configuration),
            _ => Err(TransitionAppliedError::SplitWhereSingleExpected.into()),
        }
    }

    pub fn split(&self) -> anyhow::Result<&SortedMap<String, ConfigurationData>> {
        match self {
            TransitionApplied::Split(configurations) => Ok(configurations),
            _ => Err(TransitionAppliedError::SingleWhereSplitExpected.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 allocative::Allocative;
use derive_more::Display;

use crate::bzl::ImportPath;

/// Identifier of transition function.
#[derive(Debug, Clone, Hash, Eq, PartialEq, Display, Allocative)]
#[display(fmt = "{}#{}", path, name)]
pub struct TransitionId {
    pub path: ImportPath,
    pub name: String,
}


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

pub mod applied;
pub mod id;


/*
 * 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 derivative::Derivative;
use either::Either;
use starlark_map::small_map::Entry;
use starlark_map::small_map::SmallMap;
use thiserror::Error;

use super::Directory;
use super::DirectoryData;
use super::DirectoryDigest;
use super::DirectoryEntries;
use super::DirectoryEntry;
use super::DirectoryHasher;
use super::DirectoryMut;
use super::ExclusiveDirectory;
use super::FingerprintedDirectory;
use super::ImmutableDirectory;
use super::PathAccumulator;
use crate::fs::paths::file_name::FileName;
use crate::fs::paths::file_name::FileNameBuf;
use crate::fs::paths::IntoFileNameBufIterator;

#[derive(Debug, Error)]
pub enum DirectoryInsertError {
    #[error("Path is empty")]
    EmptyPath,

    #[error("Insert conflicts with an existing leaf at path: `{}`", .path)]
    CannotTraverseLeaf { path: PathAccumulator },
}

#[derive(Debug, Error)]
pub enum DirectoryMkdirError {
    #[error("Mkdir conflicts with an existing leaf at path: `{}`", .path)]
    CannotTraverseLeaf { path: PathAccumulator },
}

#[derive(Debug, Error)]
pub enum DirectoryMergeError {
    #[error("Merge conflicts with an existing leaf at path: `{}`", .path)]
    CannotTraverseLeaf { path: PathAccumulator },
}

/// A copy-on-write DirectoryBuilder.
#[derive(Derivative, Allocative)]
#[derivative(Debug(bound = "L: ::std::fmt::Debug"))]
#[derivative(Clone(bound = "L: ::std::clone::Clone"))]
pub enum DirectoryBuilder<L, H>
where
    H: DirectoryDigest,
{
    /// This has a dedicated copy and we can mutate it.
    Mutable(SmallMap<FileNameBuf, DirectoryEntry<DirectoryBuilder<L, H>, L>>),
    Immutable(ImmutableDirectory<L, H>),
}

impl<L, H> DirectoryBuilder<L, H>
where
    H: DirectoryDigest,
{
    pub fn empty() -> Self {
        Self::Mutable(Default::default())
    }

    fn entries(
        &self,
    ) -> impl Iterator<Item = (&'_ FileName, DirectoryEntry<&'_ dyn Directory<L, H>, &'_ L>)> {
        match self {
            Self::Mutable(e) => {
                let it = e.iter().map(|(k, v)| {
                    let k = k.as_ref();
                    let v = v.as_ref().map_dir(|v| v as &dyn Directory<L, H>);
                    (k, v)
                });
                Either::Left(it)
            }
            Self::Immutable(e) => Either::Right(Directory::entries(e)),
        }
    }
}

impl<L, H> DirectoryBuilder<L, H>
where
    L: Clone,
    H: DirectoryDigest,
{
    /// Insert the entry `val` at `path`.
    ///
    /// If this replaces a portion of the tree, Ok(Some) is returned. For example inserting a file
    /// at `a/b` when the tree contains `a/b/c` would return a directory containing `c`, which is
    /// the node that was replaced at `a/b`. No path is returned under those circumstances since
    /// this can only happen at the input path.
    ///
    /// If this would conflict with an existing portion of the tree, Err is returned. This happens
    /// when inserting at a path that traverses through an existing file. For example, inserting at
    /// `a/b/c` when the current directory contains a file at `a/b` will return an error. The error
    /// indicates the path where the conflict occurred.
    pub fn insert(
        &mut self,
        path: impl IntoFileNameBufIterator,
        val: DirectoryEntry<DirectoryBuilder<L, H>, L>,
    ) -> Result<Option<DirectoryEntry<DirectoryBuilder<L, H>, L>>, DirectoryInsertError> {
        let mut path = path.into_iter();

        let path_needle = match path.next() {
            Some(path_needle) => path_needle,
            None => return Err(DirectoryInsertError::EmptyPath),
        };

        self.insert_inner(path_needle, path, val)
            .map_err(|path| DirectoryInsertError::CannotTraverseLeaf { path })
    }

    fn insert_inner(
        &mut self,
        path_needle: FileNameBuf,
        mut path_rest: impl Iterator<Item = FileNameBuf>,
        val: DirectoryEntry<DirectoryBuilder<L, H>, L>,
    ) -> Result<Option<DirectoryEntry<DirectoryBuilder<L, H>, L>>, PathAccumulator> {
        let entries = self.as_mut();

        let next_path_needle = path_rest.next();

        match next_path_needle {
            Some(next_path_needle) => match entries.entry(path_needle) {
                Entry::Occupied(mut entry) => match entry.get_mut() {
                    DirectoryEntry::Dir(d) => d
                        .insert_inner(next_path_needle, path_rest, val)
                        .map_err(|acc| acc.with(entry.key())),
                    _ => Err(PathAccumulator::new(entry.key())),
                },
                Entry::Vacant(entry) => {
                    let mut dir = DirectoryBuilder::empty();
                    dir.insert_inner(next_path_needle, path_rest, val)
                        .map_err(|acc| acc.with(entry.key()))?;
                    entry.insert(DirectoryEntry::Dir(dir));
                    Ok(None)
                }
            },
            None => Ok(entries.insert(path_needle, val)),
        }
    }

    /// Create a directory at path. If the directory already exists, this does nothing. If this
    /// would overwrite a leaf, it fails.
    pub fn mkdir(&mut self, path: impl IntoFileNameBufIterator) -> Result<(), DirectoryMkdirError> {
        let path = path.into_iter();

        self.mkdir_inner(path)
            .map_err(|path| DirectoryMkdirError::CannotTraverseLeaf { path })
    }

    fn mkdir_inner(
        &mut self,
        mut path: impl Iterator<Item = FileNameBuf>,
    ) -> Result<(), PathAccumulator> {
        let entries = self.as_mut();

        let path_needle = match path.next() {
            Some(p) => p,
            None => return Ok(()),
        };

        match entries.entry(path_needle) {
            Entry::Occupied(mut entry) => match entry.get_mut() {
                DirectoryEntry::Dir(d) => {
                    d.mkdir_inner(path).map_err(|acc| acc.with(entry.key()))?
                }
                _ => return Err(PathAccumulator::new(entry.key())),
            },
            Entry::Vacant(entry) => {
                let mut dir = DirectoryBuilder::empty();
                dir.mkdir_inner(path).map_err(|acc| acc.with(entry.key()))?;
                entry.insert(DirectoryEntry::Dir(dir));
            }
        };

        Ok(())
    }

    pub fn merge(&mut self, other: Self) -> Result<(), DirectoryMergeError> {
        self.merge_inner(other)
            .map_err(|path| DirectoryMergeError::CannotTraverseLeaf { path })
    }

    fn merge_inner(&mut self, mut other: Self) -> Result<(), PathAccumulator> {
        match (&self, &other) {
            (Self::Immutable(d1), Self::Immutable(d2)) if d1.fingerprint() == d2.fingerprint() => {
                return Ok(());
            }
            _ => {}
        }

        let other = std::mem::take(other.as_mut());

        let entries = self.as_mut();

        for (k, v) in other.into_iter() {
            match entries.entry(k) {
                Entry::Occupied(mut entry) => match (entry.get_mut(), v) {
                    (DirectoryEntry::Dir(d), DirectoryEntry::Dir(o)) => {
                        d.merge_inner(o).map_err(|e| e.with(entry.key()))?;
                    }
                    (entry, DirectoryEntry::Leaf(o)) => {
                        *entry = DirectoryEntry::Leaf(o);
                    }
                    _ => return Err(PathAccumulator::new(entry.key())),
                },
                Entry::Vacant(entry) => {
                    entry.insert(v);
                }
            }
        }

        Ok(())
    }

    pub(super) fn as_mut(
        &mut self,
    ) -> &mut SmallMap<FileNameBuf, DirectoryEntry<DirectoryBuilder<L, H>, L>> {
        if let Self::Mutable(ref mut dir) = self {
            return dir;
        };

        let entries = match std::mem::replace(self, DirectoryBuilder::Mutable(Default::default())) {
            Self::Immutable(d) => d.into_entries::<SmallMap<_, _>>(),
            Self::Mutable(..) => unreachable!(),
        };

        match self {
            Self::Mutable(ref mut e) => {
                *e = entries;
                e
            }
            Self::Immutable(..) => unreachable!(),
        }
    }
}

impl<L, H> Directory<L, H> for DirectoryBuilder<L, H>
where
    H: DirectoryDigest,
{
    fn entries(&self) -> DirectoryEntries<'_, L, H> {
        Box::new(self.entries())
    }

    fn get<'a>(
        &'a self,
        needle: &'_ FileName,
    ) -> Option<DirectoryEntry<&'a dyn Directory<L, H>, &'a L>> {
        match self {
            Self::Mutable(ref dir) => dir
                .get(needle)
                .map(|v| v.as_ref().map_dir(|d| d as &dyn Directory<L, H>)),
            Self::Immutable(ref dir) => Directory::get(dir, needle),
        }
    }

    fn to_builder(&self) -> DirectoryBuilder<L, H>
    where
        L: Clone,
    {
        self.clone()
    }
}

impl<L, H> DirectoryMut<L, H> for DirectoryBuilder<L, H>
where
    H: DirectoryDigest,
    L: Clone,
{
    fn get_mut<'a>(
        &'a mut self,
        needle: &'_ FileName,
    ) -> Option<DirectoryEntry<&'a mut dyn DirectoryMut<L, H>, &'a mut L>> {
        self.as_mut()
            .get_mut(needle)
            .map(|v| v.as_mut().map_dir(|d| d as &mut dyn DirectoryMut<L, H>))
    }
}

impl<L, H> DirectoryBuilder<L, H>
where
    H: DirectoryDigest,
{
    pub fn fingerprint(self, hasher: &impl DirectoryHasher<L, H>) -> ImmutableDirectory<L, H> {
        match self {
            Self::Mutable(entries) => {
                let entries = entries
                    .into_iter()
                    .map(|(k, v)| (k, v.map_dir(|v| v.fingerprint(hasher))))
                    .collect();
                ImmutableDirectory::Exclusive(ExclusiveDirectory {
                    data: DirectoryData::new(entries, hasher),
                })
            }
            Self::Immutable(c) => c,
        }
    }
}


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

use allocative::Allocative;
use dashmap::mapref::entry::Entry;
use dashmap::DashMap;
use dupe::Clone_;
use dupe::Dupe;
use dupe::Dupe_;

use super::DirectoryDigest;
use super::SharedDirectory;
use super::SharedDirectoryData;
use super::SharedDirectoryInner;

#[derive(Dupe_, Clone_, Allocative)]
pub struct DashMapDirectoryInterner<L, H>
where
    H: DirectoryDigest,
{
    inner: Arc<DashMap<H, Weak<SharedDirectoryInner<L, H>>>>,
}

impl<L, H> DashMapDirectoryInterner<L, H>
where
    H: DirectoryDigest,
{
    pub fn new() -> Self {
        Self {
            inner: Arc::new(DashMap::new()),
        }
    }

    pub fn len(&self) -> usize {
        self.inner.len()
    }

    pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }

    /// Get an existing entry from the interner.
    pub fn get(&self, fingerprint: &H) -> Option<SharedDirectory<L, H>> {
        self.inner
            .get(fingerprint)
            .and_then(|inner| inner.upgrade())
            .map(|inner| SharedDirectory { inner })
    }

    /// Insert a new entry into the interner. This may insert this data, or return anexisitng
    /// entry.
    pub fn intern(&self, data: SharedDirectoryData<L, H>) -> SharedDirectory<L, H> {
        let new_inner = match self.inner.entry(data.fingerprint.dupe()) {
            Entry::Occupied(mut o) => {
                if let Some(inner) = o.get().upgrade() {
                    return SharedDirectory { inner };
                }

                // Constructing this here is a bit duplicative but it avoids creating a new
                // SharedDirectoryInner above just to drop it, and avoids an early return possibly
                // calling dropped() below.
                let new_inner = Arc::new(SharedDirectoryInner {
                    data,
                    interner: self.dupe(),
                });

                o.insert(Arc::downgrade(&new_inner));

                new_inner
            }
            Entry::Vacant(v) => {
                let new_inner = Arc::new(SharedDirectoryInner {
                    data,
                    interner: self.dupe(),
                });

                v.insert(Arc::downgrade(&new_inner));

                new_inner
            }
        };

        SharedDirectory { inner: new_inner }
    }

    /// Notify the interner that an entry has been removed.
    pub fn dropped(&self, data: &SharedDirectoryData<L, H>) {
        // Note: we still check the count here, since you could hypothetically have a race where
        // one SharedDirectory data gets released, but by the time the drop code for the
        // SharedDirectoryInner executes (which calls this), another instance has been created.
        match self.inner.entry(data.fingerprint.dupe()) {
            Entry::Occupied(o) if Weak::strong_count(o.get()) == 0 => {
                o.remove();
            }
            _ => {}
        }
    }
}


/*
 * 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 super::DirectoryBuilder;
use super::DirectoryDigest;
use super::DirectoryEntry;
use super::OrderedDirectoryWalk;
use super::UnorderedDirectoryWalk;
use crate::fs::paths::file_name::FileName;

pub type DirectoryEntries<'a, L, H> =
    Box<dyn Iterator<Item = (&'a FileName, DirectoryEntry<&'a dyn Directory<L, H>, &'a L>)> + 'a>;

/// A Directory that may or may not be fingerprinted. This means it only exposes the common
/// denominator of operations available on such Directories, which is to access entries in them.
pub trait Directory<L, H> {
    fn entries(&self) -> DirectoryEntries<'_, L, H>;

    fn get<'a>(
        &'a self,
        needle: &'_ FileName,
    ) -> Option<DirectoryEntry<&'a dyn Directory<L, H>, &'a L>>;

    fn unordered_walk<'a>(&'a self) -> UnorderedDirectoryWalk<'a, L, H> {
        UnorderedDirectoryWalk::new(self)
    }

    fn ordered_walk<'a>(&'a self) -> OrderedDirectoryWalk<'a, L, H> {
        OrderedDirectoryWalk::new(self)
    }

    fn to_builder(&self) -> DirectoryBuilder<L, H>
    where
        L: Clone,
        H: DirectoryDigest;
}

impl<'a, L, H> fmt::Debug for &'a dyn Directory<L, H> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Directory")
    }
}


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

#![cfg_attr(feature = "gazebo_lint", allow(gazebo_lint_use_dupe))]

use std::marker::PhantomData;

use allocative::Allocative;
use derivative::Derivative;
use derive_more::Display;
use sorted_vector_map::SortedVectorMap;

use super::DirectoryDigest;
use super::DirectoryEntry;
use super::DirectoryHasher;
use super::FingerprintedDirectory;
use crate::fs::paths::file_name::FileNameBuf;

#[derive(Derivative, Display, Allocative)]
#[derivative(Debug(bound = "D: ::std::fmt::Debug, L: ::std::fmt::Debug"))]
#[derivative(Clone(bound = "D: ::std::clone::Clone, L: ::std::clone::Clone"))]
#[display(fmt = "Directory({})", "self.fingerprint")]
pub struct DirectoryData<D, L, H>
where
    H: DirectoryDigest,
{
    /// SortedVectorMap is a more compact immutatable representation for directories.
    /// Experimentally, it takes about 30% less space, while resulting in no runtime regression.
    pub entries: SortedVectorMap<FileNameBuf, DirectoryEntry<D, L>>,

    pub(super) fingerprint: H,

    #[derivative(Debug = "ignore")]
    pub(super) _hash: PhantomData<H>,
}

impl<D, L, H> DirectoryData<D, L, H>
where
    H: DirectoryDigest,
{
    pub fn fingerprint(&self) -> &H {
        &self.fingerprint
    }
}

impl<D, L, H> DirectoryData<D, L, H>
where
    H: DirectoryDigest,
    D: FingerprintedDirectory<L, H>,
{
    pub fn new(
        entries: SortedVectorMap<FileNameBuf, DirectoryEntry<D, L>>,
        hasher: &impl DirectoryHasher<L, H>,
    ) -> Self {
        let fingerprint =
            hasher.hash_entries(entries.iter().map(|(k, e)| (k.as_ref(), e.as_ref())));
        Self {
            entries,
            fingerprint,
            _hash: PhantomData,
        }
    }
}


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

use allocative::Allocative;
use derive_more::Display;
use dupe::Dupe;

use super::DirectoryEntry;
use super::FingerprintedDirectory;
use crate::fs::paths::file_name::FileName;

pub trait DirectoryDigest:
    Allocative + PartialEq + Eq + Hash + Clone + Dupe + Debug + Display
{
}

// TODO: Rename to DirectoryDigester
pub trait DirectoryHasher<L, H> {
    fn hash_entries<'a, D, I>(&self, entries: I) -> H
    where
        I: IntoIterator<Item = (&'a FileName, DirectoryEntry<&'a D, &'a L>)>,
        D: FingerprintedDirectory<L, H> + 'a,
        L: 'a,
        Self: Sized;
}

#[allow(unused)]
#[derive(Clone, Debug, Eq, PartialEq, Hash, Allocative, Display)]
pub struct NoDigest(!);

impl Dupe for NoDigest {}

impl DirectoryDigest for NoDigest {}


/*
 * 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 crate::fs::paths::file_name::FileName;
use crate::fs::paths::forward_rel_path::ForwardRelativePathBuf;

/// A trait shared by iterators on Directories. Unlike a regular Iterator, this returns an accessor
/// to give us the current path in addition to the current item (which borrows from the iterator
/// itself, which is why this cannot be an iterator).
pub trait DirectoryIterator {
    /// The way this iterator will report its current path.
    type PathStack: DirectoryIteratorPathStack;

    /// The items this iterator will yield.
    type Item;

    /// Provide the next item.
    fn next<'a>(
        &'a mut self,
    ) -> Option<(
        DirectoryIteratorPathAccessor<'a, Self::PathStack>,
        Self::Item,
    )>;

    /// Compute all paths in this iterator. This returns a regular Iterator since we no longer
    /// need to borrow from self in next.
    fn with_paths(self) -> DirectoryIteratorWithPaths<Self>
    where
        Self: Sized,
    {
        DirectoryIteratorWithPaths { inner: self }
    }

    /// Compute none of the paths in this iterator. Here again, this is a reglar Iteraotr.
    fn without_paths(self) -> DirectoryIteratorWithoutPaths<Self>
    where
        Self: Sized,
    {
        DirectoryIteratorWithoutPaths { inner: self }
    }
}

/// The stack of paths for this DirectoryIterator. This must allow iterating over the path
/// components htat make up the DirectoryIterator's current location.
pub trait DirectoryIteratorPathStack {
    // NOTE: Ideally we'd want this to return an iterator defined as an associated type, but that's
    // annoying to spell out without using type Foo = impl ... This is available behind
    // `type_alias_impl_trait`, but that causes lots of compiler crashes at this time.
    fn for_each_path<'a, F>(&'a self, f: F)
    where
        F: FnMut(&'a FileName);
}

/// A thin struct that can be used to produce a path on demand.
pub struct DirectoryIteratorPathAccessor<'a, T> {
    pub(super) stack: &'a T,
    pub(super) leaf: Option<&'a FileName>,
}

impl<'a, T> DirectoryIteratorPathAccessor<'a, T>
where
    T: DirectoryIteratorPathStack,
{
    fn for_each_path<'this, F>(&'this self, mut f: F)
    where
        F: FnMut(&'this FileName),
    {
        self.stack.for_each_path(&mut f);
        if let Some(leaf) = self.leaf {
            f(leaf);
        }
    }

    pub fn name(&self) -> Option<&'a FileName> {
        self.leaf
    }

    pub fn get(&self) -> ForwardRelativePathBuf {
        // Evaluate the size of our path.
        let mut size = 0;
        self.for_each_path(|name| size += name.as_str().len() + 1);

        // Remove extra "/" we accounted for.
        size = size.saturating_sub(1);

        // Produce it.
        let mut first = true;
        let mut path = String::with_capacity(size);
        self.for_each_path(|name| {
            if !first {
                path.push('/');
            }
            first = false;
            path.push_str(name.as_str());
        });

        #[cfg(test)]
        {
            assert_eq!(
                size,
                path.len(),
                "reserved the wrong capacity ({}) for {:?}",
                size,
                path
            );
        }

        // Concatenating FileName guarantees we get a ForwardRelativePathBuf.
        ForwardRelativePathBuf::unchecked_new(path)
    }
}

impl<'a, T> fmt::Display for DirectoryIteratorPathAccessor<'a, T>
where
    T: DirectoryIteratorPathStack,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.get())
    }
}

impl<'a, T> fmt::Debug for DirectoryIteratorPathAccessor<'a, T>
where
    T: DirectoryIteratorPathStack,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "DirectoryIteratorPathAccessor({})", self.get())
    }
}

/// Iterate over a DirectoryIterator with the paths.
pub struct DirectoryIteratorWithPaths<T> {
    inner: T,
}

impl<T> Iterator for DirectoryIteratorWithPaths<T>
where
    T: DirectoryIterator,
{
    type Item = (ForwardRelativePathBuf, <T as DirectoryIterator>::Item);

    fn next(&mut self) -> Option<Self::Item> {
        let (path, item) = self.inner.next()?;
        let path = path.get();
        Some((path, item))
    }
}

/// Iterate over a DirectoryIterator without the paths.
pub struct DirectoryIteratorWithoutPaths<T> {
    inner: T,
}

impl<T> Iterator for DirectoryIteratorWithoutPaths<T>
where
    T: DirectoryIterator,
{
    type Item = <T as DirectoryIterator>::Item;

    fn next(&mut self) -> Option<Self::Item> {
        let (_, item) = self.inner.next()?;
        Some(item)
    }
}