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

/// A directory that isn't fingerprinted, and as such is mutable;
pub trait DirectoryMut<L, H>: Directory<L, H> {
    fn get_mut<'a>(
        &'a mut self,
        needle: &'_ FileName,
    ) -> Option<DirectoryEntry<&'a mut dyn DirectoryMut<L, H>, &'a mut L>>;
}

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


/*
 * 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 starlark_map::small_map::SmallMap;
use thiserror::Error;

use super::Directory;
use super::DirectoryBuilder;
use super::DirectoryDigest;
use super::DirectoryEntries;
use super::DirectoryEntry;
use super::DirectoryIterator;
use super::DirectoryIteratorPathAccessor;
use super::DirectoryIteratorPathStack;
use super::FingerprintedDirectory;
use super::FingerprintedDirectoryEntries;
use super::FingerprintedOrderedDirectoryEntries;
use super::OrderedDirectoryEntries;
use crate::fs::paths::file_name::FileName;
use crate::fs::paths::file_name::FileNameBuf;
use crate::fs::paths::IntoFileNameBufIterator;

#[derive(Debug, Error)]
pub enum DirectorySearchError<L> {
    #[error("Search traverses a leaf")]
    CannotTraverseLeaf { leaf: L },
}

impl<L> DirectorySearchError<L> {
    pub fn into_leaf(self) -> L {
        let Self::CannotTraverseLeaf { leaf } = self;
        leaf
    }
}

#[derive(Debug, Error)]
pub enum DirectoryFilterError {
    #[error("Filter traverses a leaf")]
    CannotTraverseLeaf,
}

/// A query builder for filtering or search operations on directories. It's a tree of paths and
/// what action we want to take on them.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum DirectorySelector {
    /// Traverse only the netries that match this filename.
    Traverse(SmallMap<FileNameBuf, DirectorySelector>),
    /// Take this entire tree.
    Take,
}

impl DirectorySelector {
    pub fn empty() -> Self {
        Self::Traverse(Default::default())
    }

    pub fn is_empty(&self) -> bool {
        match self {
            Self::Traverse(d) => d.is_empty(),
            Self::Take => false,
        }
    }

    /// Add a path to this DirectorySelector.
    pub fn select(&mut self, path: impl IntoFileNameBufIterator) {
        let path = path.into_iter();
        self.select_inner(path)
    }

    fn select_inner(&mut self, mut path: impl Iterator<Item = FileNameBuf>) {
        let entry = match path.next() {
            Some(e) => e,
            None => {
                *self = Self::Take;
                return;
            }
        };

        match self {
            Self::Traverse(s) => s
                .entry(entry)
                .or_insert_with(DirectorySelector::empty)
                .select_inner(path),
            Self::Take => {}
        }
    }

    /// Filter a DirectoryBuilder by only retaining matching entries.
    pub fn filter<L, H>(&self, dir: &mut DirectoryBuilder<L, H>) -> Result<(), DirectoryFilterError>
    where
        L: Clone,
        H: DirectoryDigest,
    {
        let mut res = Ok(());

        match self {
            Self::Traverse(filter) => filter_inner(dir, filter, &mut res),
            Self::Take => {}
        };

        res
    }
}

fn filter_inner<L, H>(
    dir: &mut DirectoryBuilder<L, H>,
    filter: &SmallMap<FileNameBuf, DirectorySelector>,
    res: &mut Result<(), DirectoryFilterError>,
) where
    L: Clone,
    H: DirectoryDigest,
{
    let dir = dir.as_mut();

    let entries = std::mem::take(dir);

    for (k, mut v) in entries.into_iter() {
        let selector = match filter.get(&k) {
            Some(s) => s,
            None => continue,
        };

        match selector {
            DirectorySelector::Traverse(next_map) => match v {
                DirectoryEntry::Dir(ref mut d) => filter_inner(d, next_map, res),
                DirectoryEntry::Leaf(..) => {
                    *res = Err(DirectoryFilterError::CannotTraverseLeaf);
                }
            },
            DirectorySelector::Take => {}
        };

        dir.insert(k, v);
    }
}

macro_rules! impl_directory_search {
    (
        $search_ty: ident,
        $dir_ty: ident,
        $entries_ty: ident,
        $entries_method: ident,
        $mod: ident,
        $selector_search_method: ident,
    ) => {
        mod $mod {
            use super::*;

            enum SearchFrame<'a, 'b, L, H> {
                /// The search consists of just returning the root.
                ReturnRoot {
                    root: Option<DirectoryEntry<&'b dyn $dir_ty<L, H>, &'b L>>,
                },

                /// Continue the search.
                Search {
                    search: &'a SmallMap<FileNameBuf, DirectorySelector>,
                    name: Option<&'b FileName>,
                    entries: $entries_ty<'b, L, H>,
                },
            }

            pub struct $search_ty<'a, 'b, L, H> {
                stack: Vec<SearchFrame<'a, 'b, L, H>>,
            }

            impl<'a, 'b, L, H> $search_ty<'a, 'b, L, H> {
                pub fn new<D>(selector: &'a DirectorySelector, root: &'b D) -> Self
                where
                    D: $dir_ty<L, H>,
                {
                    match selector {
                        DirectorySelector::Traverse(ref search) => Self {
                            stack: vec![SearchFrame::Search {
                                search,
                                name: None,
                                entries: $entries_ty::from(root.$entries_method()),
                            }],
                        },
                        DirectorySelector::Take => Self {
                            stack: vec![SearchFrame::ReturnRoot {
                                root: Some(DirectoryEntry::Dir(root as &dyn $dir_ty<L, H>)),
                            }],
                        },
                    }
                }
            }

            impl<'a, 'b, L, H> DirectoryIterator for $search_ty<'a, 'b, L, H> {
                type PathStack = Self;
                type Item = Result<
                    DirectoryEntry<&'b dyn $dir_ty<L, H>, &'b L>,
                    DirectorySearchError<&'b L>,
                >;

                fn next<'c>(
                    &'c mut self,
                ) -> Option<(DirectoryIteratorPathAccessor<'c, Self>, Self::Item)> {
                    loop {
                        let frame = self.stack.last_mut()?;

                        match frame {
                            SearchFrame::ReturnRoot { root } => {
                                let root = root.take()?;
                                return Some((
                                    DirectoryIteratorPathAccessor {
                                        leaf: None,
                                        stack: self,
                                    },
                                    Ok(root),
                                ));
                            }
                            SearchFrame::Search {
                                search, entries, ..
                            } => {
                                if let Some((name, entry)) = entries.next() {
                                    let search = search.get(name);

                                    match search {
                                        Some(DirectorySelector::Traverse(t)) => match entry {
                                            // Traverse into this directory ... assuming it's a directory :)
                                            DirectoryEntry::Dir(d) => {
                                                self.stack.push(SearchFrame::Search {
                                                    name: Some(name),
                                                    search: t,
                                                    entries: $entries_ty::from(d.$entries_method()),
                                                });
                                                continue;
                                            }
                                            DirectoryEntry::Leaf(leaf) => {
                                                return Some((
                                                    DirectoryIteratorPathAccessor {
                                                        leaf: Some(name),
                                                        stack: self,
                                                    },
                                                    Err(DirectorySearchError::CannotTraverseLeaf {
                                                        leaf,
                                                    }),
                                                ));
                                            }
                                        },
                                        Some(DirectorySelector::Take) => {
                                            // Return the entry. Do not traverse further.
                                            return Some((
                                                DirectoryIteratorPathAccessor {
                                                    leaf: Some(name),
                                                    stack: self,
                                                },
                                                Ok(entry),
                                            ));
                                        }
                                        None => {
                                            // Ignore this node entirely.
                                            continue;
                                        }
                                    }
                                }
                            }
                        };

                        // We've exhausted this iterator. Go back to the previous stack frame.
                        self.stack.pop();
                    }
                }
            }

            impl<'a, 'b, L, H> DirectoryIteratorPathStack for $search_ty<'a, 'b, L, H> {
                fn for_each_path<'this, F>(&'this self, mut f: F)
                where
                    F: FnMut(&'this FileName),
                {
                    let it = self.stack.iter().filter_map(|frame| match frame {
                        SearchFrame::ReturnRoot { .. } => None,
                        SearchFrame::Search { name, .. } => name.as_deref(),
                    });

                    for path in it {
                        f(path);
                    }
                }
            }
        }

        impl DirectorySelector {
            pub fn $selector_search_method<'a, 'b, L, H, D: $dir_ty<L, H>>(
                &'a self,
                dir: &'b D,
            ) -> $search_ty<'a, 'b, L, H> {
                $search_ty::new(self, dir)
            }
        }

        pub use $mod::$search_ty;
    };
}

impl_directory_search!(
    UnorderedDirectorySearch,
    Directory,
    DirectoryEntries,
    entries,
    unordered_directory_search,
    unordered_search,
);

impl_directory_search!(
    OrderedDirectorySearch,
    Directory,
    OrderedDirectoryEntries,
    entries,
    ordered_directory_search,
    ordered_search,
);

impl_directory_search!(
    FingerprintedUnorderedDirectorySearch,
    FingerprintedDirectory,
    FingerprintedDirectoryEntries,
    fingerprinted_entries,
    fingerprinted_unordered_directory_search,
    fingerprinted_unordered_search,
);

impl_directory_search!(
    FingerprintedOrderedDirectorySearch,
    FingerprintedDirectory,
    FingerprintedOrderedDirectoryEntries,
    fingerprinted_entries,
    fingerprinted_ordered_directory_search,
    fingerprinted_ordered_search,
);


/*
 * 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 super::Directory;
use super::DirectoryEntries;
use super::DirectoryEntry;
use super::FingerprintedDirectory;
use super::FingerprintedDirectoryEntries;
use crate::fs::paths::file_name::FileName;

macro_rules! impl_ordered_entries {
    ($ordered_entries_ty: ident, $dir_ty: ident, $from_ty: ident,) => {
        /// A wrapper struct that allows an ordered traversal of directory entries.
        pub struct $ordered_entries_ty<'a, L, H> {
            entries: Vec<(&'a FileName, DirectoryEntry<&'a dyn $dir_ty<L, H>, &'a L>)>,
        }

        impl<'a, L, H> From<$from_ty<'a, L, H>> for $ordered_entries_ty<'a, L, H> {
            fn from(entries: $from_ty<'a, L, H>) -> Self {
                let mut entries = entries.collect::<Vec<_>>();
                entries.sort_by(|(name1, _), (name2, _)| name2.cmp(name1));
                Self { entries }
            }
        }

        impl<'a, L, H> Iterator for $ordered_entries_ty<'a, L, H> {
            type Item = (&'a FileName, DirectoryEntry<&'a dyn $dir_ty<L, H>, &'a L>);

            fn next(&mut self) -> Option<Self::Item> {
                self.entries.pop()
            }
        }
    };
}

impl_ordered_entries!(
    FingerprintedOrderedDirectoryEntries,
    FingerprintedDirectory,
    FingerprintedDirectoryEntries,
);

impl_ordered_entries!(OrderedDirectoryEntries, Directory, DirectoryEntries,);


/*
 * 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 dupe::Dupe;
use gazebo::variants::UnpackVariants;

/// An entry in a Directory, parameterized by the type of children directories and the type of leaf
/// nodes. We expect to be able to traverse directories, and we don't traverse leaves.
#[derive(
    Clone,
    Dupe,
    Debug,
    Eq,
    PartialEq,
    Display,
    Hash,
    UnpackVariants,
    Allocative
)]
#[display(bound = "D: ::std::fmt::Display, L: ::std::fmt::Display")]
pub enum DirectoryEntry<D, L> {
    Dir(D),
    Leaf(L),
}

impl<D, L> DirectoryEntry<D, L> {
    pub fn map_dir<U>(self, f: impl FnOnce(D) -> U) -> DirectoryEntry<U, L> {
        match self {
            Self::Dir(d) => DirectoryEntry::Dir(f(d)),
            Self::Leaf(l) => DirectoryEntry::Leaf(l),
        }
    }

    pub fn map_leaf<U>(self, f: impl FnOnce(L) -> U) -> DirectoryEntry<D, U> {
        match self {
            Self::Dir(d) => DirectoryEntry::Dir(d),
            Self::Leaf(l) => DirectoryEntry::Leaf(f(l)),
        }
    }

    pub fn as_ref(&self) -> DirectoryEntry<&'_ D, &'_ L> {
        match self {
            Self::Dir(ref d) => DirectoryEntry::Dir(d),
            Self::Leaf(ref l) => DirectoryEntry::Leaf(l),
        }
    }

    pub fn as_mut(&mut self) -> DirectoryEntry<&'_ mut D, &'_ mut L> {
        match self {
            Self::Dir(ref mut d) => DirectoryEntry::Dir(d),
            Self::Leaf(ref mut l) => DirectoryEntry::Leaf(l),
        }
    }

    pub fn dir(self) -> Option<D> {
        match self {
            Self::Dir(d) => Some(d),
            Self::Leaf(..) => None,
        }
    }

    pub fn leaf(self) -> Option<L> {
        match self {
            Self::Dir(..) => None,
            Self::Leaf(l) => Some(l),
        }
    }
}


/*
 * 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 derive_more::Display;

use super::impl_fingerprinted_directory;
use super::DashMapDirectoryInterner;
use super::Directory;
use super::DirectoryBuilder;
use super::DirectoryData;
use super::DirectoryDigest;
use super::DirectoryEntries;
use super::DirectoryEntry;
use super::FingerprintedDirectory;
use super::FingerprintedDirectoryEntries;
use super::ImmutableDirectory;
use super::SharedDirectory;
use crate::fs::paths::file_name::FileName;
use crate::fs::paths::file_name::FileNameBuf;

#[derive(Derivative, Display, Allocative)]
#[derivative(Debug(bound = "L: ::std::fmt::Debug"))]
#[derivative(Clone(bound = "L: ::std::clone::Clone"))]
#[display(fmt = "{}", "self.data")]
pub struct ExclusiveDirectory<L, H>
where
    H: DirectoryDigest,
{
    pub(super) data: DirectoryData<ImmutableDirectory<L, H>, L, H>,
}

impl<L, H> ExclusiveDirectory<L, H>
where
    H: DirectoryDigest,
{
    pub fn shared(self, interner: &DashMapDirectoryInterner<L, H>) -> SharedDirectory<L, H> {
        if let Some(shared) = interner.get(self.fingerprint()) {
            return shared;
        }

        let DirectoryData {
            entries,
            fingerprint,
            _hash,
        } = self.data;

        let entries = entries
            .into_iter()
            .map(|(k, v)| (k, v.map_dir(|d| d.shared(interner))))
            .collect();

        let new_data = DirectoryData {
            entries,
            fingerprint,
            _hash,
        };

        interner.intern(new_data)
    }

    pub fn into_entries<C>(self) -> C
    where
        C: FromIterator<(FileNameBuf, DirectoryEntry<DirectoryBuilder<L, H>, L>)>,
    {
        self.data
            .entries
            .into_iter()
            .map(|(k, v)| (k, v.map_dir(|v| v.into_builder())))
            .collect()
    }

    pub fn entries(
        &self,
    ) -> impl IntoIterator<Item = (&FileNameBuf, &DirectoryEntry<ImmutableDirectory<L, H>, L>)> + '_
    {
        &self.data.entries
    }

    pub fn get<'a>(
        &'a self,
        needle: &'_ FileName,
    ) -> Option<DirectoryEntry<&'a ImmutableDirectory<L, H>, &'a L>> {
        self.data.entries.get(needle).as_ref().map(|v| v.as_ref())
    }

    pub fn fingerprint(&self) -> &H {
        self.data.fingerprint()
    }

    pub fn into_builder(self) -> DirectoryBuilder<L, H> {
        DirectoryBuilder::Immutable(ImmutableDirectory::Exclusive(self))
    }
}

impl_fingerprinted_directory!(ExclusiveDirectory);


/*
 * 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 thiserror::Error;

use super::Directory;
use super::DirectoryEntry;
use super::DirectoryMut;
use super::FingerprintedDirectory;
use super::PathAccumulator;
use crate::fs::paths::file_name::FileName;
use crate::fs::paths::forward_rel_path::ForwardRelativePathBuf;

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

    #[error("Find would traverse a leaf at path: `{}`", .path)]
    CannotTraverseLeaf { path: PathAccumulator },
}

trait FindConflict<T> {
    fn new<'b>(path: &'b FileName, remaining: impl Iterator<Item = &'b FileName>, leaf: T) -> Self;

    fn with<'b>(self, path: &'b FileName) -> Self;
}

impl<T> FindConflict<T> for PathAccumulator {
    fn new<'b>(
        path: &'b FileName,
        _remaining: impl Iterator<Item = &'b FileName>,
        _leaf: T,
    ) -> Self {
        PathAccumulator::new(path)
    }

    fn with<'b>(self, path: &'b FileName) -> Self {
        PathAccumulator::with(self, path)
    }
}

struct PrefixLookupContainer<T> {
    leaf: T,
    path: ForwardRelativePathBuf,
}

impl<T> FindConflict<T> for PrefixLookupContainer<T> {
    fn new<'b>(path: &'b FileName, remaining: impl Iterator<Item = &'b FileName>, leaf: T) -> Self {
        Self {
            leaf,
            path: std::iter::once(path)
                .chain(remaining)
                .collect::<Option<ForwardRelativePathBuf>>()
                .expect("We know there is at least one path component"),
        }
    }

    fn with<'b>(self, _path: &'b FileName) -> Self {
        self
    }
}

macro_rules! impl_find {
    (
        $dir_ty: ident,
        $getter: ident,
        $find_name: ident,
        $find_prefix_name: ident,
        $mod: ident,
        $( $mutability:tt, )*
    ) => {
        mod $mod {
            use super::*;

            pub fn $find_name<'a, 'b, L, H, D: $dir_ty<L, H>>(
                dir: &'a $($mutability)* D,
                path: impl IntoIterator<Item = &'b FileName>,
            ) -> Result<Option<DirectoryEntry<&'a $($mutability)* dyn $dir_ty<L, H>, &'a $($mutability)* L>>, DirectoryFindError>
            {
                let mut path = path.into_iter();

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

                find_inner::<_, _, PathAccumulator>(dir, path_needle, path)
                    .map_err(|path| DirectoryFindError::CannotTraverseLeaf { path })
            }

            pub fn $find_prefix_name<'a, 'b, L, H, D: $dir_ty<L, H>>(
                dir: &'a $($mutability)* D,
                path: impl IntoIterator<Item = &'b FileName>,
            ) -> Result<
                Option<(
                    DirectoryEntry<&'a $($mutability)* dyn $dir_ty<L, H>, &'a $($mutability)* L>,
                    Option<ForwardRelativePathBuf>,
                )>,
                DirectoryFindError,
            > {
                let mut path = path.into_iter();

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

                match find_inner::<_, _, PrefixLookupContainer<&'a $($mutability)* L>>(dir, path_needle, path) {
                    Ok(maybe_leaf) => Ok((maybe_leaf.map(|l| (l, None)))),
                    Err(PrefixLookupContainer { leaf, path }) => {
                        Ok(Some((DirectoryEntry::Leaf(leaf), Some(path))))
                    }
                }
            }

            fn find_inner<'a, 'b, L, H, A>(
                dir: &'a $($mutability)* dyn $dir_ty<L, H>,
                path_needle: &'b FileName,
                mut path_rest: impl Iterator<Item = &'b FileName>,
            ) -> Result<Option<DirectoryEntry<&'a $($mutability)* dyn $dir_ty<L, H>, &'a $($mutability)* L>>, A>
            where
                A: FindConflict<&'a $($mutability)* L>,
            {
                let entry = match dir.$getter(path_needle) {
                    Some(entry) => entry,
                    None => return Ok(None),
                };

                let next_path_needle = match path_rest.next() {
                    Some(next_path_needle) => next_path_needle,
                    None => return Ok(Some(entry)),
                };

                match entry {
                    DirectoryEntry::Dir(dir) => {
                        find_inner::<_, _, A>(dir, next_path_needle, path_rest)
                            .map_err(|acc| acc.with(path_needle))
                    }
                    DirectoryEntry::Leaf(leaf) => Err(A::new(next_path_needle, path_rest, leaf)),
                }
            }
        }

        pub use $mod::$find_name;
        pub use $mod::$find_prefix_name;
    };
}

impl_find!(
    FingerprintedDirectory,
    get,
    find_fingerprinted,
    find_prefix_fingerprinted,
    impl_find_fingerprinted,
);
impl_find!(Directory, get, find, find_prefix, impl_find,);
impl_find!(
    DirectoryMut,
    get_mut,
    find_mut,
    find_prefix_mut,
    impl_find_mut,
    mut,
);


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

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

pub trait FingerprintedDirectory<L, H> {
    fn fingerprinted_entries(&self) -> FingerprintedDirectoryEntries<'_, L, H>;

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

    fn fingerprinted_unordered_walk(&self) -> FingerprintedUnorderedDirectoryWalk<'_, L, H> {
        FingerprintedUnorderedDirectoryWalk::new(self)
    }

    fn fingerprinted_ordered_walk(&self) -> FingerprintedOrderedDirectoryWalk<'_, L, H> {
        FingerprintedOrderedDirectoryWalk::new(self)
    }

    fn fingerprint(&self) -> &H
    where
        H: DirectoryDigest;
}

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


/*
 * 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 allocative::Allocative;
use derivative::Derivative;
use derive_more::Display;

use super::DashMapDirectoryInterner;
use super::Directory;
use super::DirectoryBuilder;
use super::DirectoryDigest;
use super::DirectoryEntries;
use super::DirectoryEntry;
use super::ExclusiveDirectory;
use super::FingerprintedDirectory;
use super::FingerprintedDirectoryEntries;
use super::SharedDirectory;
use crate::fs::paths::file_name::FileName;
use crate::fs::paths::file_name::FileNameBuf;

#[derive(Derivative, Display, Allocative)]
#[derivative(Debug(bound = "L: ::std::fmt::Debug"))]
#[derivative(Clone(bound = "L: ::std::clone::Clone"))]
pub enum ImmutableDirectory<L, H>
where
    H: DirectoryDigest,
{
    Exclusive(ExclusiveDirectory<L, H>),
    Shared(SharedDirectory<L, H>),
}

impl<L, H> ImmutableDirectory<L, H>
where
    H: DirectoryDigest,
{
    pub fn shared(self, interner: &DashMapDirectoryInterner<L, H>) -> SharedDirectory<L, H> {
        match self {
            Self::Exclusive(dir) => dir.shared(interner),
            Self::Shared(dir) => dir,
        }
    }

    pub fn into_builder(self) -> DirectoryBuilder<L, H> {
        match self {
            Self::Exclusive(d) => d.into_builder(),
            Self::Shared(s) => s.into_builder(),
        }
    }
}

impl<L, H> ImmutableDirectory<L, H>
where
    L: Clone,
    H: DirectoryDigest,
{
    pub fn into_entries<C>(self) -> C
    where
        C: FromIterator<(FileNameBuf, DirectoryEntry<DirectoryBuilder<L, H>, L>)>,
    {
        match self {
            Self::Exclusive(dir) => dir.into_entries(),
            Self::Shared(dir) => dir.into_entries(),
        }
    }
}

impl<L, H> Directory<L, H> for ImmutableDirectory<L, H>
where
    H: DirectoryDigest,
{
    fn entries(&self) -> DirectoryEntries<'_, L, H> {
        match self {
            Self::Exclusive(dir) => Directory::entries(dir),
            Self::Shared(dir) => Directory::entries(dir),
        }
    }

    fn get<'a>(
        &'a self,
        needle: &'_ FileName,
    ) -> Option<DirectoryEntry<&'a dyn Directory<L, H>, &'a L>> {
        match self {
            Self::Exclusive(dir) => Directory::get(dir, needle),
            Self::Shared(dir) => Directory::get(dir, needle),
        }
    }

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

impl<L, H> FingerprintedDirectory<L, H> for ImmutableDirectory<L, H>
where
    H: DirectoryDigest,
{
    fn fingerprinted_entries<'a>(&'a self) -> FingerprintedDirectoryEntries<'a, L, H> {
        match self {
            Self::Exclusive(dir) => FingerprintedDirectory::fingerprinted_entries(dir),
            Self::Shared(dir) => FingerprintedDirectory::fingerprinted_entries(dir),
        }
    }

    fn get<'a>(
        &'a self,
        needle: &'_ FileName,
    ) -> Option<DirectoryEntry<&'a dyn FingerprintedDirectory<L, H>, &'a L>> {
        match self {
            Self::Exclusive(dir) => FingerprintedDirectory::get(dir, needle),
            Self::Shared(dir) => FingerprintedDirectory::get(dir, needle),
        }
    }

    fn fingerprint(&self) -> &H {
        match self {
            Self::Exclusive(dir) => FingerprintedDirectory::fingerprint(dir),
            Self::Shared(dir) => FingerprintedDirectory::fingerprint(dir),
        }
    }
}

impl<L, H> PartialEq for ImmutableDirectory<L, H>
where
    H: DirectoryDigest,
{
    fn eq(&self, other: &Self) -> bool {
        self.fingerprint() == other.fingerprint()
    }
}

impl<L, H> Eq for ImmutableDirectory<L, H> where H: DirectoryDigest {}


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

macro_rules! impl_fingerprinted_directory {
    (
        $this: ident
    ) => {
        impl<L, H> Directory<L, H> for $this<L, H>
        where
            H: DirectoryDigest,
        {
            fn entries<'a>(&'a self) -> DirectoryEntries<'a, L, H> {
                let it = self.entries().into_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)
                });
                Box::new(it)
            }

            fn get<'a>(
                &'a self,
                needle: &'_ FileName,
            ) -> Option<DirectoryEntry<&'a dyn Directory<L, H>, &'a L>> {
                $this::get(self, needle).map(|v| v.map_dir(|d| d as &dyn Directory<L, H>))
            }

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

        impl<L, H> FingerprintedDirectory<L, H> for $this<L, H>
        where
            H: DirectoryDigest,
        {
            fn fingerprinted_entries<'a>(&'a self) -> FingerprintedDirectoryEntries<'a, L, H> {
                let it = self.entries().into_iter().map(|(k, v)| {
                    let k = k.as_ref();
                    let v = v
                        .as_ref()
                        .map_dir(|v| v as &dyn FingerprintedDirectory<L, H>);
                    (k, v)
                });
                Box::new(it)
            }

            fn get<'a>(
                &'a self,
                needle: &'_ FileName,
            ) -> Option<DirectoryEntry<&'a dyn FingerprintedDirectory<L, H>, &'a L>> {
                $this::get(self, needle)
                    .map(|v| v.map_dir(|d| d as &dyn FingerprintedDirectory<L, H>))
            }

            fn fingerprint(&self) -> &H {
                $this::fingerprint(self)
            }
        }

        impl<L, H> PartialEq for $this<L, H>
        where
            H: DirectoryDigest,
        {
            fn eq(&self, other: &Self) -> bool {
                self.fingerprint() == other.fingerprint()
            }
        }

        impl<L, H> Eq for $this<L, H> where H: DirectoryDigest {}
    };
}

pub(super) use impl_fingerprinted_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.
 */

