/*
 * 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::env;
use std::path::Path;
use std::path::PathBuf;

use crate::fs::fs_util;
use crate::fs::paths::abs_norm_path::AbsNormPath;
use crate::fs::paths::abs_norm_path::AbsNormPathBuf;
use crate::fs::paths::abs_path::AbsPathBuf;

/// Client working directory.
///
/// Can be different from process working directory if process changes the directory.
/// So relative paths should be resolved against this.
#[derive(Clone, Debug, derive_more::Display)]
#[display(fmt = "{}", path)]
pub struct WorkingDir {
    path: AbsNormPathBuf,
}

impl WorkingDir {
    pub fn unchecked_new(path: AbsNormPathBuf) -> WorkingDir {
        WorkingDir { path }
    }

    pub fn current_dir() -> anyhow::Result<WorkingDir> {
        let current_dir = env::current_dir()?;

        #[derive(Debug, thiserror::Error)]
        enum CurrentDirError {
            #[error("std::env::current_dir returns non-canonical path: `{0}` -> `{1}`")]
            NotCanonical(PathBuf, PathBuf),
        }

        // `current_dir` seems to return canonical path everywhere except Windows,
        // but may return non-canonical path on Windows:
        // https://fb.workplace.com/groups/buck2windows/posts/754618429743405
        let current_dir_canonical = fs_util::canonicalize(&current_dir)?;

        if current_dir != current_dir_canonical.as_path() {
            if !cfg!(windows) {
                return Err(CurrentDirError::NotCanonical(
                    current_dir,
                    current_dir_canonical.into_path_buf(),
                )
                .into());
            }
        }

        Ok(WorkingDir::unchecked_new(current_dir_canonical))
    }

    pub fn resolve(&self, path: &Path) -> AbsPathBuf {
        self.path.as_abs_path().join(path)
    }

    pub fn path(&self) -> &AbsNormPath {
        &self.path
    }

    pub fn into_abs_norm_path_buf(self) -> AbsNormPathBuf {
        self.path
    }
}


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

use std::borrow::Borrow;
use std::borrow::Cow;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::ops::Deref;
use std::path::Path;
use std::path::PathBuf;

use allocative::Allocative;
use derive_more::Display;
use ref_cast::RefCast;
use relative_path::RelativePath;
use serde::de::Error;
use serde::Deserialize;
use serde::Serialize;
use thiserror::Error;

use crate::fs::paths::abs_path::AbsPath;
use crate::fs::paths::abs_path::AbsPathBuf;
use crate::fs::paths::forward_rel_path::ForwardRelativePath;
use crate::fs::paths::forward_rel_path::ForwardRelativePathNormalizer;

/// An absolute path. This path is not platform agnostic.
///
/// The path is normalized:
/// * it is absolute
/// * not dot in path
/// * no dot-dot in path
/// * TODO(nga): normalize slashes on Windows
/// * TODO(nga): validate UTF-8
/// * the path is **not** canonicalized
#[derive(Display, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
#[display(fmt = "{}", "_0.display()")]
#[repr(transparent)]
pub struct AbsNormPath(AbsPath);

/// The owned version of [`AbsNormPath`].
#[derive(
    Clone, Display, Debug, Hash, PartialEq, Eq, Ord, PartialOrd, Allocative
)]
#[display(fmt = "{}", "_0.display()")]
pub struct AbsNormPathBuf(AbsPathBuf);

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

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

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

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

impl Deref for AbsNormPath {
    type Target = AbsPath;

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

impl Serialize for AbsNormPathBuf {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.0.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for AbsNormPathBuf {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        AbsNormPathBuf::new(PathBuf::deserialize(deserializer)?).map_err(D::Error::custom)
    }
}

impl AbsNormPath {
    /// Creates an 'AbsPath' if the given path represents an absolute path,
    /// otherwise error.
    ///
    /// ```
    /// # use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
    ///
    /// assert!(AbsNormPath::new("foo/bar").is_err());
    /// if cfg!(windows) {
    ///    assert!(AbsNormPath::new("C:\\foo\\bar").is_ok());
    /// } else {
    ///    assert!(AbsNormPath::new("/foo/bar").is_ok());
    /// }
    /// ```
    pub fn new<P: ?Sized + AsRef<Path>>(p: &P) -> anyhow::Result<&AbsNormPath> {
        let path = AbsPath::new(p.as_ref())?;
        verify_abs_path(path)?;
        Ok(AbsNormPath::ref_cast(path))
    }

    /// Creates an owned 'AbsPathBuf' with path adjoined to self.
    ///
    /// ```
    /// use std::path::Path;
    /// use buck2_core::fs::paths::abs_norm_path::{AbsNormPath, AbsNormPathBuf};
    /// use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
    ///
    /// if cfg!(not(windows)) {
    ///     let abs_path = AbsNormPath::new("/my")?;
    ///     assert_eq!(AbsNormPathBuf::from("/my/foo/bar".into())?, abs_path.join(ForwardRelativePath::new("foo/bar")?));
    /// } else {
    ///     let abs_path = AbsNormPath::new("C:\\my")?;
    ///     assert_eq!("C:\\my\\foo\\bar", abs_path.join(ForwardRelativePath::new("foo/bar")?).to_string());
    /// }
    /// # anyhow::Ok(())
    /// ```
    #[allow(clippy::collapsible_else_if)]
    pub fn join<P: AsRef<ForwardRelativePath>>(&self, path: P) -> AbsNormPathBuf {
        let path = path.as_ref();
        if path.is_empty() {
            self.to_buf()
        } else {
            if cfg!(windows) {
                AbsNormPathBuf(self.0.join(path.as_str().replace('/', "\\")))
            } else {
                AbsNormPathBuf(self.0.join(path.as_str()))
            }
        }
    }

    /// Returns a relative path of the parent directory
    ///
    /// ```
    /// use std::path::Path;
    /// use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
    ///
    /// if cfg!(not(windows)) {
    ///     assert_eq!(
    ///         Some(AbsNormPath::new("/")?),
    ///         AbsNormPath::new("/my")?.parent()
    ///     );
    ///     assert_eq!(
    ///         None,
    ///         AbsNormPath::new("/")?.parent()
    ///     );
    /// } else {
    ///     assert_eq!(
    ///         Some(AbsNormPath::new("c:/")?),
    ///         AbsNormPath::new("c:/my")?.parent()
    ///     );
    ///     assert_eq!(
    ///         None,
    ///         AbsNormPath::new("c:/")?.parent()
    ///     );
    /// }
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn parent(&self) -> Option<&AbsNormPath> {
        self.0.parent().map(AbsNormPath::ref_cast)
    }

    /// 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 std::{borrow::Cow, path::Path};
    /// use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
    /// use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
    ///
    /// if cfg!(not(windows)) {
    ///     let path = AbsNormPath::new("/test/foo/bar.txt")?;
    ///
    ///     assert_eq!(
    ///         path.strip_prefix(AbsNormPath::new("/test")?)?,
    ///         Cow::Borrowed(ForwardRelativePath::new("foo/bar.txt")?)
    ///     );
    ///     assert!(path.strip_prefix(AbsNormPath::new("/asdf")?).is_err());
    /// } else {
    ///     let path = AbsNormPath::new(r"C:\test\foo\bar.txt")?;
    ///
    ///     // strip_prefix will return Cow::Owned here but we still
    ///     // can compare it to Cow::Borrowed.
    ///     assert_eq!(
    ///         path.strip_prefix(AbsNormPath::new("c:/test")?)?,
    ///         Cow::Borrowed(ForwardRelativePath::new("foo/bar.txt")?)
    ///     );
    ///     assert_eq!(
    ///         path.strip_prefix(AbsNormPath::new(r"c:\test")?)?,
    ///         Cow::Borrowed(ForwardRelativePath::new("foo/bar.txt")?)
    ///     );
    ///     assert_eq!(
    ///         path.strip_prefix(AbsNormPath::new(r"\\?\c:\test")?)?,
    ///         Cow::Borrowed(ForwardRelativePath::new("foo/bar.txt")?)
    ///     );
    ///     assert!(path.strip_prefix(AbsNormPath::new("c:/asdf")?).is_err());
    ///
    ///     let shared_path = AbsNormPath::new(r"\\server\share\foo\bar.txt")?;
    ///     assert_eq!(
    ///         shared_path.strip_prefix(AbsNormPath::new(r"\\server\share\")?)?,
    ///         Cow::Borrowed(ForwardRelativePath::new("foo/bar.txt")?)
    ///     );
    ///     assert_eq!(
    ///         shared_path.strip_prefix(AbsNormPath::new(r"\\server\share\foo")?)?,
    ///         Cow::Borrowed(ForwardRelativePath::new("bar.txt")?)
    ///     );
    ///     assert_eq!(
    ///         shared_path.strip_prefix(AbsNormPath::new(r"\\?\UNC\server\share\foo")?)?,
    ///         Cow::Borrowed(ForwardRelativePath::new("bar.txt")?)
    ///     );
    ///     assert!(shared_path.strip_prefix(AbsNormPath::new(r"\\server\share2\foo")?).is_err());
    ///     assert!(shared_path.strip_prefix(AbsNormPath::new(r"\\server\share\fo")?).is_err());
    /// }
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn strip_prefix<P: AsRef<AbsNormPath>>(
        &self,
        base: P,
    ) -> anyhow::Result<Cow<ForwardRelativePath>> {
        let stripped_path = self.strip_prefix_impl(base.as_ref())?;
        ForwardRelativePathNormalizer::normalize_path(stripped_path)
    }

    #[cfg(not(windows))]
    fn strip_prefix_impl(&self, base: &AbsNormPath) -> anyhow::Result<&Path> {
        self.0.strip_prefix(&base.0).map_err(anyhow::Error::from)
    }

    #[cfg(windows)]
    fn strip_prefix_impl(&self, base: &AbsNormPath) -> anyhow::Result<&Path> {
        if self.windows_prefix()? == base.windows_prefix()? {
            self.strip_windows_prefix()?
                .strip_prefix(base.strip_windows_prefix()?)
                .map_err(anyhow::Error::from)
        } else {
            Err(anyhow::anyhow!("Path is not a prefix"))
        }
    }

    /// Determines whether `base` is a prefix of `self`.
    ///
    /// ```
    /// use std::path::Path;
    /// use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
    ///
    /// if cfg!(not(windows)) {
    ///     let abs_path = AbsNormPath::new("/some/foo")?;
    ///     assert!(abs_path.starts_with(AbsNormPath::new("/some")?));
    ///     assert!(!abs_path.starts_with(AbsNormPath::new("/som")?));
    /// } else {
    ///     let abs_path = AbsNormPath::new("c:/some/foo")?;
    ///     assert!(abs_path.starts_with(AbsNormPath::new("c:/some")?));
    ///     assert!(!abs_path.starts_with(AbsNormPath::new("c:/som")?));
    ///     assert!(abs_path.starts_with(AbsNormPath::new(r"\\?\C:\some")?));
    ///
    ///     let shared_path = AbsNormPath::new(r"\\server\share\foo\bar.txt")?;
    ///     assert!(shared_path.starts_with(AbsNormPath::new(r"\\server\share\")?));
    ///     assert!(shared_path.starts_with(AbsNormPath::new(r"\\server\share\foo")?));
    ///     assert!(shared_path.starts_with(AbsNormPath::new(r"\\?\UNC\server\share\foo")?));
    ///     assert!(!shared_path.starts_with(AbsNormPath::new(r"\\server\share2\foo")?));
    ///     assert!(!shared_path.starts_with(AbsNormPath::new(r"\\server\share\fo")?));
    /// }
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn starts_with<P: AsRef<AbsNormPath>>(&self, base: P) -> bool {
        self.starts_with_impl(base.as_ref())
    }

    #[cfg(not(windows))]
    fn starts_with_impl(&self, base: &AbsNormPath) -> bool {
        self.0.starts_with(&base.0)
    }

    #[cfg(windows)]
    fn starts_with_impl(&self, base: &AbsNormPath) -> bool {
        let prefix = self.windows_prefix();
        let base_prefix = base.windows_prefix();
        if let (Ok(prefix), Ok(base_prefix)) = (prefix, base_prefix) {
            if prefix == base_prefix {
                let stripped = self.strip_windows_prefix();
                let base_stripped = base.strip_windows_prefix();
                if let (Ok(stripped), Ok(base_stripped)) = (stripped, base_stripped) {
                    return stripped.starts_with(base_stripped);
                }
            }
        }
        false
    }

    /// Determines whether `child` is a suffix of `self`.
    /// Only considers whole path components to match.
    ///
    /// ```
    /// use std::path::Path;
    /// use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
    ///
    /// if cfg!(not(windows)) {
    ///     let abs_path = AbsNormPath::new("/some/foo")?;
    ///     assert!(abs_path.ends_with("foo"));
    /// } else {
    ///     let abs_path = AbsNormPath::new("c:/some/foo")?;
    ///     assert!(abs_path.ends_with("foo"));
    /// }
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn ends_with<P: AsRef<Path>>(&self, child: P) -> bool {
        self.0.ends_with(child.as_ref())
    }

    /// Build an owned `AbsPathBuf`, joined with the given path and normalized.
    ///
    /// ```
    /// use buck2_core::fs::paths::abs_norm_path::{AbsNormPath, AbsNormPathBuf};
    ///
    /// if cfg!(not(windows)) {
    ///     assert_eq!(
    ///         AbsNormPathBuf::from("/foo/baz.txt".into())?,
    ///         AbsNormPath::new("/foo/bar")?.join_normalized("../baz.txt")?
    ///     );
    ///
    ///     assert_eq!(
    ///         AbsNormPath::new("/foo")?.join_normalized("../../baz.txt").is_err(),
    ///         true
    ///     );
    /// } else {
    ///     assert_eq!(
    ///         AbsNormPathBuf::from("c:/foo/baz.txt".into())?,
    ///         AbsNormPath::new("c:/foo/bar")?.join_normalized("../baz.txt")?
    ///     );
    ///
    ///     assert_eq!(
    ///         AbsNormPath::new("c:/foo")?.join_normalized("../../baz.txt").is_err(),
    ///         true
    ///     );
    /// }
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn join_normalized<P: AsRef<RelativePath>>(
        &self,
        path: P,
    ) -> anyhow::Result<AbsNormPathBuf> {
        let mut stack = Vec::new();
        for c in self
            .0
            .components()
            .chain(path.as_ref().components().map(|c| match c {
                relative_path::Component::Normal(s) => std::path::Component::Normal(OsStr::new(s)),
                relative_path::Component::CurDir => std::path::Component::CurDir,
                relative_path::Component::ParentDir => std::path::Component::ParentDir,
            }))
        {
            match c {
                std::path::Component::Normal(_) => stack.push(c),
                std::path::Component::Prefix(_) => stack.push(c),
                std::path::Component::RootDir => stack.push(c),
                std::path::Component::CurDir => {}
                std::path::Component::ParentDir => {
                    if stack.pop().is_none() {
                        return Err(anyhow::anyhow!(PathNormalizationError::OutOfBounds(
                            self.as_os_str().into(),
                            path.as_ref().as_str().into(),
                        )));
                    }
                }
            }
        }
        let path_buf = stack.iter().collect::<PathBuf>();

        AbsNormPathBuf::try_from(path_buf)
    }

    /// Convert to an owned [`AbsNormPathBuf`].
    pub fn to_buf(&self) -> AbsNormPathBuf {
        self.to_owned()
    }

    #[cfg(windows)]
    /// Get Windows path prefix which is either disk drive letter, device or UNC name.
    ///
    /// ```
    /// use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
    ///
    /// assert_eq!("D", AbsNormPath::new("d:/foo/bar")?.windows_prefix()?);
    /// assert_eq!("D", AbsNormPath::new(r"D:\foo\bar")?.windows_prefix()?);
    /// assert_eq!("E", AbsNormPath::new(r"\\?\E:\foo\bar")?.windows_prefix()?);
    /// assert_eq!("server\\share", AbsNormPath::new(r"\\server\share")?.windows_prefix()?);
    /// assert_eq!("server\\share", AbsNormPath::new(r"\\server\share\foo\bar")?.windows_prefix()?);
    /// assert_eq!("server\\share", AbsNormPath::new(r"\\?\UNC\server\share")?.windows_prefix()?);
    /// assert_eq!("COM42", AbsNormPath::new(r"\\.\COM42")?.windows_prefix()?);
    /// assert_eq!("COM42", AbsNormPath::new(r"\\.\COM42\foo\bar")?.windows_prefix()?);
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn windows_prefix(&self) -> anyhow::Result<OsString> {
        use std::os::windows::ffi::OsStringExt;
        use std::path::Prefix;

        match self
            .0
            .components()
            .next()
            .ok_or_else(|| anyhow::anyhow!("AbsPath is empty."))?
        {
            std::path::Component::Prefix(prefix_component) => match prefix_component.kind() {
                Prefix::Disk(disk) | Prefix::VerbatimDisk(disk) => {
                    Ok(OsString::from_wide(&[disk.into()]))
                }
                Prefix::UNC(server, share) | Prefix::VerbatimUNC(server, share) => {
                    let mut server = server.to_owned();
                    server.push("\\");
                    server.push(share);
                    Ok(server)
                }
                Prefix::DeviceNS(device) => Ok(device.to_owned()),
                prefix => Err(anyhow::anyhow!("Unknown prefix kind: {:?}.", prefix)),
            },
            _ => Err(anyhow::anyhow!("AbsPath doesn't have prefix.")),
        }
    }

    #[cfg(windows)]
    /// Strip Windows path prefix which is either disk drive letter, device or UNC name.
    ///
    /// ```
    /// use std::path::Path;
    /// use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
    ///
    /// assert_eq!(Path::new(""), AbsNormPath::new("C:/")?.strip_windows_prefix()?);
    /// assert_eq!(Path::new(""), AbsNormPath::new("C:\\")?.strip_windows_prefix()?);
    /// assert_eq!(Path::new("foo/bar"), AbsNormPath::new("d:/foo/bar")?.strip_windows_prefix()?);
    /// assert_eq!(Path::new("foo\\bar"), AbsNormPath::new(r"D:\foo\bar")?.strip_windows_prefix()?);
    /// assert_eq!(Path::new("foo\\bar"), AbsNormPath::new(r"\\?\D:\foo\bar")?.strip_windows_prefix()?);
    /// assert_eq!(Path::new("path"), AbsNormPath::new(r"\\server\share\path")?.strip_windows_prefix()?);
    /// assert_eq!(Path::new("path"), AbsNormPath::new(r"\\?\UNC\server\share\path")?.strip_windows_prefix()?);
    /// assert_eq!(Path::new("abc"), AbsNormPath::new(r"\\.\COM42\abc")?.strip_windows_prefix()?);
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn strip_windows_prefix(&self) -> anyhow::Result<&Path> {
        let mut iter = self.0.iter();
        let prefix = iter
            .next()
            .ok_or_else(|| anyhow::anyhow!("AbsPath is empty."))?;
        let mut prefix = prefix.to_owned();
        // Strip leading path separator as well.
        if let Some(component) = iter.next() {
            prefix.push(component);
        }
        Ok(self.as_path().strip_prefix(&prefix)?)
    }

    pub fn as_path(&self) -> &Path {
        self.0.as_path()
    }

    pub fn as_abs_path(&self) -> &AbsPath {
        &self.0
    }
}

impl AbsNormPathBuf {
    pub fn new(path: PathBuf) -> anyhow::Result<AbsNormPathBuf> {
        let path = AbsPathBuf::try_from(path)?;
        verify_abs_path(&path)?;
        Ok(AbsNormPathBuf(path))
    }

    pub fn unchecked_new(path: PathBuf) -> Self {
        AbsNormPathBuf(AbsPathBuf::try_from(path).unwrap())
    }

    pub fn into_path_buf(self) -> PathBuf {
        self.0.into_path_buf()
    }

    pub fn into_abs_path_buf(self) -> AbsPathBuf {
        self.0
    }

    pub fn from(s: String) -> anyhow::Result<Self> {
        AbsNormPathBuf::try_from(s)
    }

    /// Creates a new 'AbsPathBuf' with a given capacity used to create the internal
    /// 'String'. See 'with_capacity' defined on 'PathBuf'
    pub fn with_capacity<P: AsRef<AbsNormPath>>(cap: usize, base: P) -> Self {
        let mut path = PathBuf::with_capacity(cap);
        path.push(base.as_ref());
        AbsNormPathBuf(AbsPathBuf::try_from(path).unwrap())
    }

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

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

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

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

    /// Pushes a `ForwardRelativePath` to the existing buffer
    /// ```
    ///
    /// use std::path::PathBuf;
    /// use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
    /// use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
    ///
    /// let prefix = if cfg!(windows) {
    ///    "C:"
    /// } else {
    ///   ""
    /// };
    ///
    /// let mut path = AbsNormPathBuf::try_from(format!("{prefix}/foo")).unwrap();
    /// path.push(ForwardRelativePath::unchecked_new("bar"));
    ///
    /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo/bar")).unwrap(), path);
    ///
    /// path.push(ForwardRelativePath::unchecked_new("more/file.rs"));
    /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo/bar/more/file.rs")).unwrap(), path);
    /// ```
    pub fn push<P: AsRef<ForwardRelativePath>>(&mut self, path: P) {
        if cfg!(windows) {
            self.0.push(path.as_ref().as_str().replace('/', "\\"))
        } else {
            self.0.push(path.as_ref().as_str())
        }
    }

    /// Pushes a `RelativePath` to the existing buffer, normalizing it.
    /// Note that this does not visit the filesystem to resolve `..`s. Instead, it cancels out the
    /// components directly, similar to `join_normalized`.
    /// ```
    ///
    /// use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
    /// use buck2_core::fs::paths::RelativePath;
    ///
    /// let prefix = if cfg!(windows) {
    ///   "C:"
    /// } else {
    ///  ""
    /// };
    ///
    /// let mut path = AbsNormPathBuf::try_from(format!("{prefix}/foo")).unwrap();
    /// path.push_normalized(RelativePath::new("bar"))?;
    ///
    /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo/bar")).unwrap(), path);
    ///
    /// path.push_normalized(RelativePath::new("more/file.rs"))?;
    /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo/bar/more/file.rs")).unwrap(), path);
    ///
    /// path.push_normalized(RelativePath::new("../other.rs"))?;
    /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo/bar/more/other.rs")).unwrap(), path);
    ///
    /// path.push_normalized(RelativePath::new(".."))?;
    /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo/bar/more")).unwrap(), path);
    ///
    /// path.push_normalized(RelativePath::new("../.."))?;
    /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo")).unwrap(), path);
    ///
    /// path.push_normalized(RelativePath::new(".."))?;
    /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/")).unwrap(), path);
    ///
    /// assert!(path.push_normalized(RelativePath::new("..")).is_err());
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn push_normalized<P: AsRef<RelativePath>>(&mut self, path: P) -> anyhow::Result<()> {
        for c in path.as_ref().components() {
            match c {
                relative_path::Component::Normal(s) => {
                    self.0.push(s);
                }
                relative_path::Component::CurDir => {}
                relative_path::Component::ParentDir => {
                    if !self.0.pop() {
                        return Err(anyhow::anyhow!(PathNormalizationError::OutOfBounds(
                            self.as_os_str().into(),
                            path.as_ref().as_str().into(),
                        )));
                    }
                }
            }
        }

        Ok(())
    }

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

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

    /// no allocation conversion
    ///
    /// ```
    ///
    /// use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
    /// use std::convert::TryFrom;
    ///
    /// assert!(AbsNormPathBuf::try_from("relative/bar".to_owned()).is_err());
    ///
    /// if cfg!(not(windows)) {
    ///     assert!(AbsNormPathBuf::try_from("/foo/bar".to_owned()).is_ok());
    ///     assert!(AbsNormPathBuf::try_from("/".to_owned()).is_ok());
    ///     assert!(AbsNormPathBuf::try_from("/normalize/./bar".to_owned()).is_err());
    ///     assert!(AbsNormPathBuf::try_from("/normalize/../bar".to_owned()).is_err());
    /// } else {
    ///     assert!(AbsNormPathBuf::try_from("c:/foo/bar".to_owned()).is_ok());
    ///     assert!(AbsNormPathBuf::try_from("c:/".to_owned()).is_ok());
    ///     assert!(AbsNormPathBuf::try_from("c:/normalize/./bar".to_owned()).is_err());
    ///     assert!(AbsNormPathBuf::try_from("c:/normalize/../bar".to_owned()).is_err());
    /// }
    /// ```
    fn try_from(s: String) -> anyhow::Result<AbsNormPathBuf> {
        AbsNormPathBuf::try_from(OsString::from(s))
    }
}

impl TryFrom<OsString> for AbsNormPathBuf {
    type Error = anyhow::Error;

    // no allocation
    fn try_from(s: OsString) -> anyhow::Result<AbsNormPathBuf> {
        AbsNormPathBuf::try_from(PathBuf::from(s))
    }
}

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

    /// no allocation conversion
    ///
    /// ```
    ///
    /// use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
    /// use std::convert::TryFrom;
    /// use std::path::PathBuf;
    ///
    /// assert!(AbsNormPathBuf::try_from(PathBuf::from("relative/bar")).is_err());
    ///
    /// if cfg!(not(windows)) {
    ///     assert!(AbsNormPathBuf::try_from(PathBuf::from("/foo/bar")).is_ok());
    ///     assert!(AbsNormPathBuf::try_from(PathBuf::from("/")).is_ok());
    ///     assert!(AbsNormPathBuf::try_from(PathBuf::from("/normalize/./bar")).is_err());
    ///     assert!(AbsNormPathBuf::try_from(PathBuf::from("/normalize/../bar")).is_err());
    /// } else {
    ///     assert!(AbsNormPathBuf::try_from(PathBuf::from("c:/foo/bar")).is_ok());
    ///     assert!(AbsNormPathBuf::try_from(PathBuf::from("c:/")).is_ok());
    ///     assert!(AbsNormPathBuf::try_from(PathBuf::from("c:/normalize/./bar")).is_err());
    ///     assert!(AbsNormPathBuf::try_from(PathBuf::from("c:/normalize/../bar")).is_err());
    /// }
    /// ```
    fn try_from(p: PathBuf) -> anyhow::Result<AbsNormPathBuf> {
        let p = AbsPathBuf::try_from(p)?;
        verify_abs_path(&p)?;
        Ok(AbsNormPathBuf(p))
    }
}

impl ToOwned for AbsNormPath {
    type Owned = AbsNormPathBuf;

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

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

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

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

impl Deref for AbsNormPathBuf {
    type Target = AbsNormPath;

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

// Separate function so windows path verification can be tested on Unix.
fn verify_abs_path_windows_part(path: &str) -> bool {
    // UNC device path.
    // TODO(nga): behavior of UNC paths is under-specified in `AbsPath`.
    let path = path.strip_prefix("\\\\.\\").unwrap_or(path);

    for component in path.split(|c| c == '/' || c == '\\') {
        if component == "." || component == ".." {
            return false;
        }
    }

    true
}

/// Verifier for AbsPath to ensure the path is absolute
fn verify_abs_path(path: &AbsPath) -> anyhow::Result<()> {
    // `Path::components` normalizes '.'s away so we cannot iterate with it.
    // TODO maybe we actually want to allow "."s and just
    //   normalize them away entirely.

    #[cfg(unix)]
    {
        use std::os::unix::ffi::OsStrExt;

        // `as_bytes` function is not available on Windows.
        // This is equivalent but faster to generic code below.
        for component in path.as_os_str().as_bytes().split(|c| *c == b'/') {
            if component == b"." || component == b".." {
                return Err(anyhow::anyhow!(AbsNormPathError::PathNotNormalized(
                    path.to_owned()
                )));
            }
        }
    }

    if !cfg!(unix) {
        let path_str = path.to_string_lossy();
        if !verify_abs_path_windows_part(&path_str) {
            return Err(anyhow::anyhow!(AbsNormPathError::PathNotNormalized(
                path.to_owned()
            )));
        }
    }

    Ok(())
}

/// Errors from 'AbsPath' creation
#[derive(Error, Debug)]
enum AbsNormPathError {
    #[error("expected a normalized path, but found a non-normalized path instead: `{0}`")]
    PathNotNormalized(AbsPathBuf),
}

/// Errors from normalizing paths
#[derive(Error, Debug)]
enum PathNormalizationError {
    #[error(
        "no such path: normalizing `{}` requires the parent directory of the root of `{}`",
        .1.to_string_lossy(),
        .0.to_string_lossy()
    )]
    OutOfBounds(OsString, OsString),
}

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

    use crate::fs::paths::abs_norm_path::verify_abs_path_windows_part;
    use crate::fs::paths::abs_norm_path::AbsNormPath;
    use crate::fs::paths::abs_norm_path::AbsNormPathBuf;
    use crate::fs::paths::forward_rel_path::ForwardRelativePath;

    #[cfg(not(windows))]
    fn make_absolute(s: &str) -> String {
        s.to_owned()
    }

    #[cfg(windows)]
    fn make_absolute(s: &str) -> String {
        let mut abs_path = "c:".to_owned();
        abs_path.push_str(s);
        abs_path
    }

    #[test]
    fn abs_paths_work_in_maps() -> anyhow::Result<()> {
        let mut map = HashMap::new();
        let foo_string = make_absolute("/foo");
        let bar_string = make_absolute("/bar");

        let p1 = AbsNormPath::new(foo_string.as_str())?;
        let p2 = AbsNormPath::new(bar_string.as_str())?;

        map.insert(p1.to_buf(), p2.to_buf());

        assert_eq!(Some(p2), map.get(p1).map(|p| p.as_ref()));

        Ok(())
    }

    #[test]
    fn abs_path_is_comparable() -> anyhow::Result<()> {
        let foo_string = make_absolute("/foo");
        let bar_string = make_absolute("/bar");
        let path1_buf = AbsNormPathBuf::from(foo_string.clone())?;
        let path2_buf = AbsNormPathBuf::from(foo_string.clone())?;
        let path3_buf = AbsNormPathBuf::from(bar_string.clone())?;

        let path1 = AbsNormPath::new(foo_string.as_str())?;
        let path2 = AbsNormPath::new(foo_string.as_str())?;
        let path3 = AbsNormPath::new(bar_string.as_str())?;

        let str2 = foo_string.as_str();
        let str3 = bar_string.as_str();
        let str_not_abs = "ble";

        let string_not_abs = "ble".to_owned();

        assert_eq!(path1_buf, path2_buf);
        assert_ne!(path1_buf, path3_buf);

        assert_eq!(path1, path2);
        assert_ne!(path1, path3);

        assert_eq!(path1_buf, path2);
        assert_ne!(path1, path3_buf);

        assert_eq!(path1_buf, str2);
        assert_ne!(path1_buf, str3);
        assert_ne!(path1_buf, str_not_abs);

        assert_eq!(path1, str2);
        assert_ne!(path1, str3);
        assert_ne!(path1, str_not_abs);

        assert_eq!(path1_buf, foo_string);
        assert_ne!(path1_buf, bar_string);
        assert_ne!(path1_buf, string_not_abs);

        assert_eq!(path1, foo_string);
        assert_ne!(path1, bar_string);
        assert_ne!(path1, string_not_abs);

        Ok(())
    }

    #[test]
    fn test_verify() {
        assert!(AbsNormPath::new("relative/bar").is_err());
        assert!(AbsNormPath::new(Path::new("relative/bar")).is_err());

        if cfg!(not(windows)) {
            assert!(AbsNormPath::new("/foo/bar").is_ok());
            assert!(AbsNormPath::new("/").is_ok());
            assert!(AbsNormPath::new("/normalize/./bar").is_err());
            assert!(AbsNormPath::new("/normalize/../bar").is_err());

            assert!(AbsNormPath::new(Path::new("/foo/bar")).is_ok());
            assert!(AbsNormPath::new(Path::new("/")).is_ok());
            assert!(AbsNormPath::new(Path::new("/normalize/./bar")).is_err());
            assert!(AbsNormPath::new(Path::new("/normalize/../bar")).is_err());
        } else {
            assert!(AbsNormPath::new("c:/foo/bar").is_ok());
            assert!(AbsNormPath::new("c:/").is_ok());
            assert!(AbsNormPath::new("c:/normalize/./bar").is_err());
            assert!(AbsNormPath::new("c:/normalize/../bar").is_err());
            assert!(AbsNormPath::new("c:\\normalize\\.\\bar").is_err());
            assert!(AbsNormPath::new("c:\\normalize\\..\\bar").is_err());
            assert!(AbsNormPath::new("/foo/bar").is_err());

            assert!(AbsNormPath::new(Path::new("c:/foo/bar")).is_ok());
            assert!(AbsNormPath::new(Path::new("c:/")).is_ok());
            assert!(AbsNormPath::new(Path::new("c:/normalize/./bar")).is_err());
            assert!(AbsNormPath::new(Path::new("c:/normalize/../bar")).is_err());

            // UNC paths.
            assert!(AbsNormPath::new(Path::new(r"\\.\COM42")).is_ok());
            assert!(AbsNormPath::new(Path::new(r"\\?\c:\test")).is_ok());
        }
    }

    #[test]
    fn test_verify_windows() {
        assert!(verify_abs_path_windows_part(r"c:\foo\bar"));
        assert!(verify_abs_path_windows_part(r"\\.\COM42"));
        assert!(verify_abs_path_windows_part(r"\\?\c:\test"));
        assert!(!verify_abs_path_windows_part(r"\\?\c:\.\test"));
    }

    #[test]
    fn test_pop() {
        let mut path = if cfg!(not(windows)) {
            PathBuf::from("/foo/bar")
        } else {
            PathBuf::from("c:/foo/bar")
        };
        let mut abs_path = AbsNormPath::new(&path).unwrap().to_buf();

        assert!(path.pop());
        assert!(abs_path.pop());
        assert_eq!(path, abs_path.as_path());

        assert!(path.pop());
        assert!(abs_path.pop());
        assert_eq!(path, abs_path.as_path());

        assert!(!path.pop());
        assert!(!abs_path.pop());
        assert_eq!(path, abs_path.as_path());
    }

    #[test]
    fn test_join() {
        let path = if cfg!(windows) {
            AbsNormPathBuf::try_from("c:\\foo\\bar".to_owned()).unwrap()
        } else {
            AbsNormPathBuf::try_from("/foo/bar".to_owned()).unwrap()
        };

        let path = path.join(ForwardRelativePath::new("baz").unwrap());
        assert_eq!(
            path.to_str().unwrap(),
            if cfg!(windows) {
                "c:\\foo\\bar\\baz"
            } else {
                "/foo/bar/baz"
            }
        );

        let path = path.join(ForwardRelativePath::empty());
        assert_eq!(
            path.to_str().unwrap(),
            if cfg!(windows) {
                "c:\\foo\\bar\\baz"
            } else {
                "/foo/bar/baz"
            }
        );
    }
}


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

use std::borrow::Borrow;
use std::ffi::OsString;
use std::fmt;
use std::ops::Deref;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;

use allocative::Allocative;
use derive_more::Display;
use ref_cast::RefCast;
use thiserror::Error;

use crate::fs::cwd;

#[derive(Error, Debug)]
enum AbsPathError {
    #[error("expected an absolute path but got a relative path instead: `{0}`")]
    PathNotAbsolute(PathBuf),
    #[error("Cannot convert path to UTF-8, `{0:?}`")]
    PathCannotBeConvertedToUtf8(OsString),
}

#[derive(Hash, Eq, PartialEq, PartialOrd, Ord, RefCast)]
#[repr(transparent)]
pub struct AbsPath(Path);

#[derive(
    Hash,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Clone,
    Allocative,
    Display,
    serde::Serialize,
    serde::Deserialize
)]
#[display(fmt = "{}", "_0.display()")]
pub struct AbsPathBuf(PathBuf);

impl fmt::Debug for AbsPath {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&self.0, f)
    }
}

impl fmt::Debug for AbsPathBuf {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&self.0, f)
    }
}

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

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

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

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

impl Deref for AbsPath {
    type Target = Path;

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

impl Deref for AbsPathBuf {
    type Target = AbsPath;

    fn deref(&self) -> &Self::Target {
        unsafe { &*(self.0.as_path() as *const Path as *const AbsPath) }
    }
}

impl Borrow<AbsPath> for AbsPathBuf {
    fn borrow(&self) -> &AbsPath {
        self
    }
}

impl ToOwned for AbsPath {
    type Owned = AbsPathBuf;

    fn to_owned(&self) -> Self::Owned {
        AbsPathBuf(self.0.to_owned())
    }
}

impl AbsPath {
    pub fn new(path: &Path) -> anyhow::Result<&AbsPath> {
        if path.is_absolute() {
            // SAFETY: repr transparent.
            Ok(unsafe { &*(path as *const Path as *const AbsPath) })
        } else {
            Err(AbsPathError::PathNotAbsolute(path.to_path_buf()).into())
        }
    }

    pub fn as_path(&self) -> &Path {
        &self.0
    }

    pub fn to_str(&self) -> anyhow::Result<&str> {
        match self.0.to_str() {
            Some(s) => Ok(s),
            None => Err(AbsPathError::PathCannotBeConvertedToUtf8(self.0.to_owned().into()).into()),
        }
    }

    pub fn join<P: AsRef<Path>>(&self, other: P) -> AbsPathBuf {
        let path = self.0.join(other);
        assert!(path.is_absolute());
        AbsPathBuf(path)
    }

    pub fn parent(&self) -> Option<&AbsPath> {
        self.0.parent().map(|p| AbsPath::new(p).unwrap())
    }

    pub fn strip_prefix<P: AsRef<AbsPath>>(&self, prefix: P) -> anyhow::Result<&Path> {
        Ok(self.0.strip_prefix(prefix.as_ref())?)
    }

    pub fn ancestors(&self) -> impl Iterator<Item = &'_ AbsPath> {
        // Taking the ancestors of an AbsPath gives you more AbsPath.
        self.0.ancestors().map(AbsPath::ref_cast)
    }

    pub fn as_maybe_relativized(&self) -> &Path {
        cwd::maybe_relativize(&self.0)
    }

    pub fn as_maybe_relativized_str(&self) -> anyhow::Result<&str> {
        Ok(cwd::maybe_relativize_str(self.to_str()?))
    }
}

impl AbsPathBuf {
    pub fn new<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
        let p = AbsPath::new(path.as_ref())?;
        Ok(p.to_owned())
    }

    pub fn into_path_buf(self) -> PathBuf {
        self.0
    }

    pub fn into_os_string(self) -> OsString {
        self.0.into_os_string()
    }

    /// Convert a path into a String. Fails if the path is not UTF8.
    pub fn into_string(self) -> anyhow::Result<String> {
        self.into_os_string()
            .into_string()
            .map_err(|x| AbsPathError::PathCannotBeConvertedToUtf8(x).into())
    }

    pub fn capacity(&self) -> usize {
        self.0.capacity()
    }

    pub fn reserve(&mut self, additional: usize) {
        self.0.reserve(additional)
    }

    pub fn shrink_to_fit(&mut self) {
        self.0.shrink_to_fit()
    }

    pub fn shrink_to(&mut self, min_capacity: usize) {
        self.0.shrink_to(min_capacity)
    }

    pub fn push<P: AsRef<Path>>(&mut self, path: P) {
        self.0.push(path);
        assert!(self.0.is_absolute());
    }

    pub fn pop(&mut self) -> bool {
        let r = self.0.pop();
        assert!(self.0.is_absolute());
        r
    }

    pub fn set_extension<S: AsRef<str>>(&mut self, extension: S) {
        self.0.set_extension(extension.as_ref());
        assert!(self.0.is_absolute());
    }
}

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

    fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
        AbsPath::new(&path)?;
        Ok(AbsPathBuf(path))
    }
}

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

    fn try_from(path: String) -> Result<Self, Self::Error> {
        AbsPathBuf::try_from(PathBuf::from(path))
    }
}

impl FromStr for AbsPathBuf {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> anyhow::Result<AbsPathBuf> {
        AbsPathBuf::try_from(s.to_owned())
    }
}


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

//!
//! General macros useful for path declaration
//!

use std::cmp;

///
/// Generates ['cmp::PartialEq'] and ['cmp::PartialOrd'] for the `lhs` and `rhs`
/// types, where `ty` is the unowned, reference path type.
macro_rules! impl_cmp {
    ($lhs:ty, $rhs:ty, $ty:ty) => {
        impl cmp::PartialEq<$rhs> for $lhs {
            #[inline]
            fn eq(&self, other: &$rhs) -> bool {
                <$ty as cmp::PartialEq>::eq(self, other)
            }
        }

        impl cmp::PartialEq<$lhs> for $rhs {
            #[inline]
            fn eq(&self, other: &$lhs) -> bool {
                <$ty as cmp::PartialEq>::eq(self, other)
            }
        }

        impl cmp::PartialOrd<$rhs> for $lhs {
            #[inline]
            fn partial_cmp(&self, other: &$rhs) -> Option<cmp::Ordering> {
                <$ty as cmp::PartialOrd>::partial_cmp(self, other)
            }
        }

        impl cmp::PartialOrd<$lhs> for $rhs {
            #[inline]
            fn partial_cmp(&self, other: &$lhs) -> Option<cmp::Ordering> {
                <$ty as cmp::PartialOrd>::partial_cmp(self, other)
            }
        }
    };
}

///
/// Generates ['cmp::PartialEq'] and ['cmp::PartialOrd'] for the `lhs` and `rhs`
/// string types, where `ty` is the unowned, reference path type.
macro_rules! impl_cmp_str {
    ($lhs:ty, $rhs:ty, $ty:ty) => {
        impl cmp::PartialEq<$rhs> for $lhs {
            #[inline]
            fn eq(&self, other: &$rhs) -> bool {
                match <$ty>::new(other) {
                    Ok(other) => <$ty as cmp::PartialEq>::eq(self, other),
                    _ => false,
                }
            }
        }

        impl cmp::PartialEq<$lhs> for $rhs {
            #[inline]
            fn eq(&self, other: &$lhs) -> bool {
                match <$ty>::new(self) {
                    Ok(this) => <$ty as cmp::PartialEq>::eq(this, other),
                    _ => false,
                }
            }
        }

        impl cmp::PartialOrd<$rhs> for $lhs {
            #[inline]
            fn partial_cmp(&self, other: &$rhs) -> Option<cmp::Ordering> {
                match <$ty>::new(other) {
                    Ok(other) => <$ty as cmp::PartialOrd>::partial_cmp(self, other),
                    _ => None,
                }
            }
        }

        impl cmp::PartialOrd<$lhs> for $rhs {
            #[inline]
            fn partial_cmp(&self, other: &$lhs) -> Option<cmp::Ordering> {
                match <$ty>::new(self) {
                    Ok(this) => <$ty as cmp::PartialOrd>::partial_cmp(this, other),
                    _ => None,
                }
            }
        }
    };
}

use crate::fs::paths::forward_rel_path::ForwardRelativePath;
use crate::fs::paths::forward_rel_path::ForwardRelativePathBuf;

impl_cmp!(
    ForwardRelativePathBuf,
    ForwardRelativePath,
    ForwardRelativePath
);
impl_cmp!(
    ForwardRelativePathBuf,
    &'_ ForwardRelativePath,
    ForwardRelativePath
);

impl_cmp_str!(ForwardRelativePathBuf, str, ForwardRelativePath);
impl_cmp_str!(ForwardRelativePathBuf, &'_ str, ForwardRelativePath);
impl_cmp_str!(ForwardRelativePathBuf, String, ForwardRelativePath);
impl_cmp_str!(ForwardRelativePath, str, ForwardRelativePath);
impl_cmp_str!(ForwardRelativePath, &'_ str, ForwardRelativePath);
impl_cmp_str!(ForwardRelativePath, String, ForwardRelativePath);
impl_cmp_str!(&'_ ForwardRelativePath, str, ForwardRelativePath);
impl_cmp_str!(&'_ ForwardRelativePath, String, ForwardRelativePath);

use crate::fs::paths::abs_norm_path::AbsNormPath;
use crate::fs::paths::abs_norm_path::AbsNormPathBuf;

impl_cmp!(AbsNormPathBuf, AbsNormPath, AbsNormPath);
impl_cmp!(AbsNormPathBuf, &'_ AbsNormPath, AbsNormPath);

impl_cmp_str!(AbsNormPathBuf, str, AbsNormPath);
impl_cmp_str!(AbsNormPathBuf, &'_ str, AbsNormPath);
impl_cmp_str!(AbsNormPathBuf, String, AbsNormPath);
impl_cmp_str!(AbsNormPath, str, AbsNormPath);
impl_cmp_str!(AbsNormPath, &'_ str, AbsNormPath);
impl_cmp_str!(AbsNormPath, String, AbsNormPath);
impl_cmp_str!(&'_ AbsNormPath, str, AbsNormPath);
impl_cmp_str!(&'_ AbsNormPath, String, AbsNormPath);

use crate::package::package_relative_path::PackageRelativePath;
use crate::package::package_relative_path::PackageRelativePathBuf;

impl_cmp!(
    PackageRelativePathBuf,
    PackageRelativePath,
    PackageRelativePath
);
impl_cmp!(
    PackageRelativePathBuf,
    &'_ PackageRelativePath,
    PackageRelativePath
);

impl_cmp_str!(PackageRelativePathBuf, str, PackageRelativePath);
impl_cmp_str!(PackageRelativePathBuf, &'_ str, PackageRelativePath);
impl_cmp_str!(PackageRelativePathBuf, String, PackageRelativePath);
impl_cmp_str!(PackageRelativePath, str, PackageRelativePath);
impl_cmp_str!(PackageRelativePath, &'_ str, PackageRelativePath);
impl_cmp_str!(PackageRelativePath, String, PackageRelativePath);
impl_cmp_str!(&'_ PackageRelativePath, str, PackageRelativePath);
impl_cmp_str!(&'_ PackageRelativePath, String, PackageRelativePath);

use crate::fs::project_rel_path::ProjectRelativePath;
use crate::fs::project_rel_path::ProjectRelativePathBuf;

impl_cmp!(
    ProjectRelativePathBuf,
    ProjectRelativePath,
    ProjectRelativePath
);
impl_cmp!(
    ProjectRelativePathBuf,
    &'_ ProjectRelativePath,
    ProjectRelativePath
);

impl_cmp_str!(ProjectRelativePathBuf, str, ProjectRelativePath);
impl_cmp_str!(ProjectRelativePathBuf, &'_ str, ProjectRelativePath);
impl_cmp_str!(ProjectRelativePathBuf, String, ProjectRelativePath);
impl_cmp_str!(ProjectRelativePath, str, ProjectRelativePath);
impl_cmp_str!(ProjectRelativePath, &'_ str, ProjectRelativePath);
impl_cmp_str!(ProjectRelativePath, String, ProjectRelativePath);
impl_cmp_str!(&'_ ProjectRelativePath, str, ProjectRelativePath);
impl_cmp_str!(&'_ ProjectRelativePath, String, ProjectRelativePath);


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

use std::borrow::Borrow;
use std::cmp::Ordering;
use std::hash::Hash;
use std::hash::Hasher;
use std::ops::Deref;
use std::path::Path;

use allocative::Allocative;
use compact_str::CompactString;
use derive_more::Display;
use ref_cast::RefCast;
use relative_path::RelativePath;
use thiserror::Error;

use crate::cells::paths::CellRelativePath;
use crate::fs::paths::forward_rel_path::ForwardRelativePath;
use crate::package::package_relative_path::PackageRelativePath;

/// Errors from ForwardRelativePath creation
#[derive(Error, Debug)]
enum FileNameError {
    #[error("file name is empty")]
    Empty,
    #[error("file name is current directory")]
    Dot,
    #[error("file name is parent directory")]
    DotDot,
    #[error("slashes in path: `{0}`")]
    Slashes(String),
}

fn verify_file_name(file_name: &str) -> anyhow::Result<()> {
    if file_name.is_empty() {
        Err(FileNameError::Empty.into())
    } else if file_name == "." {
        Err(FileNameError::Dot.into())
    } else if file_name == ".." {
        Err(FileNameError::DotDot.into())
    } else if file_name.contains('/') || file_name.contains('\\') {
        // Note we do not allow backslashes in file names
        // even if it is valid file name on Linux.
        Err(FileNameError::Slashes(file_name.to_owned()).into())
    } else {
        // At the moment we allow characters like '\0'
        // even if they are not valid at least on Linux.
        Ok(())
    }
}

/// File name. Cannot be empty, cannot contain slashes, '.' or '..'.
#[repr(transparent)]
#[derive(Display, Debug, RefCast, PartialOrd, Ord, Eq)]
pub struct FileName(str);

impl PartialEq<str> for FileName {
    #[inline]
    fn eq(&self, other: &str) -> bool {
        &self.0 == other
    }
}

impl PartialEq<FileName> for str {
    #[inline]
    fn eq(&self, other: &FileName) -> bool {
        self == &other.0
    }
}

impl AsRef<Path> for FileName {
    #[inline]
    fn as_ref(&self) -> &Path {
        Path::new(&self.0)
    }
}

impl AsRef<str> for FileName {
    #[inline]
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl AsRef<RelativePath> for FileName {
    #[inline]
    fn as_ref(&self) -> &RelativePath {
        RelativePath::new(&self.0)
    }
}

impl AsRef<ForwardRelativePath> for FileName {
    #[inline]
    fn as_ref(&self) -> &ForwardRelativePath {
        ForwardRelativePath::unchecked_new(&self.0)
    }
}

impl AsRef<PackageRelativePath> for FileName {
    #[inline]
    fn as_ref(&self) -> &PackageRelativePath {
        PackageRelativePath::unchecked_new(&self.0)
    }
}

impl AsRef<CellRelativePath> for FileName {
    #[inline]
    fn as_ref(&self) -> &CellRelativePath {
        CellRelativePath::unchecked_new(&self.0)
    }
}

impl FileName {
    /// Creates an `FileName` if the given path represents a correct
    /// platform-independent file name, otherwise error.
    ///
    /// ```
    /// use buck2_core::fs::paths::file_name::FileName;
    /// assert!(FileName::new("foo").is_ok());
    /// assert!(FileName::new("").is_err());
    /// assert!(FileName::new(".").is_err());
    /// assert!(FileName::new("..").is_err());
    /// assert!(FileName::new(".x").is_ok());
    /// assert!(FileName::new("foo/bar").is_err());
    /// assert!(FileName::new("foo/").is_err());
    /// assert!(FileName::new("foo\\bar").is_err());
    /// ```
    #[inline]
    pub fn new<S: ?Sized + AsRef<str>>(s: &S) -> anyhow::Result<&Self> {
        verify_file_name(s.as_ref())?;
        Ok(Self::unchecked_new(s.as_ref()))
    }

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

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

    /// 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::fs::paths::file_name::FileName;
    ///
    /// let path = FileName::new("foo.rs")?;
    ///
    /// assert_eq!(Some("foo"), path.file_stem());
    ///
    /// # anyhow::Ok(())
    /// ```
    #[inline]
    pub fn file_stem(&self) -> Option<&str> {
        ForwardRelativePath::unchecked_new(&self.0).file_stem()
    }

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

impl PartialEq for FileName {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.as_str() == other.as_str()
    }
}

impl Hash for FileName {
    #[inline]
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.hash(state)
    }
}

impl ToOwned for FileName {
    type Owned = FileNameBuf;

    #[inline]
    fn to_owned(&self) -> FileNameBuf {
        FileNameBuf(CompactString::new(&self.0))
    }
}

/// Owned version of [`FileName`].
#[derive(Ord, Eq, Display, Debug, Clone, Allocative)]
pub struct FileNameBuf(CompactString);

impl FileNameBuf {
    #[inline]
    pub fn unchecked_new<T>(s: T) -> Self
    where
        T: Into<CompactString>,
    {
        // NOTE: This does not turn a String into an inlined string if it's already allocated.
        Self(s.into())
    }

    #[inline]
    pub fn try_from_or_get_back(s: CompactString) -> Result<FileNameBuf, CompactString> {
        if verify_file_name(&s).is_ok() {
            Ok(FileNameBuf(s))
        } else {
            Err(s)
        }
    }

    #[inline]
    pub fn into_inner(self) -> CompactString {
        self.0
    }

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

impl<T> PartialEq<T> for FileNameBuf
where
    T: AsRef<str>,
{
    #[inline]
    fn eq(&self, other: &T) -> bool {
        self.0 == other.as_ref()
    }
}

impl<T> PartialOrd<T> for FileNameBuf
where
    T: AsRef<str>,
{
    #[inline]
    fn partial_cmp(&self, other: &T) -> Option<Ordering> {
        self.as_str().partial_cmp(other.as_ref())
    }
}

impl Hash for FileNameBuf {
    #[inline]
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.as_str().hash(state)
    }
}

impl Borrow<FileName> for FileNameBuf {
    #[inline]
    fn borrow(&self) -> &FileName {
        self.as_ref()
    }
}

impl Deref for FileNameBuf {
    type Target = FileName;

    #[inline]
    fn deref(&self) -> &Self::Target {
        self.as_ref()
    }
}

impl Borrow<str> for FileNameBuf {
    #[inline]
    fn borrow(&self) -> &str {
        self.as_ref()
    }
}

impl AsRef<FileName> for FileNameBuf {
    #[inline]
    fn as_ref(&self) -> &FileName {
        FileName::unchecked_new(self.0.as_str())
    }
}

impl AsRef<Path> for FileNameBuf {
    #[inline]
    fn as_ref(&self) -> &Path {
        Path::new(self.0.as_str())
    }
}

impl AsRef<str> for FileNameBuf {
    #[inline]
    fn as_ref(&self) -> &str {
        self.0.as_str()
    }
}

impl AsRef<RelativePath> for FileNameBuf {
    #[inline]
    fn as_ref(&self) -> &RelativePath {
        RelativePath::new(self.0.as_str())
    }
}

impl AsRef<ForwardRelativePath> for FileNameBuf {
    #[inline]
    fn as_ref(&self) -> &ForwardRelativePath {
        ForwardRelativePath::unchecked_new(self.0.as_str())
    }
}

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

    #[inline]
    fn try_from(value: String) -> anyhow::Result<FileNameBuf> {
        // NOTE: This does not turn a String into an inlined string.
        verify_file_name(value.as_str())?;
        Ok(FileNameBuf(value.into()))
    }
}

impl TryFrom<CompactString> for FileNameBuf {
    type Error = anyhow::Error;

    #[inline]
    fn try_from(value: CompactString) -> anyhow::Result<FileNameBuf> {
        verify_file_name(value.as_str())?;
        Ok(FileNameBuf(value))
    }
}


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

//! Format related utilities of the core path types

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

/// formats the path as a quoted string
pub(crate) fn quoted_display<D>(d: &D, f: &mut Formatter) -> fmt::Result
where
    D: Display + ?Sized,
{
    write!(f, "\"{:}\"", d)
}