#![allow(clippy::module_inception)]

mod builder;
mod dashmap_directory_interner;
mod directory;
mod directory_data;
mod directory_hasher;
mod directory_iterator;
mod directory_mut;
mod directory_selector;
mod entries;
mod entry;
mod exclusive_directory;
mod find;
mod fingerprinted_directory;
mod immutable_directory;
mod macros;
mod no_hasher;
mod path_accumulator;
mod shared_directory;
mod test;
mod walk;

pub use builder::DirectoryBuilder;
pub use builder::DirectoryInsertError;
pub use builder::DirectoryMergeError;
pub use builder::DirectoryMkdirError;
pub use dashmap_directory_interner::DashMapDirectoryInterner;
pub use directory::Directory;
pub use directory::DirectoryEntries;
pub use directory_data::DirectoryData;
pub use directory_hasher::DirectoryDigest;
pub use directory_hasher::DirectoryHasher;
pub use directory_hasher::NoDigest;
pub use directory_iterator::DirectoryIterator;
pub use directory_iterator::DirectoryIteratorPathAccessor;
pub use directory_iterator::DirectoryIteratorPathStack;
pub use directory_iterator::DirectoryIteratorWithPaths;
pub use directory_iterator::DirectoryIteratorWithoutPaths;
pub use directory_mut::DirectoryMut;
pub use directory_selector::DirectorySearchError;
pub use directory_selector::DirectorySelector;
pub use directory_selector::FingerprintedOrderedDirectorySearch;
pub use directory_selector::FingerprintedUnorderedDirectorySearch;
pub use directory_selector::OrderedDirectorySearch;
pub use directory_selector::UnorderedDirectorySearch;
use entries::FingerprintedOrderedDirectoryEntries;
use entries::OrderedDirectoryEntries;
pub use entry::DirectoryEntry;
pub use exclusive_directory::ExclusiveDirectory;
pub use find::find;
pub use find::find_fingerprinted;
pub use find::find_mut;
pub use find::find_prefix;
pub use find::find_prefix_fingerprinted;
pub use find::find_prefix_mut;
pub use find::DirectoryFindError;
pub use fingerprinted_directory::FingerprintedDirectory;
pub use fingerprinted_directory::FingerprintedDirectoryEntries;
pub use immutable_directory::ImmutableDirectory;
pub use path_accumulator::PathAccumulator;
pub use shared_directory::SharedDirectory;
pub use shared_directory::SharedDirectoryData;
pub use shared_directory::SharedDirectoryInner;
pub use walk::fingerprinted_ordered_entry_walk;
pub use walk::fingerprinted_unordered_entry_walk;
pub use walk::ordered_entry_walk;
pub use walk::unordered_entry_walk;
pub use walk::DirectoryEntryWalk;
pub use walk::FingerprintedOrderedDirectoryWalk;
pub use walk::FingerprintedUnorderedDirectoryWalk;
pub use walk::OrderedDirectoryWalk;
pub use walk::UnorderedDirectoryWalk;

use self::macros::impl_fingerprinted_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.
 */

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

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

impl Dupe for NoHash {}


/*
 * 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::file_name::FileNameBuf;

/// Accumulate path components in reverse order. This is used to show the path where an issue
/// occurred in Directory operations.
pub struct PathAccumulator {
    inner: Vec<FileNameBuf>,
}

impl PathAccumulator {
    pub fn new(p: &FileName) -> Self {
        PathAccumulator {
            inner: vec![p.to_owned()],
        }
    }

    pub fn with(mut self, p: &FileName) -> Self {
        self.inner.push(p.to_owned());
        self
    }
}

impl fmt::Debug for PathAccumulator {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Dedicated Debug since we reverse the order so a derived debug would be less than useful.
        fmt::Display::fmt(self, f)
    }
}

impl fmt::Display for PathAccumulator {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut first = true;
        for e in self.inner.iter().rev() {
            if !first {
                write!(f, "/")?;
            }
            first = false;
            write!(f, "{}", e)?;
        }
        Ok(())
    }
}

impl<'a> IntoIterator for &'a PathAccumulator {
    type Item = &'a FileName;
    type IntoIter = impl Iterator<Item = Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.inner.iter().rev().map(|e| e.as_ref())
    }
}

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

    #[test]
    fn test_path_accumulator() {
        let p = PathAccumulator::new(FileName::unchecked_new("foo"));
        assert_eq!(p.to_string(), "foo");

        let p = p.with(FileName::unchecked_new("bar"));
        assert_eq!(p.to_string(), "bar/foo");

        let mut iter = (&p).into_iter();
        assert_eq!(iter.next().unwrap().to_string(), "bar");
        assert_eq!(iter.next().unwrap().to_string(), "foo");
        assert_eq!(iter.next(), None);
    }
}


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

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

use std::sync::Arc;

use allocative::Allocative;
use derivative::Derivative;
use derive_more::Display;
use dupe::Clone_;
use dupe::Dupe_;

use super::impl_fingerprinted_directory;
use super::DashMapDirectoryInterner;
use super::Directory;
use super::DirectoryBuilder;
use super::DirectoryData;
use super::DirectoryDigest;
use super::DirectoryEntries;
use super::DirectoryEntry;
use super::FingerprintedDirectory;
use super::FingerprintedDirectoryEntries;
use super::ImmutableDirectory;
use crate::fs::paths::file_name::FileName;
use crate::fs::paths::file_name::FileNameBuf;

pub type SharedDirectoryData<L, H> = DirectoryData<SharedDirectory<L, H>, L, H>;

#[derive(Derivative, Display, Allocative)]
#[derivative(Debug(bound = "L: ::std::fmt::Debug"))]
#[display(fmt = "{}", "self.data")]
pub struct SharedDirectoryInner<L, H>
where
    H: DirectoryDigest,
{
    pub(super) data: SharedDirectoryData<L, H>,

    #[derivative(Debug = "ignore")]
    pub(super) interner: DashMapDirectoryInterner<L, H>,
}

impl<L, H> Drop for SharedDirectoryInner<L, H>
where
    H: DirectoryDigest,
{
    fn drop(&mut self) {
        self.interner.dropped(&self.data)
    }
}

#[derive(Derivative, Clone_, Dupe_, Display, Allocative)]
#[derivative(Debug(bound = "L: ::std::fmt::Debug"))]
#[display(fmt = "{}", "self.inner")]
pub struct SharedDirectory<L, H>
where
    H: DirectoryDigest,
{
    pub(super) inner: Arc<SharedDirectoryInner<L, H>>,
}

impl<L, H> SharedDirectory<L, H>
where
    H: DirectoryDigest,
{
    pub fn as_immutable(self) -> ImmutableDirectory<L, H> {
        ImmutableDirectory::Shared(self)
    }

    pub fn entries(
        &self,
    ) -> impl IntoIterator<Item = (&FileNameBuf, &DirectoryEntry<SharedDirectory<L, H>, L>)> + '_
    {
        &self.inner.data.entries
    }

    pub fn get<'a>(
        &'a self,
        needle: &'_ FileName,
    ) -> Option<DirectoryEntry<&'a SharedDirectory<L, H>, &'a L>> {
        self.inner
            .data
            .entries
            .get(needle)
            .as_ref()
            .map(|v| v.as_ref())
    }

    pub fn fingerprint(&self) -> &H {
        self.inner.data.fingerprint()
    }

    pub fn into_builder(self) -> DirectoryBuilder<L, H> {
        DirectoryBuilder::Immutable(self.as_immutable())
    }

    pub fn ptr_eq(&self, other: &SharedDirectory<L, H>) -> bool {
        Arc::ptr_eq(&self.inner, &other.inner)
    }
}

impl<L, H> SharedDirectory<L, H>
where
    L: Clone,
    H: DirectoryDigest,
{
    pub fn into_entries<C>(self) -> C
    where
        C: FromIterator<(FileNameBuf, DirectoryEntry<DirectoryBuilder<L, H>, L>)>,
    {
        self.entries()
            .into_iter()
            .map(|(k, v)| (k.clone(), v.clone().map_dir(|v| v.into_builder())))
            .collect()
    }
}

impl_fingerprinted_directory!(SharedDirectory);


/*
 * 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(test)]

use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;

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

use super::*;
use crate::fs::paths::file_name::FileName;
use crate::fs::paths::forward_rel_path::ForwardRelativePath;

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

pub struct TestHasher;

#[derive(Clone, Dupe, Debug, Eq, PartialEq, Hash, Allocative, Display)]
struct TestDigest(u64);

impl DirectoryDigest for TestDigest {}

impl DirectoryHasher<NopEntry, TestDigest> for TestHasher {
    fn hash_entries<'a, D, I>(&self, entries: I) -> TestDigest
    where
        I: IntoIterator<Item = (&'a FileName, DirectoryEntry<&'a D, &'a NopEntry>)>,
        D: FingerprintedDirectory<NopEntry, TestDigest> + 'a,
    {
        let mut hasher = DefaultHasher::new();

        let mut entries = entries
            .into_iter()
            .map(|(name, entry)| {
                let entry = entry.map_dir(|d| d.fingerprint());
                (name, entry)
            })
            .collect::<Vec<_>>();
        entries.sort_by_key(|(name, _)| *name);

        entries.hash(&mut hasher);
        TestDigest(hasher.finish())
    }
}

type TestDirectoryBuilder = DirectoryBuilder<NopEntry, TestDigest>;
type NoHasherDirectoryBuilder = DirectoryBuilder<NopEntry, NoDigest>;

fn path<'a>(s: &'a str) -> &'a ForwardRelativePath {
    ForwardRelativePath::unchecked_new(s)
}

#[test]
fn test_insert() -> anyhow::Result<()> {
    let mut b = NoHasherDirectoryBuilder::empty();

    assert_matches!(
        b.insert(path("a/b"), DirectoryEntry::Leaf(NopEntry)),
        Ok(None)
    );

    assert_matches!(
        b.insert(path("a/b/c"), DirectoryEntry::Leaf(NopEntry)),
        Err(DirectoryInsertError::CannotTraverseLeaf { path }) => {
            assert_eq!(path.to_string(), "a/b");
        }
    );

    assert_matches!(
        b.insert(path("a"), DirectoryEntry::Leaf(NopEntry)),
        Ok(Some(DirectoryEntry::Dir(..)))
    );

    Ok(())
}

#[test]
fn test_walk() -> anyhow::Result<()> {
    let mut b = TestDirectoryBuilder::empty();
    b.insert(path("a/b"), DirectoryEntry::Leaf(NopEntry))?;
    b.insert(
        path("b"),
        DirectoryEntry::Dir(TestDirectoryBuilder::empty()),
    )?;

    {
        let mut it = b.ordered_walk().with_paths();

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("a"))
        );

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("a/b"))
        );

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("b"))
        );

        assert_matches!(it.next(), None);
    }

    {
        let it = b.unordered_walk().with_paths();
        let mut collected = it.collect::<Vec<_>>();
        collected.sort_by_key(|(name, _)| name.clone());
        let mut it = collected.into_iter();

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("a"))
        );

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("a/b"))
        );

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("b"))
        );

        assert_matches!(it.next(), None);
    }

    let b = b.fingerprint(&TestHasher);

    {
        let mut it = b.fingerprinted_ordered_walk().with_paths();

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("a"))
        );

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("a/b"))
        );

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("b"))
        );

        assert_matches!(it.next(), None);
    }

    {
        let it = b.fingerprinted_unordered_walk().with_paths();
        let mut collected = it.collect::<Vec<_>>();
        collected.sort_by_key(|(name, _)| name.clone());
        let mut it = collected.into_iter();

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("a"))
        );

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("a/b"))
        );

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p, path("b"))
        );

        assert_matches!(it.next(), None);
    }

    {
        let mut walk = b.fingerprinted_ordered_walk();

        assert_matches!(
            walk.next(),
            Some((p, _)) => assert_eq!(p.get(), path("a"))
        );

        assert_matches!(
            walk.next(),
            Some((p, _)) => assert_eq!(p.get(), path("a/b"))
        );

        assert_matches!(
            walk.next(),
            Some((p, _)) => assert_eq!(p.get(), path("b"))
        );

        assert_matches!(walk.next(), None);
    }

    {
        // Test that the API we envision does compile.
        let mut expected_paths = vec![path("a"), path("a/b"), path("b")].into_iter();
        let mut walk = b.fingerprinted_ordered_walk();
        while let Some((p, _)) = walk.next() {
            assert_eq!(p.get(), expected_paths.next().unwrap());
        }
    }

    Ok(())
}

#[test]
fn test_merge() -> anyhow::Result<()> {
    let mut a = TestDirectoryBuilder::empty();
    a.insert(path("a/b"), DirectoryEntry::Leaf(NopEntry))?;

    let mut b = TestDirectoryBuilder::empty();
    b.insert(path("a/c"), DirectoryEntry::Leaf(NopEntry))?;

    a.merge(b)?;

    let mut it = a.ordered_walk().with_paths();

    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a"))
    );

    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a/b"))
    );

    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a/c"))
    );

    assert_matches!(it.next(), None);

    Ok(())
}

#[test]
fn test_merge_overwrite() -> anyhow::Result<()> {
    let mut a = TestDirectoryBuilder::empty();
    a.insert(path("a/b"), DirectoryEntry::Leaf(NopEntry))?;

    let mut b = TestDirectoryBuilder::empty();
    b.insert(path("a"), DirectoryEntry::Leaf(NopEntry))?;

    a.merge(b)?;

    Ok(())
}

#[test]
fn test_merge_conflict() -> anyhow::Result<()> {
    let mut a = TestDirectoryBuilder::empty();
    a.insert(path("a"), DirectoryEntry::Leaf(NopEntry))?;

    let mut b = TestDirectoryBuilder::empty();
    b.insert(path("a/b"), DirectoryEntry::Leaf(NopEntry))?;

    assert_matches!(
        a.merge(b),
        Err(DirectoryMergeError::CannotTraverseLeaf { path }) => {
            assert_eq!(path.to_string(), "a");
        }
    );

    Ok(())
}

#[test]
fn test_copy_on_write() -> anyhow::Result<()> {
    let empty = TestDirectoryBuilder::empty().fingerprint(&TestHasher);

    let mut a = TestDirectoryBuilder::empty();
    a.insert(path("a"), DirectoryEntry::Dir(empty.into_builder()))?;

    a.insert(path("a/b"), DirectoryEntry::Leaf(NopEntry))?;

    let mut it = a.ordered_walk().with_paths();

    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a"))
    );

    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a/b"))
    );

    Ok(())
}

#[test]
fn test_find() -> anyhow::Result<()> {
    let mut a = TestDirectoryBuilder::empty();
    a.insert(path("a/b/c"), DirectoryEntry::Leaf(NopEntry))?;

    assert_matches!(find(&a, path("a/b/c")), Ok(Some(DirectoryEntry::Leaf(..))));

    assert_matches!(find(&a, path("a/b")), Ok(Some(DirectoryEntry::Dir(..))));

    Ok(())
}

#[test]
fn test_find_prefix() -> anyhow::Result<()> {
    let mut a = TestDirectoryBuilder::empty();
    a.insert(path("a/b/c"), DirectoryEntry::Leaf(NopEntry))?;

    assert_matches!(
        find_prefix(&a, path("a/b/c")),
        Ok(Some((DirectoryEntry::Leaf(..), None)))
    );
    assert_matches!(
        find_prefix(&a, path("a/b")),
        Ok(Some((DirectoryEntry::Dir(..), None)))
    );

    assert_matches!(
        find_prefix(&a, path("a/b/c/d")),
        Ok(Some((DirectoryEntry::Leaf(..), Some(rest)))) => {
            assert_eq!(rest, path("d"));
        }
    );
    assert_matches!(
        find_prefix(&a, path("a/b/c/d/e")),
        Ok(Some((DirectoryEntry::Leaf(..), Some(rest)))) => {
            assert_eq!(rest, path("d/e"));
        }
    );

    Ok(())
}

#[test]
fn test_find_mut() -> anyhow::Result<()> {
    // Fewer tests than test_find since under the hood it's the exact same implementation.
    let mut a = TestDirectoryBuilder::empty();
    a.insert(path("a/b/c"), DirectoryEntry::Leaf(NopEntry))?;

    assert_matches!(
        find_mut(&mut a, path("a/b/c")),
        Ok(Some(DirectoryEntry::Leaf(..)))
    );

    assert_matches!(
        find_prefix_mut(&mut a, path("a/b/c/d")),
        Ok(Some((DirectoryEntry::Leaf(..), Some(rest)))) => {
            assert_eq!(rest, path("d"));
        }
    );

    Ok(())
}

#[test]
fn test_search() -> anyhow::Result<()> {
    let mut b = TestDirectoryBuilder::empty();
    b.insert(path("a/b"), DirectoryEntry::Leaf(NopEntry))?;
    b.insert(path("b/c"), DirectoryEntry::Leaf(NopEntry))?;
    let d = b.fingerprint(&TestHasher);

    {
        let mut selector = DirectorySelector::empty();
        selector.select(path("a/b"));

        let mut it = selector.fingerprinted_ordered_search(&d).with_paths();

        assert_matches!(
            it.next(),
            Some((p, Ok(DirectoryEntry::Leaf(..)))) => assert_eq!(p, path("a/b"))
        );
        assert_matches!(it.next(), None)
    }

    {
        let mut selector = DirectorySelector::empty();
        selector.select(path("a/b/c"));
        selector.select(path("b/c"));

        let mut it = selector.fingerprinted_ordered_search(&d).with_paths();

        assert_matches!(
            it.next(),
            Some((p, Err(DirectorySearchError::CannotTraverseLeaf { .. }))) => assert_eq!(p, path("a/b"))
        );
        assert_matches!(
            it.next(),
            Some((p, Ok(DirectoryEntry::Leaf(..)))) => assert_eq!(p, path("b/c"))
        );
        assert_matches!(it.next(), None)
    }

    {
        let mut selector = DirectorySelector::empty();
        selector.select(path("a"));

        let mut it = selector.fingerprinted_ordered_search(&d).with_paths();
        assert_matches!(
            it.next(),
            Some((p, Ok(DirectoryEntry::Dir(..)))) => assert_eq!(p, path("a"))
        );
    }

    Ok(())
}

#[test]
fn test_filter() -> anyhow::Result<()> {
    let mut b = TestDirectoryBuilder::empty();
    b.insert(path("a/aa"), DirectoryEntry::Leaf(NopEntry))?;
    b.insert(path("a/a"), DirectoryEntry::Leaf(NopEntry))?;
    b.insert(path("b/b"), DirectoryEntry::Leaf(NopEntry))?;
    b.insert(path("b/bb"), DirectoryEntry::Leaf(NopEntry))?;
    b.insert(path("c/c"), DirectoryEntry::Leaf(NopEntry))?;
    b.insert(path("c/cc"), DirectoryEntry::Leaf(NopEntry))?;

    let mut selector = DirectorySelector::empty();
    selector.select(path("a"));
    selector.select(path("b/b"));

    selector.filter(&mut b)?;

    let mut it = b.ordered_walk().with_paths();

    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a"))
    );
    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a/a"))
    );
    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a/aa"))
    );
    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("b"))
    );
    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("b/b"))
    );

    assert_matches!(it.next(), None);

    Ok(())
}

#[test]
fn test_entry_walk() {
    {
        let e = DirectoryEntry::<TestDirectoryBuilder, _>::Leaf(NopEntry);
        let mut it = ordered_entry_walk(e.as_ref());

        assert_matches!(
            it.next(),
            Some((p, _)) => assert_eq!(p.get(), path(""))
        );

        assert_matches!(it.next(), None);
    }

    {
        let e = DirectoryEntry::<_, NopEntry>::Dir(TestDirectoryBuilder::empty());
        let mut it = ordered_entry_walk(e.as_ref());

        assert_matches!(it.next(), None);
    }
}

#[test]
fn test_bounds() {
    fn assert_impls_debug<T: std::fmt::Debug>() {}
    fn assert_impls_clone<T: std::clone::Clone>() {}
    fn assert_impls_eq<T: std::cmp::Eq>() {}

    assert_impls_debug::<TestDirectoryBuilder>();
    assert_impls_clone::<TestDirectoryBuilder>();
    assert_impls_eq::<DirectoryEntry<ExclusiveDirectory<NopEntry, TestDigest>, NopEntry>>();
    assert_impls_eq::<DirectoryEntry<SharedDirectory<NopEntry, TestDigest>, NopEntry>>();
    assert_impls_eq::<DirectoryEntry<ImmutableDirectory<NopEntry, TestDigest>, NopEntry>>();
}

#[test]
fn test_mkdir() -> anyhow::Result<()> {
    let mut b = TestDirectoryBuilder::empty();
    b.mkdir(path("foo/bar"))?;
    b.mkdir(path("foo"))?;

    let mut it = b.ordered_walk().with_paths();

    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("foo"))
    );

    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("foo/bar"))
    );

    assert_matches!(it.next(), None);

    Ok(())
}

#[test]
fn test_mkdir_overwrite() -> anyhow::Result<()> {
    let mut b = TestDirectoryBuilder::empty();
    b.insert(path("a/b"), DirectoryEntry::Leaf(NopEntry))?;

    assert_matches!(
        b.mkdir(path("a/b/c")),
        Err(DirectoryMkdirError::CannotTraverseLeaf { path }) => {
            assert_eq!(path.to_string(), "a/b");
        }
    );

    Ok(())
}

#[test]
fn test_directory_interner() -> anyhow::Result<()> {
    let interner = DashMapDirectoryInterner::new();

    let d1 = {
        let mut b = TestDirectoryBuilder::empty();
        b.insert(path("a/b"), DirectoryEntry::Leaf(NopEntry))?;
        b.fingerprint(&TestHasher).shared(&interner)
    };

    let d2 = {
        let mut b = TestDirectoryBuilder::empty();
        b.insert(path("a/b"), DirectoryEntry::Leaf(NopEntry))?;
        b.fingerprint(&TestHasher).shared(&interner)
    };

    assert!(d1.ptr_eq(&d2));

    assert_eq!(interner.len(), 2);

    drop(d1);
    assert_eq!(interner.len(), 2);

    drop(d2);
    assert_eq!(interner.len(), 0);

    Ok(())
}

#[test]
fn test_directory_interner_deep() -> anyhow::Result<()> {
    let interner = DashMapDirectoryInterner::new();

    let d1 = {
        let mut b = TestDirectoryBuilder::empty();
        b.insert(path("a/b"), DirectoryEntry::Leaf(NopEntry))?;
        b.fingerprint(&TestHasher).shared(&interner)
    };

    let _d2 = {
        let mut b = TestDirectoryBuilder::empty();
        b.insert(path("b"), DirectoryEntry::Leaf(NopEntry))?;
        b.fingerprint(&TestHasher).shared(&interner)
    };

    assert_eq!(interner.len(), 2);

    drop(d1);

    // Now we only have d2.
    assert_eq!(interner.len(), 1);

    Ok(())
}

#[test]
fn test_filter_continues_on_error() -> anyhow::Result<()> {
    let mut b = TestDirectoryBuilder::empty();
    b.insert(path("a/aa/aaa"), DirectoryEntry::Leaf(NopEntry))?;
    b.insert(path("a/aa/bbb"), DirectoryEntry::Leaf(NopEntry))?;
    b.insert(path("a/bb"), DirectoryEntry::Leaf(NopEntry))?;
    b.insert(path("c"), DirectoryEntry::Leaf(NopEntry))?;

    let mut selector = DirectorySelector::empty();
    selector.select(path("a/aa"));
    selector.select(path("c/d"));

    assert_matches!(selector.filter(&mut b), Err(..));

    let mut it = b.ordered_walk().with_paths();

    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a"))
    );
    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a/aa"))
    );
    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a/aa/aaa"))
    );
    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("a/aa/bbb"))
    );
    assert_matches!(
        it.next(),
        Some((p, _)) => assert_eq!(p, path("c"))
    );

    assert_matches!(it.next(), None);

    Ok(())
}


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

use derivative::Derivative;

use super::Directory;
use super::DirectoryEntries;
use super::DirectoryEntry;
use super::DirectoryIterator;
use super::DirectoryIteratorPathAccessor;
use super::DirectoryIteratorPathStack;
use super::FingerprintedDirectory;
use super::FingerprintedDirectoryEntries;
use super::FingerprintedOrderedDirectoryEntries;
use super::OrderedDirectoryEntries;
use crate::fs::paths::forward_rel_path::ForwardRelativePathBuf;

macro_rules! impl_directory_walk {
    (
        $walk_ty: ident,
        $entry_walk_fn: ident,
        $dir_ty: ident,
        $entries_ty: ident,
        $entries_method: ident,
        $mod: ident,
    ) => {
        mod $mod {
            use $crate::directory::DirectoryEntry;
            use $crate::directory::DirectoryIterator;
            use $crate::directory::DirectoryIteratorPathAccessor;
            use $crate::directory::DirectoryIteratorPathStack;

            use super::$dir_ty;
            use super::$entries_ty;
            use super::DirectoryEntryWalk;
            use crate::fs::paths::file_name::FileName;

            struct WalkFrame<'a, L, H> {
                name: Option<&'a FileName>,
                entries: $entries_ty<'a, L, H>,
            }

            pub struct $walk_ty<'a, L, H> {
                stack: Vec<WalkFrame<'a, L, H>>,
            }

            impl<'a, L, H> $walk_ty<'a, L, H> {
                pub fn new<D>(root: &'a D) -> Self
                where
                    D: $dir_ty<L, H> + ?Sized,
                {
                    Self {
                        stack: vec![WalkFrame {
                            name: None,
                            entries: $entries_ty::from($dir_ty::$entries_method(root)),
                        }],
                    }
                }
            }

            impl<'a, L, H> DirectoryIterator for $walk_ty<'a, L, H> {
                type PathStack = Self;
                type Item = DirectoryEntry<&'a dyn $dir_ty<L, H>, &'a L>;

                fn next<'b>(
                    &'b mut self,
                ) -> Option<(DirectoryIteratorPathAccessor<'b, Self>, Self::Item)> {
                    loop {
                        let frame = self.stack.last_mut()?;

                        if let Some((name, entry)) = frame.entries.next() {
                            let leaf_name = match entry {
                                DirectoryEntry::Dir(dir) => {
                                    self.stack.push(WalkFrame {
                                        name: Some(name),
                                        entries: $entries_ty::from($dir_ty::$entries_method(dir)),
                                    });
                                    None
                                }
                                DirectoryEntry::Leaf(..) => Some(name),
                            };

                            return Some((
                                DirectoryIteratorPathAccessor {
                                    leaf: leaf_name,
                                    stack: self,
                                },
                                entry,
                            ));
                        }

                        self.stack.pop();
                    }
                }
            }

            impl<'a, L, H> DirectoryIteratorPathStack for $walk_ty<'a, L, H> {
                fn for_each_path<'this, F>(&'this self, mut f: F)
                where
                    F: FnMut(&'this FileName),
                {
                    let it = self.stack.iter().filter_map(|frame| match frame {
                        WalkFrame { name, .. } => name.as_deref(),
                    });

                    for path in it {
                        f(path);
                    }
                }
            }

            pub fn $entry_walk_fn<'a, D, L, H>(
                entry: DirectoryEntry<&'a D, &'a L>,
            ) -> DirectoryEntryWalk<'a, L, $walk_ty<'a, L, H>>
            where
                D: $dir_ty<L, H> + ?Sized,
            {
                match entry {
                    DirectoryEntry::Dir(d) => DirectoryEntryWalk::Dir {
                        inner: $walk_ty::new(d),
                    },
                    DirectoryEntry::Leaf(d) => DirectoryEntryWalk::Leaf { entry: Some(d) },
                }
            }
        }

        pub use $mod::$entry_walk_fn;
        pub use $mod::$walk_ty;
    };
}

pub enum DirectoryEntryWalk<'a, L, I> {
    Dir { inner: I },
    Leaf { entry: Option<&'a L> },
}

impl<'a, D, L, I> DirectoryEntryWalk<'a, L, I>
where
    I: DirectoryIterator<Item = DirectoryEntry<D, &'a L>>,
    D: 'a,
{
    pub fn next<'this>(
        &'this mut self,
    ) -> Option<(
        DirectoryEntryWalkPathAccessor<'this, <I as DirectoryIterator>::PathStack>,
        DirectoryEntry<D, &'a L>,
    )> {
        match self {
            Self::Dir { inner } => {
                let (accessor, item) = inner.next()?;
                Some((
                    DirectoryEntryWalkPathAccessor {
                        inner: Some(accessor),
                    },
                    item,
                ))
            }
            Self::Leaf { entry } => {
                let entry = entry.take()?;
                Some((
                    DirectoryEntryWalkPathAccessor { inner: None },
                    DirectoryEntry::Leaf(entry),
                ))
            }
        }
    }
}

#[derive(Derivative)]
#[derivative(Debug(bound = "T: DirectoryIteratorPathStack"))]
pub struct DirectoryEntryWalkPathAccessor<'a, T> {
    inner: Option<DirectoryIteratorPathAccessor<'a, T>>,
}

impl<'a, T> DirectoryEntryWalkPathAccessor<'a, T>
where
    T: DirectoryIteratorPathStack,
{
    pub fn get(&self) -> ForwardRelativePathBuf {
        match self.inner.as_ref() {
            Some(i) => i.get(),
            None => ForwardRelativePathBuf::unchecked_new("".to_owned()),
        }
    }
}

impl_directory_walk!(
    FingerprintedUnorderedDirectoryWalk,
    fingerprinted_unordered_entry_walk,
    FingerprintedDirectory,
    FingerprintedDirectoryEntries,
    fingerprinted_entries,
    fingerprinted_unordered_directory_walk_impl,
);

impl_directory_walk!(
    UnorderedDirectoryWalk,
    unordered_entry_walk,
    Directory,
    DirectoryEntries,
    entries,
    unordered_directory_walk_impl,
);

impl_directory_walk!(
    FingerprintedOrderedDirectoryWalk,
    fingerprinted_ordered_entry_walk,
    FingerprintedDirectory,
    FingerprintedOrderedDirectoryEntries,
    fingerprinted_entries,
    fingerprinted_ordered_directory_walk_impl,
);

impl_directory_walk!(
    OrderedDirectoryWalk,
    ordered_entry_walk,
    Directory,
    OrderedDirectoryEntries,
    entries,
    ordered_directory_walk_impl,
);


/*
 * 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::buck_path::path::BuckPathRef;
use crate::cells::cell_path::CellPathRef;
use crate::cells::CellResolver;
use crate::fs::buck_out_path::BuckOutPath;
use crate::fs::buck_out_path::BuckOutPathResolver;
use crate::fs::project::ProjectRoot;
use crate::fs::project_rel_path::ProjectRelativePathBuf;

#[derive(Clone, Allocative)]
pub struct ArtifactFs {
    buck_path_resolver: CellResolver,
    buck_out_path_resolver: BuckOutPathResolver,
    project_filesystem: ProjectRoot,
}

impl ArtifactFs {
    pub fn new(
        buck_path_resolver: CellResolver,
        buck_out_path_resolver: BuckOutPathResolver,
        project_filesystem: ProjectRoot,
    ) -> Self {
        Self {
            buck_path_resolver,
            buck_out_path_resolver,
            project_filesystem,
        }
    }

    pub fn retrieve_unhashed_location(&self, path: &BuckOutPath) -> Option<ProjectRelativePathBuf> {
        self.buck_out_path_resolver.unhashed_gen(path)
    }

    pub fn resolve_build(&self, path: &BuckOutPath) -> ProjectRelativePathBuf {
        self.buck_out_path_resolver.resolve_gen(path)
    }

    pub fn resolve_cell_path(&self, path: CellPathRef) -> anyhow::Result<ProjectRelativePathBuf> {
        self.buck_path_resolver.resolve_path(path)
    }

    pub fn resolve_source(
        &self,
        source_artifact_path: BuckPathRef,
    ) -> anyhow::Result<ProjectRelativePathBuf> {
        self.buck_path_resolver
            .resolve_buck_path(source_artifact_path)
    }

    pub fn resolve_offline_output_cache_path(&self, path: &BuckOutPath) -> ProjectRelativePathBuf {
        self.buck_out_path_resolver.resolve_offline_cache(path)
    }

    pub fn fs(&self) -> &ProjectRoot {
        &self.project_filesystem
    }

    pub fn buck_out_path_resolver(&self) -> &BuckOutPathResolver {
        &self.buck_out_path_resolver
    }

    pub fn buck_path_resolver(&self) -> &CellResolver {
        &self.buck_path_resolver
    }
}


/*
 * 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 tokio::fs::File;

use crate::fs::paths::abs_path::AbsPath;
use crate::io_counters::IoCounterKey;

pub async fn open<P: AsRef<AbsPath>>(path: P) -> anyhow::Result<File> {
    let _guard = IoCounterKey::Read.guard();
    tokio::fs::File::open(path.as_ref().as_maybe_relativized())
        .await
        .with_context(|| format!("open({})", path.as_ref().display()))
}


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

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

use crate::base_deferred_key::BaseDeferredKey;
use crate::category::Category;
use crate::fs::paths::forward_rel_path::ForwardRelativePath;
use crate::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use crate::fs::project_rel_path::ProjectRelativePath;
use crate::fs::project_rel_path::ProjectRelativePathBuf;

#[derive(Clone, Debug, Display, Allocative, Hash, Eq, PartialEq)]
#[display(fmt = "({})/{}", owner, "path.as_str()")]
struct BuckOutPathData {
    /// The owner responsible for creating this path.
    owner: BaseDeferredKey,
    /// The unique identifier for this action (only set for outputs inside dynamic actions)
    action_key: Option<Arc<str>>,
    /// The path relative to that target.
    path: ForwardRelativePathBuf,
}

/// Represents a resolvable path corresponding to outputs of rules that are part
/// of a `Package`. The `BuckOutPath` refers to only the outputs of rules,
/// not the repo sources.
///
/// This structure contains a target label for generating the base of the path (base
/// path), and a `ForwardRelativePath` that represents the specific output
/// location relative to the 'base path'.
///
/// For `Eq`/`Hash` we want the equality to be based on the path on disk,
/// regardless of how the path looks to the user. Therefore we ignore the `hidden` field.
#[derive(Clone, Dupe, Debug, Display, Hash, PartialEq, Eq, Allocative)]
pub struct BuckOutPath(Arc<BuckOutPathData>);

impl BuckOutPath {
    pub fn new(owner: BaseDeferredKey, path: ForwardRelativePathBuf) -> Self {
        Self::with_action_key(owner, path, None)
    }

    pub fn with_action_key(
        owner: BaseDeferredKey,
        path: ForwardRelativePathBuf,
        action_key: Option<Arc<str>>,
    ) -> Self {
        BuckOutPath(Arc::new(BuckOutPathData {
            owner,
            action_key,
            path,
        }))
    }

    pub fn owner(&self) -> &BaseDeferredKey {
        &self.0.owner
    }

    pub fn action_key(&self) -> Option<&str> {
        self.0.action_key.as_deref()
    }

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

#[derive(Clone, Debug, Display, Eq, PartialEq)]
#[display(fmt = "tmp/({})/{}", owner, "path.as_str()")]
pub struct BuckOutScratchPath {
    /// The deferred responsible for creating this path.
    owner: BaseDeferredKey,
    /// The path relative to that target.
    path: ForwardRelativePathBuf,
}

impl BuckOutScratchPath {
    /// Returning an Err from this function is considered an internal error - we try
    /// really hard to normalise anything the user supplies.
    pub fn new(
        owner: BaseDeferredKey,
        category: &Category,
        identifier: Option<&str>,
    ) -> anyhow::Result<Self> {
        const MAKE_SENSIBLE_PREFIX: &str = "_buck_";
        // Windows has MAX_PATH limit (260 chars).
        const LENGTH_THRESHOLD: usize = 50;

        /// A path is sensible if it's going to produce a ForwardRelativePath that works and isn't too long.
        /// These are heuristics to make it more likely that paths that are not sensible are replaced, not guarantees.
        fn is_sensible(x: &str) -> Option<&ForwardRelativePath> {
            // We need a way to make something sensible, so disallow this prefix
            if x.starts_with(MAKE_SENSIBLE_PREFIX) {
                return None;
            }
            // If things are too long they don't make good paths
            if x.len() > LENGTH_THRESHOLD {
                return None;
            }

            // If things have weird characters in they won't make a good path.
            // Mostly based off Windows banned characters, with some additions.
            // We do allow `/` and `\` as they can just be part of the path.
            // We exclude spaces because they cause issues with some command line tools.
            const BAD_CHARS: &str = "<>;:\"'\n\r\t?* ";
            for c in BAD_CHARS.as_bytes() {
                if x.as_bytes().contains(c) {
                    return None;
                }
            }
            ForwardRelativePath::new(x).ok()
        }

        let path = ForwardRelativePath::new(category.as_str())?;
        let path = match identifier {
            Some(v) => {
                if let Some(v) = is_sensible(v) {
                    path.join_normalized(v)?
                } else {
                    // FIXME: Should this be a crypto hasher?
                    let mut hasher = DefaultHasher::new();
                    v.hash(&mut hasher);
                    let output_hash = format!("{}{:x}", MAKE_SENSIBLE_PREFIX, hasher.finish());
                    path.join_normalized(ForwardRelativePath::new(&output_hash)?)?
                }
            }
            _ => path.to_buf(),
        };

        Ok(Self { owner, path })
    }
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct BuckOutTestPath {
    /// A base path. This is primarily useful when e.g. set of tests should all be in the same
    /// path.
    base: ForwardRelativePathBuf,

    /// A path relative to the base path.
    path: ForwardRelativePathBuf,
}

impl BuckOutTestPath {
    pub fn new(base: ForwardRelativePathBuf, path: ForwardRelativePathBuf) -> Self {
        BuckOutTestPath { base, path }
    }

    pub fn into_path(self) -> ForwardRelativePathBuf {
        self.path
    }
}

#[derive(Clone, Allocative)]
pub struct BuckOutPathResolver(ProjectRelativePathBuf);

impl BuckOutPathResolver {
    /// creates a 'BuckOutPathResolver' that will resolve outputs to the provided buck-out root.
    /// If not set, buck_out defaults to "buck-out/v2"
    pub fn new(buck_out: ProjectRelativePathBuf) -> Self {
        BuckOutPathResolver(buck_out)
    }

    /// Returns the buck-out root.
    pub fn root(&self) -> &ProjectRelativePath {
        &self.0
    }

    /// Resolves a 'BuckOutPath' into a 'ProjectRelativePath' based on the base
    /// directory, target and cell.
    pub fn resolve_gen(&self, path: &BuckOutPath) -> ProjectRelativePathBuf {
        self.prefixed_path_for_owner(
            ForwardRelativePath::unchecked_new("gen"),
            path.owner(),
            path.action_key(),
            path.path(),
        )
    }

    pub fn resolve_offline_cache(&self, path: &BuckOutPath) -> ProjectRelativePathBuf {
        self.prefixed_path_for_owner(
            ForwardRelativePath::unchecked_new("offline-cache"),
            path.owner(),
            path.action_key(),
            path.path(),
        )
    }

    pub fn resolve_scratch(&self, path: &BuckOutScratchPath) -> ProjectRelativePathBuf {
        self.prefixed_path_for_owner(
            ForwardRelativePath::unchecked_new("tmp"),
            &path.owner,
            None,
            &path.path,
        )
    }

    /// Resolve a test path
    pub fn resolve_test(&self, path: &BuckOutTestPath) -> ProjectRelativePathBuf {
        ProjectRelativePathBuf::from(ForwardRelativePathBuf::concat([
            self.0.as_forward_relative_path(),
            ForwardRelativePath::new("test").unwrap(),
            &path.base,
            &path.path,
        ]))
    }

    fn prefixed_path_for_owner(
        &self,
        prefix: &ForwardRelativePath,
        owner: &BaseDeferredKey,
        action_key: Option<&str>,
        path: &ForwardRelativePath,
    ) -> ProjectRelativePathBuf {
        owner.make_hashed_path(&self.0, prefix, action_key, path)
    }

    /// This function returns the exact location of the symlink of a given target.
    /// Note that it (deliberately) ignores the configuration and takes no action_key information.
    /// A `None` implies there is no unhashed location.
    pub fn unhashed_gen(&self, path: &BuckOutPath) -> Option<ProjectRelativePathBuf> {
        Some(ProjectRelativePathBuf::from(
            ForwardRelativePathBuf::concat([
                self.0.as_ref(),
                ForwardRelativePath::unchecked_new("gen"),
                &path.0.owner.make_unhashed_path()?,
                path.path(),
            ]),
        ))
    }
}

#[cfg(test)]
mod tests {

    use std::sync::Arc;

    use dupe::Dupe;
    use regex::Regex;

    use crate::base_deferred_key::BaseDeferredKey;
    use crate::buck_path::path::BuckPath;
    use crate::category::Category;
    use crate::cells::cell_root_path::CellRootPathBuf;
    use crate::cells::name::CellName;
    use crate::cells::paths::CellRelativePath;
    use crate::cells::CellResolver;
    use crate::configuration::data::ConfigurationData;
    use crate::fs::buck_out_path::BuckOutPath;
    use crate::fs::buck_out_path::BuckOutPathResolver;
    use crate::fs::buck_out_path::BuckOutScratchPath;
    use crate::fs::paths::forward_rel_path::ForwardRelativePathBuf;
    use crate::fs::project_rel_path::ProjectRelativePathBuf;
    use crate::package::package_relative_path::PackageRelativePathBuf;
    use crate::package::PackageLabel;
    use crate::target::label::TargetLabel;
    use crate::target::name::TargetNameRef;

    #[test]
    fn buck_path_resolves() -> anyhow::Result<()> {
        let cell_resolver = CellResolver::testing_with_name_and_path(
            CellName::testing_new("foo"),
            CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new("bar-cell".into())),
        );

        let resolved = cell_resolver.resolve_buck_path(
            BuckPath::testing_new(
                PackageLabel::new(
                    CellName::testing_new("foo"),
                    CellRelativePath::unchecked_new("baz-package"),
                ),
                PackageRelativePathBuf::unchecked_new("faz.file".into()),
            )
            .as_ref(),
        )?;

        assert_eq!(
            ProjectRelativePathBuf::unchecked_new("bar-cell/baz-package/faz.file".into()),
            resolved
        );

        assert_eq!(
            cell_resolver
                .resolve_buck_path(
                    BuckPath::testing_new(
                        PackageLabel::new(
                            CellName::testing_new("none_existent"),
                            CellRelativePath::unchecked_new("baz")
                        ),
                        PackageRelativePathBuf::unchecked_new("fazx".into())
                    )
                    .as_ref()
                )
                .is_err(),
            true
        );

        Ok(())
    }

    #[test]
    fn buck_output_path_resolves() -> anyhow::Result<()> {
        let path_resolver = BuckOutPathResolver::new(ProjectRelativePathBuf::unchecked_new(
            "base/buck-out/v2".into(),
        ));

        let pkg = PackageLabel::new(
            CellName::testing_new("foo"),
            CellRelativePath::unchecked_new("baz-package"),
        );
        let target = TargetLabel::new(pkg, TargetNameRef::unchecked_new("target-name"));
        let cfg_target = target.configure(ConfigurationData::testing_new());

        let resolved = path_resolver.resolve_gen(&BuckOutPath::new(
            BaseDeferredKey::TargetLabel(cfg_target),
            ForwardRelativePathBuf::unchecked_new("faz.file".into()),
        ));

        let re =
            Regex::new("base/buck-out/v2/gen/foo/[0-9a-z]+/baz-package/__target-name__/faz.file")?;
        assert!(
            re.is_match(resolved.as_str()),
            "{}.is_match({})",
            re,
            resolved
        );
        Ok(())
    }

    #[test]
    fn buck_target_output_path_resolves() -> anyhow::Result<()> {
        let path_resolver =
            BuckOutPathResolver::new(ProjectRelativePathBuf::unchecked_new("buck-out".into()));

        let pkg = PackageLabel::new(
            CellName::testing_new("foo"),
            CellRelativePath::unchecked_new("baz-package"),
        );
        let target = TargetLabel::new(pkg, TargetNameRef::unchecked_new("target-name"));
        let cfg_target = target.configure(ConfigurationData::testing_new());

        let resolved = path_resolver.resolve_gen(&BuckOutPath::new(
            BaseDeferredKey::TargetLabel(cfg_target.dupe()),
            ForwardRelativePathBuf::unchecked_new("quux".to_owned()),
        ));

        let re = Regex::new("buck-out/gen/foo/[0-9a-z]+/baz-package/__target-name__/quux")?;
        assert!(
            re.is_match(resolved.as_str()),
            "{}.is_match({})",
            re,
            resolved
        );

        let path = BuckOutPath::with_action_key(
            BaseDeferredKey::TargetLabel(cfg_target),
            ForwardRelativePathBuf::unchecked_new("quux".to_owned()),
            Some(Arc::from("xxx")),
        );
        let resolved = path_resolver.resolve_gen(&path);

        let re = Regex::new(
            "buck-out/gen/foo/[0-9a-z]+/baz-package/__target-name__/__action__xxx__/quux",
        )?;
        assert!(
            re.is_match(resolved.as_str()),
            "{}.is_match({})",
            re,
            resolved
        );

        Ok(())
    }

    #[test]
    fn test_scratch_path_is_sensible() {
        let pkg = PackageLabel::new(
            CellName::testing_new("foo"),
            CellRelativePath::unchecked_new("baz-package"),
        );
        let target = TargetLabel::new(pkg, TargetNameRef::unchecked_new("target-name"));
        let cfg_target = target.configure(ConfigurationData::testing_new());
        let category = Category::try_from("category").unwrap();

        // We expect these all to be valid paths, avoiding weird things we throw in
        BuckOutScratchPath::new(
            BaseDeferredKey::TargetLabel(cfg_target.dupe()),
            &category,
            None,
        )
        .unwrap();

        let mk = move |s| {
            BuckOutScratchPath::new(
                BaseDeferredKey::TargetLabel(cfg_target.dupe()),
                &category,
                Some(s),
            )
            .unwrap()
            .path
            .as_str()
            .to_owned()
        };

        // We want to preserve reasonable strings
        assert!(mk("hello").ends_with("/hello"));
        assert!(mk("hello/slash").ends_with("/hello/slash"));
        assert!(mk("hello_underscore").ends_with("/hello_underscore"));
        // But hide silly ones
        assert!(!mk("<>weird").contains("<>"));
        assert!(!mk("a space").contains(' '));
        let long_string = str::repeat("q", 10000);
        assert!(!mk(&long_string).contains(&long_string));
        assert!(!mk("foo/../bar").contains(".."));
        // But that we do create different versions
        assert_eq!(mk("normal"), mk("normal"));
        assert_eq!(mk("weird <>"), mk("weird <>"));
        assert_ne!(mk("weird <>"), mk("weird ><"))
    }
}


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

use std::path::Path;

use anyhow::Context as _;
use once_cell::sync::OnceCell;

use crate::fs::paths::abs_path::AbsPathBuf;

#[derive(Debug, thiserror::Error)]
enum CwdError {
    #[error("cwd is already set to `{}`", _0.display())]
    CwdAlreadySet(AbsPathBuf),
}

static CWD: OnceCell<AbsPathBuf> = OnceCell::new();

/// Promise the cwd will not change going forward. This should only be called once.
pub fn cwd_will_not_change() -> anyhow::Result<()> {
    let cwd = std::env::current_dir().context("Failed to get cwd")?;
    let cwd = AbsPathBuf::new(cwd).context("Cwd is not absolute")?;
    CWD.set(cwd)
        .ok()
        .context("cwd_will_not_change was called twice")?;
    Ok(())
}

pub(crate) fn assert_cwd_is_not_set() -> anyhow::Result<()> {
    if let Some(cwd) = CWD.get() {
        return Err(CwdError::CwdAlreadySet(cwd.clone()).into());
    }
    Ok(())
}

pub(crate) fn maybe_relativize(path: &Path) -> &Path {
    maybe_relativize_impl(path, &CWD)
}

fn maybe_relativize_impl<'a>(path: &'a Path, cell: &'_ OnceCell<AbsPathBuf>) -> &'a Path {
    if let Some(cwd) = cell.get() {
        if let Ok(path) = path.strip_prefix(cwd) {
            if path == Path::new("") {
                return Path::new(".");
            }
            return path;
        }
    }

    path
}

pub(crate) fn maybe_relativize_str(path: &str) -> &str {
    maybe_relativize_str_impl(path, &CWD)
}

fn maybe_relativize_str_impl<'a>(path: &'a str, cell: &'_ OnceCell<AbsPathBuf>) -> &'a str {
    if let Some(path) = maybe_relativize_impl(Path::new(path), cell).to_str() {
        return path;
    }

    path
}

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

    fn foo_bar() -> AbsPathBuf {
        if cfg!(windows) {
            AbsPathBuf::new("C:\\foo\\bar").unwrap()
        } else {
            AbsPathBuf::new("/foo/bar").unwrap()
        }
    }

    #[test]
    fn test_maybe_relativize() {
        let cell = OnceCell::new();
        let path = foo_bar().join("baz").into_path_buf();
        assert_eq!(maybe_relativize_impl(&path, &cell), path);
        cell.set(foo_bar()).unwrap();
        assert_eq!(maybe_relativize_impl(&path, &cell), Path::new("baz"));
        assert_eq!(maybe_relativize_impl(&foo_bar(), &cell), Path::new("."));
    }

    #[test]
    fn test_maybe_relativize_str() {
        let cell = OnceCell::new();
        let path = foo_bar().join("baz").to_str().unwrap().to_owned();
        assert_eq!(maybe_relativize_str_impl(&path, &cell), path);
        cell.set(foo_bar()).unwrap();
        assert_eq!(maybe_relativize_str_impl(&path, &cell), "baz");
        assert_eq!(
            maybe_relativize_str_impl(foo_bar().to_str().unwrap(), &cell),
            "."
        );
    }
}