/*
 * 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::env::VarError;
use std::str::FromStr;

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

pub struct EnvHelper<T> {
    convert: fn(&str) -> anyhow::Result<T>,
    var: &'static str,
    cell: OnceCell<Option<T>>,
}

impl<T> EnvHelper<T> {
    pub const fn with_converter(var: &'static str, convert: fn(&str) -> anyhow::Result<T>) -> Self {
        Self {
            convert,
            var,
            cell: OnceCell::new(),
        }
    }

    pub const fn new(var: &'static str) -> Self
    where
        T: FromStr,
        anyhow::Error: From<<T as FromStr>::Err>,
    {
        fn convert_from_str<T>(v: &str) -> anyhow::Result<T>
        where
            T: FromStr,
            anyhow::Error: From<<T as FromStr>::Err>,
        {
            Ok(T::from_str(v)?)
        }

        Self::with_converter(var, convert_from_str::<T>)
    }

    // This code does not really require `'static` lifetime.
    // `EnvHelper` caches computed value. When it is used like
    // `EnvHelper::new(...).get(...)`, it performs unnecessary work.
    // To avoid it, we require `'static` lifetime, to force placing `EnvHelper` in static variable.
    pub fn get(&'static self) -> anyhow::Result<Option<&T>> {
        let var = self.var;
        let convert = self.convert;

        self.cell
            .get_or_try_init(move || match env::var(var) {
                Ok(v) => {
                    tracing::info!("Env override found: ${} = {}", var, v);
                    Ok(Some((convert)(&v).map_err(anyhow::Error::from)?))
                }
                Err(VarError::NotPresent) => Ok(None),
                Err(VarError::NotUnicode(..)) => Err(anyhow::anyhow!("Variable is not unicode")),
            })
            .map(Option::as_ref)
            .with_context(|| format!("Invalid value for ${}", var))
    }

    pub fn get_copied(&'static self) -> anyhow::Result<Option<T>>
    where
        T: Copy,
    {
        Ok(self.get()?.copied())
    }
}


/*
 * 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::str::FromStr;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::Mutex;

use anyhow::Context;
use arc_swap::ArcSwapOption;
use once_cell::sync::OnceCell;
use starlark_map::small_set::SmallSet;
use thiserror::Error;

use crate::env_helper::EnvHelper;
use crate::is_open_source;

type StructuredErrorHandler = Box<
    dyn for<'a> Fn(&'a str, &anyhow::Error, (&'a str, u32, u32), StructuredErrorOptions)
        + Send
        + Sync
        + 'static,
>;

static HANDLER: OnceCell<StructuredErrorHandler> = OnceCell::new();

pub static BUCK2_HARD_ERROR_ENV_VAR: EnvHelper<String> = EnvHelper::new("BUCK2_HARD_ERROR");

static HARD_ERROR_CONFIG: HardErrorConfigHolder = HardErrorConfigHolder {
    config: ArcSwapOption::const_empty(),
};

static ALL_SOFT_ERROR_COUNTERS: Mutex<Vec<&'static AtomicUsize>> = Mutex::new(Vec::new());

/// Throw a "soft_error" i.e. one that is destined to become a hard error
/// in the near future. The macro lives in this crate to allow it be
/// made available everywhere. Calling programs are responsible for
/// calling initialize() to provide a handler for logging these soft_errors.
///
/// You should pass two arguments:
///
/// * The category string that will remain constant and identifies this specific soft error
///   (used to report as a key).
/// * The error is an `anyhow::Error` will in the future will be propagated as the error.
///
/// Soft errors from Meta internal runs can be viewed
/// [in logview](https://www.internalfb.com/logview/overview/buck2).
///
/// You'll get the error back as the Ok() value if it wasn't thrown, otherwise you get a Err() to
/// propagate.
#[macro_export]
macro_rules! soft_error(
    ($category:expr, $err:expr) => {
        $crate::soft_error!($category, $err,)
    };
    ($category:expr, $err:expr, $($k:ident : $v:expr),+) => {
        $crate::soft_error!($category, $err, $($k: $v,)*)
    };
    ($category:expr, $err:expr, $($k:ident : $v:expr ,)*) => { {
        static COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
        static ONCE: std::sync::Once = std::sync::Once::new();
        $crate::error::handle_soft_error(
            $category,
            $err,
            &COUNT,
            &ONCE,
            (file!(), line!(), column!()),
            $crate::error::StructuredErrorOptions {
                $($k: $v,)*
                ..Default::default()
            }
        )
    } };
);

/// Tag and report this error. Return said error.
#[macro_export]
macro_rules! tag_error(
    ($category:expr, $err:expr) => {
        $crate::tag_error!($category, $err,)
    };
    ($category:expr, $err:expr, $($k:ident : $v:expr),+) => {
        $crate::tag_error!($category, $err, $($k: $v,)*)
    };
    ($category:expr, $err:expr, $($k:ident : $v:expr ,)*) => {
        match $crate::soft_error!($category, $err, $($k: $v,)*) {
            Ok(err) => err,
            Err(err) => err,
        }
    };
);

/// If this result is an error, tag it, then return the result.
#[macro_export]
macro_rules! tag_result(
    ($category:expr, $res:expr) => {
        $crate::tag_result($category, $res,)
    };
    ($category:expr, $err:expr, $($k:ident : $v:expr),+) => {
        $crate::tag_result!($category, $err, $($k: $v,)*)
    };
    ($category:expr, $res:expr, $($k:ident : $v:expr ,)*) => {
        $res.map_err(|err| $crate::tag_error!($category, err, $($k: $v,)*))
    };
);

fn hard_error_config() -> anyhow::Result<Arc<HardErrorConfig>> {
    // This function should return `Guard<Arc<HardErrorConfig>>` to make it a little bit faster,
    // see https://github.com/vorner/arc-swap/issues/90

    if let Some(config) = HARD_ERROR_CONFIG.config.load_full() {
        return Ok(config);
    }

    let config = BUCK2_HARD_ERROR_ENV_VAR.get()?.map_or("", |s| s.as_str());
    let config = HardErrorConfig::from_str(config)?;
    HARD_ERROR_CONFIG.config.store(Some(Arc::new(config)));
    HARD_ERROR_CONFIG
        .config
        .load_full()
        .context("Just stored a value (internal error)")
}

pub fn reload_hard_error_config(var_value: &str) -> anyhow::Result<()> {
    HARD_ERROR_CONFIG.reload_hard_error_config(var_value)
}

pub struct StructuredErrorOptions {
    /// Log this error (to our event log and possibly to a task), but do not print it to stderr.
    pub quiet: bool,
    /// Create a task for this error.
    pub task: bool,
    pub daemon_in_memory_state_is_corrupted: bool,
    pub daemon_materializer_state_is_corrupted: bool,
    pub action_cache_is_corrupted: bool,
}

impl Default for StructuredErrorOptions {
    fn default() -> Self {
        Self {
            quiet: false,
            task: true,
            daemon_in_memory_state_is_corrupted: false,
            daemon_materializer_state_is_corrupted: false,
            action_cache_is_corrupted: false,
        }
    }
}

// Hidden because an implementation detail of `soft_error!`.
#[doc(hidden)]
pub fn handle_soft_error(
    category: &str,
    err: anyhow::Error,
    count: &'static AtomicUsize,
    once: &std::sync::Once,
    loc: (&'static str, u32, u32),
    options: StructuredErrorOptions,
) -> anyhow::Result<anyhow::Error> {
    validate_category(category)?;

    if is_open_source() {
        // We don't log these, and we have no legacy users, and they might not upgrade that often,
        // so lets just break open source things immediately.
        return Err(err);
    }

    if cfg!(test) {
        // When running unit tests of `buck2_core` crate, all errors are hard errors.
        return Err(err);
    }

    once.call_once(|| {
        ALL_SOFT_ERROR_COUNTERS.lock().unwrap().push(count);
    });

    // We want to limit each error to appearing at most 10 times in a build (no point spamming people)
    if count.fetch_add(1, Ordering::SeqCst) < 10 {
        if let Some(handler) = HANDLER.get() {
            handler(category, &err, loc, options);
        }
    }

    if hard_error_config()?.should_hard_error(category) {
        return Err(err.context("Upgraded warning to failure via $BUCK2_HARD_ERROR"));
    }

    Ok(err)
}

#[allow(clippy::significant_drop_in_scrutinee)] // False positive.
pub fn reset_soft_error_counters() {
    for counter in ALL_SOFT_ERROR_COUNTERS.lock().unwrap().iter() {
        counter.store(0, Ordering::Relaxed);
    }
}

pub fn initialize(handler: StructuredErrorHandler) -> anyhow::Result<()> {
    hard_error_config()?;

    if let Err(_e) = HANDLER.set(handler) {
        panic!("Cannot initialize StructuredErrorHandler handler more than once");
    }

    Ok(())
}

/// Parse either a boolean or `only=category1,category2`
#[derive(Debug, PartialEq, Eq)]
enum HardErrorConfig {
    Bool(bool),
    Selected(SmallSet<String>),
}

impl HardErrorConfig {
    fn should_hard_error(&self, category: &str) -> bool {
        match self {
            Self::Bool(v) => *v,
            Self::Selected(s) => s.contains(category),
        }
    }
}

impl FromStr for HardErrorConfig {
    type Err = InvalidHardErrorConfig;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.is_empty() {
            return Ok(Self::Bool(false));
        }

        if let Ok(v) = s.parse() {
            return Ok(Self::Bool(v));
        }

        let mut parts = s.split('=');

        match (parts.next(), parts.next(), parts.next()) {
            (Some("only"), Some(v), None) => {
                return Ok(Self::Selected(
                    v.split(',').map(|s| s.trim().to_owned()).collect(),
                ));
            }
            _ => {}
        }

        Err(InvalidHardErrorConfig(s.to_owned()))
    }
}

struct HardErrorConfigHolder {
    config: ArcSwapOption<HardErrorConfig>,
}

impl HardErrorConfigHolder {
    fn reload_hard_error_config(&self, var_value: &str) -> anyhow::Result<()> {
        let config = HardErrorConfig::from_str(var_value)?;
        if let Some(old_config) = &*self.config.load() {
            if **old_config == config {
                return Ok(());
            }
        }

        self.config.store(Some(Arc::new(config)));
        Ok(())
    }
}

#[derive(Error, Debug)]
#[error("Invalid hard error config: `{0}`")]
struct InvalidHardErrorConfig(String);

#[derive(Error, Debug)]
enum InvalidSoftError {
    #[error("Invalid category, must be lower_snake_case, got `{0}`")]
    InvalidCategory(String),
}

/// A category must be a-z with no consecutive underscores. Or we raise an error.
fn validate_category(category: &str) -> anyhow::Result<()> {
    let mut allow_underscore = false;
    for x in category.chars() {
        if x.is_ascii_lowercase() {
            allow_underscore = true;
        } else if allow_underscore && x == '_' {
            allow_underscore = false;
        } else {
            // Go to the shared error path
            allow_underscore = false;
            break;
        }
    }
    if !allow_underscore {
        Err(InvalidSoftError::InvalidCategory(category.to_owned()).into())
    } else {
        Ok(())
    }
}

#[cfg(test)]
pub(crate) mod tests {

    use assert_matches::assert_matches;

    use super::*;
    use crate::error::HardErrorConfig;

    #[test]
    fn test_hard_error() -> anyhow::Result<()> {
        assert!(HardErrorConfig::from_str("true")?.should_hard_error("foo"));
        assert!(!HardErrorConfig::from_str("false")?.should_hard_error("foo"));
        assert_eq!(
            HardErrorConfig::Bool(false),
            HardErrorConfig::from_str("")?,
            "Empty string must parse to no hard errors"
        );
        assert!(!HardErrorConfig::from_str("")?.should_hard_error("foo"));

        assert!(HardErrorConfig::from_str("only=foo,bar")?.should_hard_error("foo"));
        assert!(!HardErrorConfig::from_str("only=foo,bar")?.should_hard_error("baz"));

        Ok(())
    }

    #[test]
    fn test_reset_soft_error_handler() {
        let config = HardErrorConfigHolder {
            config: ArcSwapOption::const_empty(),
        };

        assert!(config.config.load().is_none());

        config.reload_hard_error_config("true").unwrap();
        let c0 = config.config.load();
        let c0 = c0.as_ref().unwrap();
        config.reload_hard_error_config("true").unwrap();
        let c1 = config.config.load();
        let c1 = c1.as_ref().unwrap();
        assert!(Arc::ptr_eq(c0, c1), "Reload identical config is no-op");
        assert_eq!(**c0, HardErrorConfig::Bool(true));

        config.reload_hard_error_config("false").unwrap();
        let c2 = config.config.load();
        let c2 = c2.as_ref().unwrap();
        assert_eq!(**c2, HardErrorConfig::Bool(false));
    }

    #[test]
    fn test_validate_category() {
        assert_matches!(validate_category("valid"), Ok(_));
        assert_matches!(validate_category("a_valid_category"), Ok(_));
        assert_matches!(validate_category(""), Err(_));
        assert_matches!(validate_category("Invalid_because_capital"), Err(_));
        assert_matches!(validate_category("some_1number"), Err(_));
        assert_matches!(validate_category("two__underscore"), Err(_));
        assert_matches!(validate_category("a-dash"), Err(_));
        assert_matches!(validate_category("_leading_underscore"), Err(_));
        assert_matches!(validate_category("trailing_underscore_"), Err(_));
    }
}


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

use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;

use dupe::Dupe;

#[derive(Copy, Clone, Dupe, Debug)]
pub enum IoCounterKey {
    Copy,
    Symlink,
    Hardlink,
    MkDir,
    ReadDir,
    ReadDirEden,
    RmDir,
    RmDirAll,
    Stat,
    StatEden,
    Chmod,
    ReadLink,
    Remove,
    Rename,
    Read,
    Write,
    Canonicalize,
    EdenSettle,
}

static IN_PROGRESS: [AtomicU32; IoCounterKey::COUNT] = [
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
    AtomicU32::new(0),
];

impl IoCounterKey {
    pub const ALL: &'static [IoCounterKey] = &[
        IoCounterKey::Copy,
        IoCounterKey::Symlink,
        IoCounterKey::Hardlink,
        IoCounterKey::MkDir,
        IoCounterKey::ReadDir,
        IoCounterKey::ReadDirEden,
        IoCounterKey::RmDir,
        IoCounterKey::RmDirAll,
        IoCounterKey::Stat,
        IoCounterKey::StatEden,
        IoCounterKey::Chmod,
        IoCounterKey::ReadLink,
        IoCounterKey::Remove,
        IoCounterKey::Rename,
        IoCounterKey::Read,
        IoCounterKey::Write,
        IoCounterKey::Canonicalize,
        IoCounterKey::EdenSettle,
    ];

    const COUNT: usize = IoCounterKey::ALL.len();

    pub fn get(&self) -> u32 {
        IN_PROGRESS[*self as usize].load(Ordering::Relaxed)
    }

    pub fn guard(&self) -> IoCounterGuard {
        IN_PROGRESS[*self as usize].fetch_add(1, Ordering::Relaxed);
        IoCounterGuard(*self)
    }
}

#[must_use]
pub struct IoCounterGuard(IoCounterKey);

impl Drop for IoCounterGuard {
    fn drop(&mut self) {
        IN_PROGRESS[self.0 as usize].fetch_sub(1, Ordering::Relaxed);
    }
}

#[cfg(test)]
mod tests {
    use crate::io_counters::IoCounterKey;

    #[test]
    fn test_keys() {
        for k in IoCounterKey::ALL {
            // Check `IN_PROGRESS` is correct size.
            k.get();
        }
    }
}


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

// Because Buck build uses different version of Rust than Cargo build.
#![allow(stable_features)]
#![feature(absolute_path)]
#![feature(const_fn_fn_ptr_basics)]
#![feature(const_fn_trait_bound)]
#![feature(const_panic)]
#![feature(control_flow_enum)]
#![feature(fs_try_exists)]
#![feature(termination_trait_lib)]
#![feature(try_trait_v2)]
#![feature(type_alias_impl_trait)]
#![feature(never_type)]
#![feature(pattern)]
#![feature(box_patterns)]
#![feature(maybe_uninit_slice)]
#![feature(impl_trait_in_assoc_type)]
#![cfg_attr(windows, feature(windows_file_type_ext))]
// Plugins
#![cfg_attr(feature = "gazebo_lint", feature(plugin))]
#![cfg_attr(feature = "gazebo_lint", allow(deprecated))] // :(
#![cfg_attr(feature = "gazebo_lint", plugin(gazebo_lint))]

#[cfg(test)]
#[macro_use]
extern crate maplit;

#[macro_use]
pub mod error;

mod ascii_char_set;
pub mod async_once_cell;
pub mod base_deferred_key;
pub mod buck_path;
pub mod build_file_path;
pub mod bzl;
pub mod category;
pub mod cells;
pub mod configuration;
pub mod directory;
pub mod env_helper;
pub mod fs;
pub mod io_counters;
pub mod logging;
pub mod package;
pub mod pattern;
pub mod provider;
pub mod rollout_percentage;
pub mod target;
pub mod target_aliases;
pub mod unsafe_send_future;

/// Marker for things that are only sensible to use inside Facebook,
/// not intended to be complete, but intended to be useful to audit
/// en-mass at some point in the future.
pub fn facebook_only() {}

#[inline]
pub fn is_open_source() -> bool {
    // @oss-disable: false
    true // @oss-enable
}

/// Internal build with `buck2`.
#[inline]
pub fn is_fbcode_build() -> bool {
    cfg!(fbcode_build)
}


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

use std::sync::Arc;

use anyhow::Context as _;
use tracing_subscriber::filter::Filtered;
use tracing_subscriber::fmt::MakeWriter;
use tracing_subscriber::prelude::*;
use tracing_subscriber::reload;
use tracing_subscriber::reload::Handle;
use tracing_subscriber::EnvFilter;

pub trait LogConfigurationReloadHandle: Send + Sync + 'static {
    fn update_log_filter(&self, format: &str) -> anyhow::Result<()>;
}

impl dyn LogConfigurationReloadHandle {
    pub fn noop() -> Arc<dyn LogConfigurationReloadHandle> {
        Arc::new(NoopLogConfigurationReloadHandle) as _
    }
}

struct NoopLogConfigurationReloadHandle;

impl LogConfigurationReloadHandle for NoopLogConfigurationReloadHandle {
    fn update_log_filter(&self, _filter: &str) -> anyhow::Result<()> {
        Ok(())
    }
}

impl<L, R> LogConfigurationReloadHandle for Handle<Filtered<L, EnvFilter, R>, R>
where
    L: Send + Sync + 'static,
    R: Send + Sync + 'static,
{
    fn update_log_filter(&self, raw: &str) -> anyhow::Result<()> {
        let filter = EnvFilter::try_new(raw).context("Invalid log filter")?;
        self.modify(|layer| *layer.filter_mut() = filter)
            .context("Error updating log filter")?;
        tracing::debug!("Log filter was updated to: `{}`", raw);
        Ok(())
    }
}

pub fn init_tracing_for_writer<W>(
    writer: W,
) -> anyhow::Result<Arc<dyn LogConfigurationReloadHandle>>
where
    W: for<'writer> MakeWriter<'writer> + Send + Sync + 'static,
{
    // By default, show warnings/errors.
    // If the user specifies BUCK_LOG, we want to honour that.
    const ENV_VAR: &str = "BUCK_LOG";

    let filter = match std::env::var_os(ENV_VAR) {
        Some(v) => {
            let v = v
                .into_string()
                .ok()
                .with_context(|| format!("Failed to parse ${} as utf-8", ENV_VAR))?;
            EnvFilter::try_new(v)
                .with_context(|| format!("Failed to parse ${} as a filter", ENV_VAR))?
        }
        // daemon_listener is all emitted before the client starts tailing, which is why we log
        // those by default.
        None => EnvFilter::new("warn,[daemon_listener]=info"),
    };

    let layer = tracing_subscriber::fmt::layer()
        .with_writer(writer)
        .with_filter(filter);

    let (layer, handle) = reload::Layer::new(layer);

    tracing_subscriber::registry().with(layer).init();

    Ok(Arc::new(handle) as _)
}


/*
 * 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::ffi::OsString;
use std::str::FromStr;

use dupe::Dupe;
use os_str_bytes::OsStrBytes;
use rand::Rng;

/// Returns true or false for percentage-based feature rollouts based on a configuration string.
/// Configurations supported today are random and hostname.
/// - Random: Enabled by directly setting a decimal value.
///     Checks whether to enable feature based on a random roll
/// - Hostname: Set by `hostname=<value>`, ex. `hostname=0.5`. Checks whether to roll out feature
///     based on hash of hostname. Useful when you want the same host to consistently get the
///     same feature enabled/disabled.
/// It's possible to extend this system to support per-username rollout as well in addition to
/// per-host rollout.
#[derive(Copy, Clone, Dupe, Debug)]
pub struct RolloutPercentage {
    inner: Inner,
}

impl RolloutPercentage {
    pub fn roll(&self) -> bool {
        self.roll_inner(|| hostname::get().ok())
    }

    fn roll_inner<F>(&self, get_hostname: F) -> bool
    where
        F: FnOnce() -> Option<OsString>,
    {
        match self.inner {
            Inner::Hostname(pct) => {
                match get_hostname() {
                    Some(h) => {
                        let hash = blake3::hash(&h.to_raw_bytes());
                        // For simplicity, we just divide the value of the first byte by 256 to get a
                        // decimal value to compare against our percentage. Note that the first byte
                        // can never exceed 255, so if we set the percentage to 100% we return true.
                        // TODO(scottcao): Use get_shard internally
                        (hash.as_bytes()[0] as f64 / 256_f64) < pct
                    }
                    None => {
                        tracing::warn!("Unable to obtain hostname");
                        pct == 1f64 // 1.0 is an exact value in floats, we *can* compare that one.
                    }
                }
            }
            Inner::Rate(pct) => rand::thread_rng().gen::<f64>() < pct,
            Inner::Bool(b) => b,
        }
    }

    pub fn from_bool(val: bool) -> Self {
        Self {
            inner: Inner::Bool(val),
        }
    }

    pub fn never() -> Self {
        Self::from_bool(false)
    }

    pub fn always() -> Self {
        Self::from_bool(true)
    }
}

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

    fn from_str(val: &str) -> Result<Self, Self::Err> {
        Ok(Self {
            inner: val.parse()?,
        })
    }
}

#[derive(Copy, Clone, Dupe, Debug)]
enum Inner {
    Rate(f64),
    Hostname(f64),
    Bool(bool),
}

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

    fn from_str(val: &str) -> Result<Self, Self::Err> {
        if let Some(val) = val.strip_prefix("hostname:") {
            let val = val.parse()?;
            let val = rate(val)?;
            return Ok(Inner::Hostname(val));
        }

        if let Ok(val) = val.parse() {
            let val = rate(val)?;
            return Ok(Inner::Rate(val));
        }

        if let Ok(val) = val.parse() {
            return Ok(Inner::Bool(val));
        }

        Err(anyhow::anyhow!(
            "RolloutPercentage must be either a float or a bool"
        ))
    }
}

fn rate(val: f64) -> anyhow::Result<f64> {
    if (0.0..=1.0).contains(&val) {
        Ok(val)
    } else {
        Err(anyhow::anyhow!(
            "RolloutPercentage floats must be within [0,1] (got: {})",
            val
        ))
    }
}

#[cfg(test)]
mod tests {
    use assert_matches::assert_matches;
    use rand::distributions::Alphanumeric;
    use rand::distributions::DistString;

    use super::*;

    #[test]
    fn test_parse_ok() {
        assert_matches!("true".parse(), Ok(Inner::Bool(true)));

        // NOTE: 0.5 and 1 are exact values for floats
        assert_matches!("0.5".parse(), Ok(Inner::Rate(v)) if v == 0.5);
        assert_matches!("1".parse(), Ok(Inner::Rate(v)) if v == 1.0);
    }

    #[test]
    fn test_parse_err() {
        assert_matches!(Inner::from_str("foo"), Err(..));
        assert_matches!(Inner::from_str("-1"), Err(..));
        assert_matches!(Inner::from_str("1.1"), Err(..));
    }

    #[test]
    fn test_roll() {
        let hostname = || {
            Some(OsString::from(
                Alphanumeric.sample_string(&mut rand::thread_rng(), 16),
            ))
        };

        assert!(
            RolloutPercentage {
                inner: Inner::Bool(true)
            }
            .roll_inner(hostname)
        );

        for _ in 0..1000 {
            assert!(
                RolloutPercentage {
                    inner: Inner::Rate(1.0)
                }
                .roll_inner(hostname)
            );
            assert!(
                !RolloutPercentage {
                    inner: Inner::Rate(0.0)
                }
                .roll_inner(hostname)
            );
        }

        for _ in 0..1000 {
            assert!(
                RolloutPercentage {
                    inner: Inner::Hostname(1.0)
                }
                .roll_inner(hostname)
            );
            assert!(
                !RolloutPercentage {
                    inner: Inner::Hostname(0.0)
                }
                .roll_inner(hostname)
            );
        }
    }

    #[test]
    fn test_roll_no_hostname() {
        let hostname = || None;

        assert!(
            !RolloutPercentage {
                inner: Inner::Hostname(0.99999)
            }
            .roll_inner(hostname)
        );

        assert!(
            RolloutPercentage {
                inner: Inner::Hostname(1.0)
            }
            .roll_inner(hostname)
        );
    }
}


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

/// When calling a command like `buck2 build dramatic`,
/// this trait is used to resolve the string `dramatic` to a fully qualified target name.
pub trait TargetAliasResolver {
    fn get<'a>(&'a self, name: &str) -> anyhow::Result<Option<&'a str>>;
}


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

use std::future::Future;
use std::pin::Pin;

use pin_project::pin_project;

/// A wrapper type to make a future which is not normally [`Send`] declare itself as [`Send`].
/// See the specific constructors for the arguments why this might be safe, even though
/// Rust can't infer it as such.
#[pin_project]
pub struct UnsafeSendFuture<F>(#[pin] F);

unsafe impl<F> Send for UnsafeSendFuture<F> {}

impl<F> Future for UnsafeSendFuture<F>
where
    F: Future,
{
    type Output = <F as Future>::Output;

    fn poll(
        self: Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Self::Output> {
        self.project().0.poll(cx)
    }
}

impl<F: Future> UnsafeSendFuture<F> {
    /// Create a future that is [`Send`] from a future that isn't [`Send`].
    /// Safe to use when the only reason the future isn't [`Send`] is due to Starlark types
    /// (e.g. `Module`, `Value`) and that the future owns _all_ the Starlark types on the heap
    /// (e.g. all those with the same lifetime). As a first approximation, if the `'v` lifetime
    /// isn't in the futures type, then the future probably owns all the Starlark.
    ///
    /// The reason this pattern is safe is that while a `Value` isn't [`Send`], if you send _all_ the
    /// `Value`s on a given heap, along with the heap, to a different thread, together, that is safe.
    /// It's only not safe when some `Value` end up on one heap, and some on another.
    /// Rust can't express that natively, so we use this function to explain it to Rust.
    pub unsafe fn new_encapsulates_starlark(fut: F) -> Self {
        Self(fut)
    }
}


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

pub mod 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 allocative::Allocative;
use buck2_util::arc_str::ArcS;
use derive_more::Display;
use dupe::Dupe;

use crate::cells::cell_path::CellPath;
use crate::package::package_relative_path::PackageRelativePath;
use crate::package::PackageLabel;

/// Represents a resolvable path corresponding to some path that is part of a
/// 'Package'. The 'BuckPath' refers to only paths in the repo source, not
/// outputs of a 'Package'.
#[derive(
    Clone,
    Debug,
    derive_more::Display,
    Hash,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Allocative
)]
#[display(fmt = "{}", "self.as_ref()")]
pub struct BuckPath {
    pkg: PackageLabel,
    path: ArcS<PackageRelativePath>,
}

impl BuckPath {
    #[inline]
    pub fn new(pkg: PackageLabel, path: ArcS<PackageRelativePath>) -> Self {
        BuckPath { pkg, path }
    }

    /// This is slow, but OK to use in tests.
    pub fn testing_new(pkg: PackageLabel, path: impl AsRef<PackageRelativePath>) -> Self {
        BuckPath::new(pkg, ArcS::from(path.as_ref()))
    }

    #[inline]
    pub fn package(&self) -> PackageLabel {
        self.pkg.dupe()
    }

    #[inline]
    pub fn path(&self) -> &PackageRelativePath {
        &self.path
    }

    #[inline]
    pub fn to_cell_path(&self) -> CellPath {
        self.as_ref().to_cell_path()
    }

    #[inline]
    pub fn as_ref(&self) -> BuckPathRef {
        BuckPathRef {
            pkg: self.pkg.dupe(),
            path: &self.path,
        }
    }
}

#[derive(Display, Debug, Eq, Hash, PartialEq, Clone, Dupe)]
#[display(fmt = "{}/{}", pkg, "path.as_str()")]
pub struct BuckPathRef<'a> {
    pkg: PackageLabel,
    path: &'a ArcS<PackageRelativePath>,
}

impl<'a> BuckPathRef<'a> {
    #[inline]
    pub fn new(pkg: PackageLabel, path: &'a ArcS<PackageRelativePath>) -> BuckPathRef<'a> {
        BuckPathRef { pkg, path }
    }

    #[inline]
    pub fn package(&self) -> PackageLabel {
        self.pkg.dupe()
    }

    #[inline]
    pub fn path(&self) -> &PackageRelativePath {
        self.path
    }

    #[inline]
    pub fn to_cell_path(&self) -> CellPath {
        self.pkg
            .as_cell_path()
            .join(self.path.as_forward_rel_path())
    }

    #[inline]
    pub fn to_buck_path(&self) -> BuckPath {
        BuckPath {
            pkg: self.pkg.dupe(),
            path: self.path.dupe(),
        }
    }
}


/*
 * 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 allocative::Allocative;

#[derive(Debug, thiserror::Error)]
enum CellAliasError {
    #[error("Empty alias where non-empty is required")]
    EmptyAlias,
}

/// A 'CellAlias' is a user-provided string name that maps to a 'CellName'.
/// The mapping of 'CellAlias' to 'CellName' is specific to the current cell so
/// that the same 'CellAlias' may map to different 'CellName's depending on what
/// the current 'CellInstance' is that references the 'CellAlias'.
#[derive(
    Clone,
    Debug,
    derive_more::Display,
    Hash,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Allocative
)]
pub struct CellAlias(String);

impl CellAlias {
    pub fn new(alias: String) -> CellAlias {
        CellAlias(alias)
    }

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

impl Borrow<str> for CellAlias {
    fn borrow(&self) -> &str {
        &self.0
    }
}

/// Empty string is an alias for the current cell.
/// This type does not permit it.
#[derive(
    derive_more::Display,
    Debug,
    Clone,
    Eq,
    PartialEq,
    Hash,
    Ord,
    PartialOrd,
    Allocative
)]
pub struct NonEmptyCellAlias(String);

impl NonEmptyCellAlias {
    pub fn new(alias: String) -> anyhow::Result<NonEmptyCellAlias> {
        if alias.is_empty() {
            Err(CellAliasError::EmptyAlias.into())
        } else {
            Ok(NonEmptyCellAlias(alias))
        }
    }

    pub fn testing_new(alias: &str) -> NonEmptyCellAlias {
        Self::new(alias.to_owned()).unwrap()
    }

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

impl Borrow<str> for NonEmptyCellAlias {
    fn borrow(&self) -> &str {
        &self.0
    }
}


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

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

use crate::cells::name::CellName;

/// Used to hold the cell name for the top-level build file being interpreted.
/// There's a subtlety where that doesn't necessarily match the cell of the file
/// being interpreted. This gets its own type because its easy to get wrong.
#[derive(
    Clone,
    Dupe,
    Copy,
    Hash,
    Eq,
    PartialEq,
    Debug,
    derive_more::Display,
    Allocative
)]
#[repr(C)]
pub struct BuildFileCell(CellName);

impl BuildFileCell {
    #[inline]
    pub fn new(name: CellName) -> Self {
        Self(name)
    }

    #[inline]
    pub fn name(&self) -> CellName {
        self.0.dupe()
    }
}


/*
 * 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 anyhow::Context;
use dupe::Dupe;
use relative_path::RelativePath;

use crate::cells::name::CellName;
use crate::cells::paths::CellRelativePath;
use crate::cells::paths::CellRelativePathBuf;
use crate::fs::paths::forward_rel_path::ForwardRelativePath;

#[derive(thiserror::Error, Debug)]
#[error("attempted to strip prefix of two CellPath with different cell names `{0}` and `{1}`")]
struct StripPrefixError(CellName, CellName);

/// Represents a resolvable path corresponding to some path that is relative to the cell
/// corresponding to the 'CellName'.
#[derive(
    Clone,
    Debug,
    derive_more::Display,
    Hash,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Allocative
)]
#[display(fmt = "{}", "self.as_ref()")]
pub struct CellPath {
    cell: CellName,
    path: Box<CellRelativePath>,
}

impl CellPath {
    #[inline]
    pub fn new(cell: CellName, path: CellRelativePathBuf) -> Self {
        CellPath {
            cell,
            path: path.into_box(),
        }
    }

    #[inline]
    pub fn cell(&self) -> CellName {
        self.cell
    }

    #[inline]
    pub fn path(&self) -> &CellRelativePath {
        &self.path
    }

    /// Creates an owned 'CellRelativePathBuf' with path adjoined to self.
    ///
    /// ```
    /// use buck2_core::cells::cell_path::CellPath;
    /// use buck2_core::cells::paths::{CellRelativePathBuf};
    /// use buck2_core::cells::name::CellName;
    /// use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
    ///
    /// let path = CellPath::new(
    ///     CellName::testing_new("cell"),
    ///     CellRelativePathBuf::unchecked_new("foo/bar".into())
    /// );
    /// let other = ForwardRelativePath::new("baz")?;
    /// assert_eq!(
    ///     CellPath::new(CellName::testing_new("cell"),
    ///     CellRelativePathBuf::unchecked_new("foo/bar/baz".into())), path.join(other)
    /// );
    ///
    /// # anyhow::Ok(())
    /// ```
    #[inline]
    pub fn join<P: AsRef<ForwardRelativePath>>(&self, path: P) -> CellPath {
        self.as_ref().join(path)
    }

    /// Returns a relative path of the parent directory
    ///
    /// ```
    /// use buck2_core::cells::cell_path::CellPath;
    /// use buck2_core::cells::paths::{CellRelativePathBuf};
    /// use buck2_core::cells::name::CellName;
    ///
    /// assert_eq!(
    ///     Some(
    ///         CellPath::new(CellName::testing_new("cell"),
    ///         CellRelativePathBuf::unchecked_new("foo".into()))
    ///     ),
    ///     CellPath::new(
    ///         CellName::testing_new("cell"),
    ///         CellRelativePathBuf::unchecked_new("foo/bar".into())
    ///     ).parent().map(|p| p.to_owned()),
    /// );
    ///
    /// # anyhow::Ok(())
    /// ```
    #[inline]
    pub fn parent(&self) -> Option<CellPathRef> {
        self.as_ref().parent()
    }

    /// Produces an iterator over CellPath and its ancestors.
    ///
    /// The iterator will yield all the CellPaths that is returned if
    /// the parent method is used zero or more times in order.
    ///
    /// ```
    /// use buck2_core::cells::cell_path::CellPath;
    /// use buck2_core::cells::paths::{CellRelativePathBuf};
    /// use buck2_core::cells::name::CellName;
    ///
    /// let path = CellPath::testing_new("cell//foo/bar");
    /// let mut ancestors = path.ancestors();
    ///
    /// assert_eq!(ancestors.next(), Some(CellPath::testing_new("cell//foo/bar").as_ref()));
    /// assert_eq!(ancestors.next(), Some(CellPath::testing_new("cell//foo").as_ref()));
    /// assert_eq!(ancestors.next(), Some(CellPath::testing_new("cell//").as_ref()));
    /// assert_eq!(ancestors.next(), None);
    ///
    /// # anyhow::Ok(())
    /// ```
    #[inline]
    pub fn ancestors(&self) -> impl Iterator<Item = CellPathRef> {
        self.as_ref().ancestors()
    }

    /// Returns a 'ForwardRelativePath' that, when joined onto `base`, yields
    /// `self`.
    ///
    /// Error if `base` is not a prefix of `self` or the returned
    /// path is not a 'ForwardRelativePath'
    ///
    /// ```
    /// use buck2_core::cells::cell_path::CellPath;
    /// use buck2_core::cells::paths::{CellRelativePathBuf};
    /// use buck2_core::cells::name::CellName;
    /// use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
    ///
    /// let path = CellPath::new(
    ///     CellName::testing_new("cell"),
    ///     CellRelativePathBuf::unchecked_new("test/haha/foo.txt".into())
    /// );
    ///
    /// assert_eq!(
    ///     path.strip_prefix(
    ///         CellPath::new(
    ///             CellName::testing_new("cell"),
    ///             CellRelativePathBuf::unchecked_new("test".into()),
    ///         ).as_ref()
    ///     )?,
    ///     ForwardRelativePathBuf::unchecked_new("haha/foo.txt".into())
    /// );
    /// assert_eq!(
    ///     path.strip_prefix(
    ///         CellPath::new(
    ///             CellName::testing_new("cell"),
    ///             CellRelativePathBuf::unchecked_new("asdf".into()),
    ///         ).as_ref()
    ///     ).is_err(),
    ///     true
    /// );
    /// assert_eq!(
    ///     path.strip_prefix(
    ///         CellPath::new(
    ///             CellName::testing_new("another"),
    ///             CellRelativePathBuf::unchecked_new("test".into()),
    ///         ).as_ref()
    ///     ).is_err(),
    ///     true
    /// );
    ///
    /// # anyhow::Ok(())
    /// ```
    #[inline]
    pub fn strip_prefix<'a>(
        &'a self,
        base: CellPathRef,
    ) -> anyhow::Result<&'a ForwardRelativePath> {
        self.as_ref().strip_prefix(base)
    }

    /// Build an owned `CellPath`, joined with the given path and
    /// normalized.
    ///
    /// ```
    ///
    /// use buck2_core::cells::paths::CellRelativePathBuf;
    /// use buck2_core::cells::name::CellName;
    /// use std::convert::TryFrom;
    /// use buck2_core::cells::cell_path::CellPath;
    ///
    /// assert_eq!(
    ///     CellPath::new(
    ///         CellName::testing_new("cell"),
    ///         CellRelativePathBuf::unchecked_new("foo/bar".into())
    ///     ).join_normalized("../baz.txt")?,
    ///     CellPath::new(
    ///         CellName::testing_new("cell"),
    ///         CellRelativePathBuf::unchecked_new("foo/baz.txt".into())
    ///     ),
    /// );
    ///
    /// assert_eq!(
    ///     CellPath::new(
    ///         CellName::testing_new("cell"),
    ///         CellRelativePathBuf::unchecked_new("foo".into())
    ///     ).join_normalized("../../baz.txt").is_err(),
    ///     true
    /// );
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn join_normalized<P: AsRef<RelativePath>>(&self, path: P) -> anyhow::Result<CellPath> {
        Ok(CellPath::new(self.cell, self.path.join_normalized(path)?))
    }

    /// Checks that cell matches and `self` path starts with `base` path
    ///
    /// ```
    ///
    /// use buck2_core::cells::paths::CellRelativePathBuf;
    /// use buck2_core::cells::cell_path::CellPath;
    /// use buck2_core::cells::name::CellName;
    /// use std::convert::TryFrom;
    ///
    /// assert!(
    ///     CellPath::new(
    ///         CellName::testing_new("cell"),
    ///         CellRelativePathBuf::unchecked_new("foo/bar".into())
    ///     ).starts_with(CellPath::new(
    ///         CellName::testing_new("cell"),
    ///         CellRelativePathBuf::unchecked_new("foo".into())
    ///     ).as_ref()),
    /// );
    ///
    /// # anyhow::Ok(())
    /// ```
    #[inline]
    pub fn starts_with(&self, base: CellPathRef) -> bool {
        self.as_ref().starts_with(base)
    }

    #[inline]
    pub fn into_parts(self) -> (CellName, Box<CellRelativePath>) {
        (self.cell, self.path)
    }

    pub fn testing_new(path: &str) -> CellPath {
        let (cell_name, relative_path) = path.split_once("//").unwrap();
        CellPath::new(
            CellName::testing_new(cell_name),
            CellRelativePathBuf::testing_new(relative_path),
        )
    }

    #[inline]
    pub fn as_ref(&self) -> CellPathRef {
        CellPathRef {
            cell: self.cell,
            path: &self.path,
        }
    }
}

#[derive(Debug, Clone, Dupe, Copy, Eq, PartialEq, Hash, derive_more::Display)]
#[display(fmt = "{}//{}", cell, path)]
pub struct CellPathRef<'a> {
    cell: CellName,
    path: &'a CellRelativePath,
}

impl<'a> CellPathRef<'a> {
    #[inline]
    pub fn new(cell: CellName, path: &'a CellRelativePath) -> CellPathRef<'a> {
        CellPathRef { cell, path }
    }

    pub fn testing_new(path: &str) -> CellPathRef {
        let (cell, path) = path
            .split_once("//")
            .with_context(|| format!("invalid path: `{}`", path))
            .unwrap();
        CellPathRef {
            cell: CellName::testing_new(cell),
            path: CellRelativePath::testing_new(path),
        }
    }

    #[inline]
    pub fn parent(&self) -> Option<CellPathRef<'a>> {
        Some(CellPathRef {
            cell: self.cell,
            path: self.path.parent()?,
        })
    }

    #[inline]
    pub fn to_owned(&self) -> CellPath {
        CellPath {
            cell: self.cell,
            path: self.path.to_box(),
        }
    }

    #[inline]
    pub fn cell(&self) -> CellName {
        self.cell
    }

    #[inline]
    pub fn path(&self) -> &'a CellRelativePath {
        self.path
    }

    pub fn ancestors(&self) -> impl Iterator<Item = CellPathRef<'a>> + 'a {
        struct Ancestors<'a>(Option<CellPathRef<'a>>);
        impl<'a> Iterator for Ancestors<'a> {
            type Item = CellPathRef<'a>;

            fn next(&mut self) -> Option<Self::Item> {
                match &self.0 {
                    None => None,
                    Some(v) => {
                        let mut next = v.parent();
                        std::mem::swap(&mut next, &mut self.0);
                        next
                    }
                }
            }
        }

        Ancestors(Some(self.dupe()))
    }

    #[inline]
    pub fn join<P: AsRef<ForwardRelativePath>>(&self, path: P) -> CellPath {
        CellPath {
            cell: self.cell,
            path: self.path.join(path).into_box(),
        }
    }

    #[inline]
    pub fn starts_with(&self, base: CellPathRef) -> bool {
        self.cell() == base.cell() && self.path().starts_with(base.path())
    }

    #[inline]
    pub fn strip_prefix(&self, base: CellPathRef) -> anyhow::Result<&'a ForwardRelativePath> {
        if self.cell != base.cell {
            return Err(StripPrefixError(self.cell, base.cell).into());
        }
        self.path.strip_prefix(&base.path)
    }
}

pub(crate) enum CellPathCow<'a> {
    Borrowed(CellPathRef<'a>),
    Owned(CellPath),
}

impl<'a> CellPathCow<'a> {
    pub(crate) fn into_owned(self) -> CellPath {
        match self {
            CellPathCow::Borrowed(v) => v.to_owned(),
            CellPathCow::Owned(v) => v,
        }
    }

    pub(crate) fn as_ref(&self) -> CellPathRef<'_> {
        match self {
            CellPathCow::Borrowed(v) => *v,
            CellPathCow::Owned(v) => v.as_ref(),
        }
    }
}


/*
 * 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::ops::Deref;

use allocative::Allocative;
use ref_cast::RefCast;

use crate::cells::paths::CellRelativePath;
use crate::fs::project_rel_path::ProjectRelativePath;
use crate::fs::project_rel_path::ProjectRelativePathBuf;

/// Path to the cell root.
#[derive(RefCast, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct CellRootPath(ProjectRelativePath);

impl CellRootPath {
    /// Constructor. Does not check the path is a valid cell root.
    pub fn new(path: &ProjectRelativePath) -> &CellRootPath {
        CellRootPath::ref_cast(path)
    }

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

    /// Project relative path to the cell root.
    pub fn as_project_relative_path(&self) -> &ProjectRelativePath {
        &self.0
    }

    /// Join cell path and cell-relative path.
    pub fn join(&self, path: &CellRelativePath) -> ProjectRelativePathBuf {
        self.0.join(path)
    }

    /// To owned.
    pub fn to_buf(&self) -> CellRootPathBuf {
        CellRootPathBuf(self.0.to_buf())
    }

    #[inline]
    pub fn is_repo_root(&self) -> bool {
        self.0.is_empty()
    }
}

impl Deref for CellRootPath {
    type Target = ProjectRelativePath;

    fn deref(&self) -> &ProjectRelativePath {
        &self.0
    }
}

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

/// Path to the cell root.
#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::Display, Allocative)]
pub struct CellRootPathBuf(ProjectRelativePathBuf);

impl CellRootPathBuf {
    /// Constructor. Does not check the path is a valid cell root.
    pub fn new(path: ProjectRelativePathBuf) -> Self {
        CellRootPathBuf(path)
    }

    pub fn testing_new(path: &str) -> CellRootPathBuf {
        CellRootPathBuf::new(ProjectRelativePathBuf::testing_new(path))
    }

    /// Project relative path to the cell root.
    pub fn project_relative_path(&self) -> &ProjectRelativePath {
        &self.0
    }

    pub fn as_path(&self) -> &CellRootPath {
        CellRootPath::new(&self.0)
    }
}

impl Deref for CellRootPathBuf {
    type Target = CellRootPath;

    fn deref(&self) -> &CellRootPath {
        CellRootPath::new(&self.0)
    }
}

impl Borrow<CellRootPath> for CellRootPathBuf {
    fn borrow(&self) -> &CellRootPath {
        self.deref()
    }
}


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

use std::sync::Arc;

use allocative::Allocative;
use derivative::Derivative;
use dupe::Dupe;

use crate::cells::cell_root_path::CellRootPath;
use crate::cells::cell_root_path::CellRootPathBuf;
use crate::cells::name::CellName;
use crate::cells::nested::NestedCells;
use crate::cells::CellAliasResolver;
use crate::fs::paths::file_name::FileNameBuf;

#[derive(Debug, thiserror::Error)]
enum CellInstanceError {
    #[error("Inconsistent cell name: `{0}` in instance, but `{1}` in alias resolver")]
    InconsistentCellName(CellName, CellName),
}

/// A 'CellInstance', contains a 'CellName' and a path for that cell.
#[derive(Clone, Debug, derive_more::Display, Dupe, PartialEq, Eq, Allocative)]
#[display(fmt = "{}", "_0.name")]
pub struct CellInstance(Arc<CellData>);

#[derive(Derivative, PartialEq, Eq, Allocative)]
#[derivative(Debug)]
struct CellData {
    /// the fully canonicalized 'CellName'
    name: CellName,
    /// the project relative path to this 'CellInstance'
    path: CellRootPathBuf,
    /// a list of potential buildfile names for this cell (e.g. 'BUCK', 'TARGETS',
    /// 'TARGET.v2'). The candidates are listed in priority order, buck will use
    /// the first one it encounters in a directory.
    buildfiles: Vec<FileNameBuf>,
    #[derivative(Debug = "ignore")]
    /// the aliases of this specific cell
    aliases: CellAliasResolver,
    nested_cells: NestedCells,
}

impl CellInstance {
    pub(crate) fn new(
        name: CellName,
        path: CellRootPathBuf,
        buildfiles: Vec<FileNameBuf>,
        aliases: CellAliasResolver,
        nested_cells: NestedCells,
    ) -> anyhow::Result<CellInstance> {
        if name != aliases.current {
            return Err(CellInstanceError::InconsistentCellName(
                name,
                aliases.current,
            ))
            .unwrap();
        }
        Ok(CellInstance(Arc::new(CellData {
            name,
            path,
            buildfiles,
            aliases,
            nested_cells,
        })))
    }

    /// Get the name of the cell, as supplied in `cell_name//foo:bar`.
    #[inline]
    pub fn name(&self) -> CellName {
        self.0.name.dupe()
    }

    /// Get the path of the cell, where it is routed.
    #[inline]
    pub fn path(&self) -> &CellRootPath {
        &self.0.path
    }

    // Get the name of build files for the cell.
    #[inline]
    pub fn buildfiles(&self) -> &[FileNameBuf] {
        &self.0.buildfiles
    }

    #[inline]
    pub fn cell_alias_resolver(&self) -> &CellAliasResolver {
        &self.0.aliases
    }

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


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

//!
//! # Cell
//! A 'Cell' is sub-project within the main project for Buck. All files
//! reachable by Buck is belongs to a single Cell.
//! Cells can be sub-directories of other cells, but that makes that
//! sub-directory part of the sub-cell and no longer part of the parent cell.
//! For example, let's say there's cells 'parent-cell' and 'sub-cell' declared
//! in folders of the same names.
//! ```text
//!  parent-cell
//! +-- folder1
//! +-- folder2
//! +-- sub-cell
//! |   +-- folder3
//! ```
//! All files part of `folder1` and `folder2` will be part of 'parent-cell'.
//! Anything part of `sub-cell`, including `folder3`, are only part of the
//! 'sub-cell'.
//!
//! For users, each Cell is identified by 'CellAlias's. A 'CellAlias' is a
//! human-readable name that contains alphanumeric characters and underscores.
//! (i.e. shouldn't contain any special characters like `/`). Something like `1`
//! is a valid identifier, though not we do not suggest such naming as it's not
//! very descriptive.
//!
//! It's possible that in certain cell contexts, some Cells are not reachable by
//! any 'CellAlias'. However, in the global context, every Cell will be
//! reachable by at least one 'CellAlias'.
//!
//! ## Cell Alias
//! The cell alias appears within a fully qualified target with the syntax
//! `<cell alias>//<target label>`. For example, in `foo//some:target`, `foo` is
//! the cell alias. Examples like `foo/bar//some:target` has an invalid cell
//! alias of `foo/bar` since special characters are forbidden.
//!
//! The 'CellAlias' is specified via configuration files per cell. A
//! configuration specifies these with the syntax `<cell alias>=<relative path
//! to cell>`. We allow a many to one mapping from 'CellAlias' to Cell.
//!
//! Each Cell may give different aliases to the same cell. The 'CellAlias' will
//! be resolved based on the contextual cell that the alias appears in.
//! e.g. `mycell//foo:bar` build file will have any aliases that appears within
//! it be resolved using the aliases defined in `mycell` cell.
//!
//! Cells may omit declaring aliases for cells that exists globally. This means
//! that there will be no alias for those cells, and hence render those cells
//! inaccessible from the cell context that doesn't declare them.
//!
//! ### The Empty Cell Alias
//! The empty cell alias is a special alias injected by Buck to represent the
//! current contextual cell. That means, inside `mycell` cell, references to the
//! 'CellAlias' `""` will resolve to the `mycell` cell.
//!
//! ## Cell Name
//! Each Cell is uniquely identifier globally via a one to one mapping to a
//! 'CellName'. A 'CellName' is a canonicalized, human-readable name that
//! corresponds to a 'CellInstance'. The cell name is inferred from the global
//! list of 'CellAlias's available, by picking the first alias for each cell
//! path based on lexicogrpahic ordering of the aliases. The 'CellName' is
//! subject to the same character restrictions as 'CellAlias'.
//!
//! # Resolving Cells
//! Cells are represented by 'CellInstance'. The 'CellResolver' is able to
//! resolve 'CellNames' to 'CellInstance's. It is also able to find the
//! containing Cell given a path. 'CellAlias' can be resolved with an
//! 'CellAliasResolver'. Each 'CellInstance' contains a 'CellAliasResolver' for
//! the cell alias mapping for that particular cell.
//!
//! e.g.
//! ```
//! use buck2_core::fs::project_rel_path::{ProjectRelativePath, ProjectRelativePathBuf};
//! use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
//! use buck2_core::cells::CellResolver;
//! use std::convert::TryFrom;
//! use maplit::hashmap;
//! use buck2_core::cells::cell_root_path::CellRootPathBuf;
//! use buck2_core::cells::name::CellName;
//! use buck2_core::cells::alias::CellAlias;
//! use dupe::Dupe;
//! use buck2_core::cells::alias::NonEmptyCellAlias;
//!
//! let cell_config = ForwardRelativePathBuf::try_from(".buckconfig".to_owned())?;
//! let fbsource = ProjectRelativePath::new("")?;
//! let fbcode = ProjectRelativePath::new("fbcode")?;
//!
//! let cells = CellResolver::testing_with_names_and_paths_with_alias(&[
//!     (CellName::testing_new("fbsource"), CellRootPathBuf::new(fbsource.to_buf()), hashmap![
//!         NonEmptyCellAlias::new("fbsource".to_owned()).unwrap() => CellName::testing_new("fbsource"),
//!         NonEmptyCellAlias::new("fbcode".to_owned()).unwrap() => CellName::testing_new("fbcode"),
//!     ]),
//!     (CellName::testing_new("fbcode"), CellRootPathBuf::new(fbcode.to_buf()), hashmap![
//!         NonEmptyCellAlias::new("fbcode".to_owned()).unwrap() => CellName::testing_new("fbcode"),
//!         NonEmptyCellAlias::new("fbsource".to_owned()).unwrap() => CellName::testing_new("fbsource"),
//!     ])
//! ]);
//!
//! let fbsource_cell_name = cells.find(ProjectRelativePath::new("something/in/fbsource")?)?.dupe();
//! assert_eq!(fbsource_cell_name, CellName::testing_new("fbsource"));
//!
//! let fbcode_cell_name = cells.find(ProjectRelativePath::new("fbcode/something/in/fbcode")?)?.dupe();
//! assert_eq!(fbcode_cell_name, CellName::testing_new("fbcode"));
//!
//! let fbsource_cell = cells.get(fbsource_cell_name)?;
//! assert_eq!(fbsource_cell.name(), CellName::testing_new("fbsource"));
//! let fbcode_cell = cells.get(fbcode_cell_name)?;
//! assert_eq!(fbcode_cell.name(), CellName::testing_new("fbcode"));
//!
//! let fbsource_aliases = fbsource_cell.cell_alias_resolver();
//! assert_eq!(fbsource_aliases.resolve("")?, CellName::testing_new("fbsource"));
//! assert_eq!(fbsource_aliases.resolve("fbsource")?, CellName::testing_new("fbsource"));
//! assert_eq!(fbsource_aliases.resolve("fbcode")?, CellName::testing_new("fbcode"));
//!
//! let fbcode_aliases = fbcode_cell.cell_alias_resolver();
//! assert_eq!(fbcode_aliases.resolve("")?, CellName::testing_new("fbcode"));
//! assert_eq!(fbcode_aliases.resolve("fbsource")?, CellName::testing_new("fbsource"));
//! assert_eq!(fbcode_aliases.resolve("fbcode")?, CellName::testing_new("fbcode"));
//!
//! # anyhow::Ok(())
//! ```
//!

pub mod alias;
pub mod build_file_cell;
pub mod cell_path;
pub mod cell_root_path;
pub mod instance;
pub mod name;
pub mod nested;
pub mod paths;
pub(crate) mod sequence_trie_allocative;
pub mod unchecked_cell_rel_path;

use std::collections::hash_map;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;

use allocative::Allocative;
use anyhow::Context;
use dupe::Dupe;
use dupe::OptionDupedExt;
use gazebo::prelude::*;
use instance::CellInstance;
use itertools::Itertools;
use sequence_trie::SequenceTrie;
use thiserror::Error;

use crate::buck_path::path::BuckPathRef;
use crate::cells::alias::CellAlias;
use crate::cells::alias::NonEmptyCellAlias;
use crate::cells::cell_path::CellPath;
use crate::cells::cell_path::CellPathRef;
use crate::cells::cell_root_path::CellRootPath;
use crate::cells::cell_root_path::CellRootPathBuf;
use crate::cells::name::CellName;
use crate::cells::nested::NestedCells;
use crate::fs::paths::abs_norm_path::AbsNormPath;
use crate::fs::paths::abs_norm_path::AbsNormPathBuf;
use crate::fs::paths::abs_path::AbsPath;
use crate::fs::paths::file_name::FileNameBuf;
use crate::fs::project::ProjectRoot;
use crate::fs::project_rel_path::ProjectRelativePath;
use crate::fs::project_rel_path::ProjectRelativePathBuf;
use crate::package::PackageLabel;

/// Errors from cell creation
#[derive(Error, Debug)]
enum CellError {
    #[error("Cell paths `{1}` and `{2}` had the same alias `{0}`.")]
    DuplicateAliases(NonEmptyCellAlias, CellRootPathBuf, CellRootPathBuf),
    #[error("Cell paths `{1}` and `{2}` had the same cell name `{0}`.")]
    DuplicateNames(CellName, CellRootPathBuf, CellRootPathBuf),
    #[error("Two cells, `{0}` and `{1}`, share the same path `{2}`")]
    DuplicatePaths(CellName, CellName, CellRootPathBuf),
    #[error("cannot find the cell at current path `{0}`. Known roots are `<{}>`", .1.join(", "))]
    UnknownCellPath(ProjectRelativePathBuf, Vec<String>),
    #[error("unknown cell alias: `{0}`. In cell `{1}`, known aliases are: `{}`", .2.iter().join(", "))]
    UnknownCellAlias(CellAlias, CellName, Vec<NonEmptyCellAlias>),
    #[error("unknown cell name: `{0}`. known cell names are `{}`", .1.iter().join(", "))]
    UnknownCellName(CellName, Vec<CellName>),
    #[error(
        "Cell name `{0}` should be an alias for an existing cell, but `{1}` isn't a known alias"
    )]
    AliasOnlyCell(NonEmptyCellAlias, NonEmptyCellAlias),
    #[error("Cell `{0}` alias `{0}` should point to itself, but it points to `{1}`")]
    WrongSelfAlias(CellName, CellName),
    #[error("No cell name for the root path, add an entry for `.`")]
    NoRootCell,
}

/// A 'CellAliasResolver' is unique to a 'CellInstance'.
/// It is responsible for resolving all 'CellAlias' encountered within the
/// 'CellInstance' into the global canonical 'CellName's
#[derive(Clone, Dupe, Debug, PartialEq, Eq, Allocative)]
pub struct CellAliasResolver {
    /// Current cell name.
    current: CellName,
    aliases: Arc<HashMap<NonEmptyCellAlias, CellName>>,
}

impl CellAliasResolver {
    /// Create an instance of `CellAliasResolver`. The special alias `""` must be present, or
    /// this will fail
    pub fn new(
        current: CellName,
        mut aliases: HashMap<NonEmptyCellAlias, CellName>,
    ) -> anyhow::Result<CellAliasResolver> {
        let current_as_alias = NonEmptyCellAlias::new(current.as_str().to_owned())?;
        if let Some(alias_target) = aliases.insert(current_as_alias, current) {
            if alias_target != current {
                return Err(CellError::WrongSelfAlias(current, alias_target).into());
            }
        }

        let aliases = Arc::new(aliases);

        Ok(CellAliasResolver { current, aliases })
    }

    /// resolves a 'CellAlias' into its corresponding 'CellName'
    pub fn resolve(&self, alias: &str) -> anyhow::Result<CellName> {
        if alias.is_empty() {
            return Ok(self.current);
        }
        self.aliases.get(alias).duped().ok_or_else(|| {
            anyhow::Error::new(CellError::UnknownCellAlias(
                CellAlias::new(alias.to_owned()),
                self.current,
                self.aliases.keys().cloned().collect(),
            ))
        })
    }

    /// finds the 'CellName' for the current cell (with the alias `""`. See module docs)
    pub fn resolve_self(&self) -> CellName {
        self.current
    }

    pub fn mappings(&self) -> impl Iterator<Item = (&NonEmptyCellAlias, CellName)> {
        self.aliases.iter().map(|(alias, name)| (alias, *name))
    }
}

/// Resolves 'CellName's into 'CellInstance's.
// TODO(bobyf) we need to check if cells changed
#[derive(Clone, Dupe, PartialEq, Eq, Debug, Allocative)]
pub struct CellResolver(Arc<CellResolverInternals>);

#[derive(PartialEq, Eq, Debug, Allocative)]
struct CellResolverInternals {
    cells: HashMap<CellName, CellInstance>,
    #[allocative(visit = crate::cells::sequence_trie_allocative::visit_sequence_trie)]
    path_mappings: SequenceTrie<FileNameBuf, CellName>,
    root_cell: CellName,
}

impl CellResolver {
    // Make this public till we start parsing config files from cells
    pub fn new(cells: Vec<CellInstance>) -> anyhow::Result<CellResolver> {
        let mut path_mappings: SequenceTrie<FileNameBuf, CellName> = SequenceTrie::new();
        let mut root_cell = None;
        for cell in &cells {
            if cell.path().is_empty() {
                root_cell = Some(cell.name());
            }
            let prev = path_mappings.insert(cell.path().iter(), cell.name());
            if let Some(prev) = prev {
                return Err(
                    CellError::DuplicatePaths(cell.name(), prev, cell.path().to_buf()).into(),
                );
            }
        }

        let mut cells_map: HashMap<CellName, CellInstance> = HashMap::with_capacity(cells.len());
        for cell in cells {
            match cells_map.entry(cell.name()) {
                hash_map::Entry::Occupied(entry) => {
                    return Err(CellError::DuplicateNames(
                        cell.name(),
                        entry.get().path().to_buf(),
                        cell.path().to_buf(),
                    )
                    .into());
                }
                hash_map::Entry::Vacant(entry) => {
                    entry.insert(cell);
                }
            }
        }

        let root_cell = root_cell.ok_or(CellError::NoRootCell)?;
        Ok(CellResolver(Arc::new(CellResolverInternals {
            cells: cells_map,
            root_cell,
            path_mappings,
        })))
    }

    /// Get a `Cell` from the `CellMap`
    pub fn get(&self, cell: CellName) -> anyhow::Result<&CellInstance> {
        self.0.cells.get(&cell).ok_or_else(|| {
            anyhow::Error::new(CellError::UnknownCellName(
                cell,
                self.0.cells.keys().copied().collect(),
            ))
        })
    }

    pub fn is_root_cell(&self, name: CellName) -> bool {
        name == self.0.root_cell
    }

    pub fn root_cell(&self) -> CellName {
        self.0.root_cell
    }

    pub fn root_cell_instance(&self) -> &CellInstance {
        self.get(self.root_cell())
            .expect("Should have had a root cell")
    }

    pub fn root_cell_cell_alias_resolver(&self) -> &CellAliasResolver {
        self.root_cell_instance().cell_alias_resolver()
    }

    /// Get a `CellName` from a path by finding the best matching cell path that
    /// is a prefix of the current path relative to the project root. e.g. `fbcode/foo/bar` matches
    /// cell path `fbcode`.
    pub fn find<P: AsRef<ProjectRelativePath> + ?Sized>(
        &self,
        path: &P,
    ) -> anyhow::Result<CellName> {
        self.0
            .path_mappings
            .get_ancestor(path.as_ref().iter())
            .copied()
            .ok_or_else(|| {
                anyhow::Error::new(CellError::UnknownCellPath(
                    path.as_ref().to_buf(),
                    self.0
                        .path_mappings
                        .keys()
                        .map(|p| p.iter().join("/"))
                        .collect(),
                ))
            })
    }

    pub fn get_cell_path<P: AsRef<ProjectRelativePath> + ?Sized>(
        &self,
        path: &P,
    ) -> anyhow::Result<CellPath> {
        let path = path.as_ref();
        let cell = self.find(path)?;
        let instance = self.get(cell)?;
        let relative = path.strip_prefix(instance.path().as_project_relative_path())?;
        Ok(CellPath::new(cell, relative.to_owned().into()))
    }

    pub fn get_cell_path_from_abs_path(
        &self,
        path: &AbsPath,
        fs: &ProjectRoot,
    ) -> anyhow::Result<CellPath> {
        let abs_path = AbsPath::new(path)?;
        self.get_cell_path(&fs.relativize_any(abs_path)?)
    }

    pub fn cells(&self) -> impl Iterator<Item = (CellName, &CellInstance)> {
        self.0
            .cells
            .iter()
            .map(|(name, instance)| (*name, instance))
    }

    /// Resolves a cell alias and a cell relative path into an absolute path.
    /// `cwd` is used to perform contextual resolution and figure out which
    /// cell mapping to use (i.e., map from alias to cell name).
    pub fn resolve_cell_relative_path(
        &self,
        cell_alias: &str,
        cell_relative_path: &str,
        project_filesystem: &ProjectRoot,
        cwd: &AbsNormPath,
    ) -> anyhow::Result<AbsNormPathBuf> {
        // We expect this to always succeed as long as the client connects to the
        // appropriate daemon.
        let proj_relative_path = project_filesystem
            .relativize(cwd)
            .with_context(|| format!("Error relativizing cwd (`{}`)", cwd))?;
        let context_cell_name = self.find(&proj_relative_path)?;
        let context_cell = self.get(context_cell_name)?;

        let resolved_cell_name = context_cell.cell_alias_resolver().resolve(cell_alias)?;
        let cell = self.get(resolved_cell_name)?;
        let cell_absolute_path = project_filesystem.resolve(cell.path().as_project_relative_path());
        cell_absolute_path.join_normalized(cell_relative_path)
    }

    /// Resolves a given 'Package' to the 'ProjectRelativePath' that points to
    /// the 'Package'
    ///
    /// ```
    /// use buck2_core::cells::CellResolver;
    /// use buck2_core::fs::project_rel_path::{ProjectRelativePath, ProjectRelativePathBuf};
    /// use std::convert::TryFrom;
    /// use buck2_core::cells::cell_path::CellPath;
    /// use buck2_core::cells::cell_root_path::CellRootPathBuf;
    /// use buck2_core::cells::name::CellName;
    /// use buck2_core::cells::paths::CellRelativePathBuf;
    ///
    /// let cell_path = ProjectRelativePath::new("my/cell")?;
    /// let cells = CellResolver::testing_with_name_and_path(
    ///     CellName::testing_new("mycell"),
    ///     CellRootPathBuf::new(cell_path.to_buf()),
    /// );
    ///
    /// let cell_path = CellPath::new(
    ///     CellName::testing_new("mycell"),
    ///     CellRelativePathBuf::unchecked_new("some/path".to_owned()));
    ///
    /// assert_eq!(
    ///     cells.resolve_path(cell_path.as_ref())?,
    ///     ProjectRelativePathBuf::unchecked_new("my/cell/some/path".into()),
    /// );
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn resolve_path(&self, cell_path: CellPathRef) -> anyhow::Result<ProjectRelativePathBuf> {
        Ok(self.get(cell_path.cell())?.path().join(cell_path.path()))
    }

    /// resolves a given 'Package' to the 'ProjectRelativePath' that points to
    /// the 'Package'
    ///
    /// ```
    /// use buck2_core::cells::CellResolver;
    /// use buck2_core::fs::project_rel_path::{ProjectRelativePath, ProjectRelativePathBuf};
    /// use buck2_core::fs::paths::forward_rel_path::{ForwardRelativePathBuf, ForwardRelativePath};
    /// use buck2_core::package::PackageLabel;
    /// use std::convert::TryFrom;
    /// use buck2_core::cells::cell_root_path::CellRootPathBuf;
    /// use buck2_core::cells::name::CellName;
    /// use buck2_core::cells::paths::CellRelativePath;
    ///
    /// let cell_path = ProjectRelativePath::new("my/cell")?;
    ///
    /// let cells = CellResolver::testing_with_name_and_path(
    ///     CellName::testing_new("mycell"),
    ///     CellRootPathBuf::new(cell_path.to_buf()),
    /// );
    ///
    /// let pkg = PackageLabel::new(
    ///     CellName::testing_new("mycell"),
    ///     CellRelativePath::unchecked_new("somepkg"),
    /// );
    ///
    /// assert_eq!(
    ///     cells.resolve_package(pkg)?,
    ///     ProjectRelativePathBuf::unchecked_new("my/cell/somepkg".into()),
    /// );
    ///
    /// # anyhow::Ok(())
    /// ```
    pub fn resolve_package(&self, pkg: PackageLabel) -> anyhow::Result<ProjectRelativePathBuf> {
        self.resolve_path(pkg.as_cell_path())
    }

    /// Resolves a 'BuckPath' into a 'ProjectRelativePath' based on the package
    /// and cell.
    pub fn resolve_buck_path(&self, path: BuckPathRef) -> anyhow::Result<ProjectRelativePathBuf> {
        Ok(self.resolve_package(path.package())?.join(path.path()))
    }

    // These are constructors for tests.

    pub fn testing_with_name_and_path(
        other_name: CellName,
        other_path: CellRootPathBuf,
    ) -> CellResolver {
        // It is an error to build a CellResolver that doesn't cover the root.
        // Therefore, if it isn't needed for the test, just make one up.
        if other_path.is_empty() {
            Self::testing_with_names_and_paths_with_alias(&[(
                other_name,
                other_path,
                HashMap::new(),
            )])
        } else {
            Self::testing_with_names_and_paths_with_alias(&[
                (other_name, other_path, HashMap::new()),
                (
                    CellName::testing_new("root"),
                    CellRootPathBuf::testing_new(""),
                    HashMap::new(),
                ),
            ])
        }
    }

    pub fn testing_with_names_and_paths(cells: &[(CellName, CellRootPathBuf)]) -> CellResolver {
        Self::testing_with_names_and_paths_with_alias(
            &cells.map(|(name, path)| (*name, path.clone(), HashMap::new())),
        )
    }

    pub fn testing_with_names_and_paths_with_alias(
        cells: &[(
            CellName,
            CellRootPathBuf,
            HashMap<NonEmptyCellAlias, CellName>,
        )],
    ) -> CellResolver {
        let cell_path_by_name: HashMap<CellName, CellRootPathBuf> = cells
            .iter()
            .map(|(name, path, _)| (*name, path.clone()))
            .collect();

        assert_eq!(cell_path_by_name.len(), cells.len(), "duplicate cell names");
        assert_eq!(
            cells.len(),
            cells
                .iter()
                .map(|(_, path, _)| path.as_path())
                .unique()
                .count(),
            "duplicate cell paths"
        );

        let mut cell_aggregator = CellsAggregator::new();

        for (name, path, alias) in cells {
            cell_aggregator.cell_info(path.clone()).name = Some(*name);

            for (alias, name) in alias {
                cell_aggregator
                    .add_cell_entry(
                        path.clone(),
                        alias.clone(),
                        cell_path_by_name.get(name).unwrap().clone(),
                    )
                    .unwrap();
            }
        }

        cell_aggregator.make_cell_resolver().unwrap()
    }

    pub(crate) fn resolve_path_crossing_cell_boundaries<'a>(
        &self,
        mut path: CellPathRef<'a>,
    ) -> anyhow::Result<CellPathRef<'a>> {
        let mut rem: u32 = 1000;
        loop {
            // Sanity check. Should never happen.
            rem = rem
                .checked_sub(1)
                .context("Overflow computing cell boundaries")?;

            let nested_cells = self.get(path.cell())?.nested_cells();
            match nested_cells.matches_checked(path.path()) {
                None => return Ok(path),
                Some((_, new_cell_path)) => {
                    path = new_cell_path;
                }
            }
        }
    }
}

/// Aggregates cell information as we parse cell configs and keeps state to
/// generate a final 'CellResolver'
#[derive(Debug)]
pub struct CellsAggregator {
    cell_infos: HashMap<CellRootPathBuf, CellAggregatorInfo>,
}

fn default_buildfiles() -> Vec<FileNameBuf> {
    (["BUCK.v2", "BUCK"][..]).map(|&n| FileNameBuf::try_from(n.to_owned()).unwrap())
}

#[derive(Default, Debug)]
struct CellAggregatorInfo {
    /// The name to use for this alias.
    /// So that it is predictable, we always use the first name we encounter,
    /// so the root file can choose what the alias is called.
    name: Option<CellName>,
    /// All the aliases known by this cell.
    alias_mapping: HashMap<NonEmptyCellAlias, CellRootPathBuf>,
    /// The build file name in this if it's been set. If it hasn't we'll use the
    /// default `["BUCK.v2", "BUCK"]` when building the resolver.
    buildfiles: Option<Vec<FileNameBuf>>,
}

impl CellAggregatorInfo {
    fn add_alias_mapping(
        &mut self,
        from: NonEmptyCellAlias,
        to: CellRootPathBuf,
    ) -> anyhow::Result<()> {
        let old = self.alias_mapping.insert(from.clone(), to.clone());
        if let Some(old) = old {
            if old != to {
                return Err(CellError::DuplicateAliases(from, old, to).into());
            }
        }
        Ok(())
    }
}

impl CellsAggregator {
    pub fn new() -> Self {
        Self {
            cell_infos: HashMap::new(),
        }
    }

    fn cell_info(&mut self, cell_path: CellRootPathBuf) -> &mut CellAggregatorInfo {
        self.cell_infos
            .entry(cell_path)
            .or_insert_with(CellAggregatorInfo::default)
    }

    /// Adds a cell configuration entry
    pub fn add_cell_entry(
        &mut self,
        cell_root: CellRootPathBuf,
        parsed_alias: NonEmptyCellAlias,
        alias_path: CellRootPathBuf,
    ) -> anyhow::Result<()> {
        let name = &mut self.cell_info(alias_path.clone()).name;
        if name.is_none() {
            *name = Some(CellName::unchecked_new(parsed_alias.as_str())?);
        }
        self.cell_info(cell_root)
            .add_alias_mapping(parsed_alias, alias_path)
    }

    /// Adds a cell alias configuration entry
    pub fn add_cell_alias(
        &mut self,
        cell_root: CellRootPathBuf,
        parsed_alias: NonEmptyCellAlias,
        alias_destination: NonEmptyCellAlias,
    ) -> anyhow::Result<CellRootPathBuf> {
        let cell_info = self.cell_info(cell_root);
        let alias_path = match cell_info.alias_mapping.get(&alias_destination) {
            None => return Err(CellError::AliasOnlyCell(parsed_alias, alias_destination).into()),
            Some(alias_path) => alias_path.clone(),
        };
        cell_info.add_alias_mapping(parsed_alias, alias_path.clone())?;
        Ok(alias_path)
    }

    pub fn set_buildfiles(&mut self, cell_root: CellRootPathBuf, buildfiles: Vec<FileNameBuf>) {
        let cell_info = self.cell_info(cell_root);
        cell_info.buildfiles = Some(buildfiles);
    }

    fn get_cell_name_from_path(&self, path: &CellRootPath) -> anyhow::Result<CellName> {
        self.cell_infos
            .get(path)
            .and_then(|info| info.name)
            .ok_or_else(|| {
                anyhow::anyhow!(CellError::UnknownCellPath(
                    path.as_project_relative_path().to_buf(),
                    self.cell_infos
                        .keys()
                        .map(|p| p.as_str().to_owned())
                        .collect()
                ))
            })
    }

    /// Creates the 'CellResolver' from all the entries that were aggregated
    pub fn make_cell_resolver(self) -> anyhow::Result<CellResolver> {
        let mut cell_mappings = Vec::new();

        let all_cell_roots_for_nested_cells: Vec<_> = self
            .cell_infos
            .keys()
            .map(|path| Ok((self.get_cell_name_from_path(path)?, path.as_path())))
            .collect::<anyhow::Result<_>>()?;

        for (cell_path, cell_info) in &self.cell_infos {
            let nested_cells =
                NestedCells::from_cell_roots(&all_cell_roots_for_nested_cells, cell_path);

            let mut aliases_for_cell = HashMap::new();
            let cell_name = self.get_cell_name_from_path(cell_path)?;

            for (alias, path_for_alias) in &cell_info.alias_mapping {
                aliases_for_cell
                    .insert(alias.clone(), self.get_cell_name_from_path(path_for_alias)?);
            }

            cell_mappings.push(CellInstance::new(
                cell_name,
                cell_path.clone(),
                cell_info
                    .buildfiles
                    .clone()
                    .unwrap_or_else(default_buildfiles),
                CellAliasResolver::new(cell_name, aliases_for_cell)?,
                nested_cells,
            )?);
        }

        CellResolver::new(cell_mappings)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::cells::CellResolver;
    use crate::fs::paths::forward_rel_path::ForwardRelativePath;
    use crate::fs::paths::forward_rel_path::ForwardRelativePathBuf;

    #[test]
    fn test_of_names_and_paths() -> anyhow::Result<()> {
        use crate::fs::project_rel_path::ProjectRelativePathBuf;

        let cell_resolver = CellResolver::testing_with_name_and_path(
            CellName::testing_new("foo"),
            CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new("bar".into())),
        );

        let cell = cell_resolver.get(CellName::testing_new("foo"))?;
        assert_eq!(CellName::testing_new("foo"), cell.name());
        assert_eq!("bar", cell.path().as_str());

        Ok(())
    }

    #[test]
    fn test_cells() -> anyhow::Result<()> {
        let cell1_path = CellRootPath::new(ProjectRelativePath::new("my/cell1")?);
        let cell2_path = CellRootPath::new(ProjectRelativePath::new("cell2")?);
        let cell3_path = CellRootPath::new(ProjectRelativePath::new("my/cell3")?);

        let cells = CellResolver::testing_with_names_and_paths_with_alias(&[
            (
                CellName::testing_new("root"),
                CellRootPathBuf::testing_new(""),
                HashMap::new(),
            ),
            (
                CellName::testing_new("cell1"),
                cell1_path.to_buf(),
                hashmap![
                    NonEmptyCellAlias::new("cell1".to_owned()).unwrap() => CellName::testing_new("cell1"),
                    NonEmptyCellAlias::new("cell2".to_owned()).unwrap() => CellName::testing_new("cell2"),
                    NonEmptyCellAlias::new("cell3".to_owned()).unwrap() => CellName::testing_new("cell3"),
                ],
            ),
            (
                CellName::testing_new("cell2"),
                cell2_path.to_buf(),
                hashmap![
                    NonEmptyCellAlias::new("cell2".to_owned()).unwrap() => CellName::testing_new("cell2"),
                    NonEmptyCellAlias::new("cell1".to_owned()).unwrap() => CellName::testing_new("cell1"),
                    NonEmptyCellAlias::new("cell3".to_owned()).unwrap() => CellName::testing_new("cell3"),
                ],
            ),
            (
                CellName::testing_new("cell3"),
                cell3_path.to_buf(),
                hashmap![
                    NonEmptyCellAlias::new("z_cell3".to_owned()).unwrap() => CellName::testing_new("cell3"),
                    NonEmptyCellAlias::new("z_cell1".to_owned()).unwrap() => CellName::testing_new("cell1"),
                    NonEmptyCellAlias::new("z_cell2".to_owned()).unwrap() => CellName::testing_new("cell2"),
                ],
            ),
        ]);

        {
            let cell1 = cells.get(CellName::testing_new("cell1")).unwrap();
            assert_eq!(cell1.path(), cell1_path);

            let aliases = cell1.cell_alias_resolver();
            assert_eq!(aliases.resolve("").unwrap(), CellName::testing_new("cell1"));
            assert_eq!(
                aliases.resolve("cell1").unwrap(),
                CellName::testing_new("cell1")
            );
            assert_eq!(
                aliases.resolve("cell2").unwrap(),
                CellName::testing_new("cell2")
            );
            assert_eq!(
                aliases.resolve("cell3").unwrap(),
                CellName::testing_new("cell3")
            );
        }

        {
            let cell2 = cells.get(CellName::testing_new("cell2")).unwrap();
            assert_eq!(cell2.path(), cell2_path);

            let aliases = cell2.cell_alias_resolver();
            assert_eq!(aliases.resolve("").unwrap(), CellName::testing_new("cell2"));
            assert_eq!(
                aliases.resolve("cell1").unwrap(),
                CellName::testing_new("cell1")
            );
            assert_eq!(
                aliases.resolve("cell2").unwrap(),
                CellName::testing_new("cell2")
            );
            assert_eq!(
                aliases.resolve("cell3").unwrap(),
                CellName::testing_new("cell3")
            );
        }

        {
            let cell3 = cells.get(CellName::testing_new("cell3")).unwrap();
            assert_eq!(cell3.path(), cell3_path);

            let aliases = cell3.cell_alias_resolver();
            assert_eq!(aliases.resolve("").unwrap(), CellName::testing_new("cell3"));
            assert_eq!(
                aliases.resolve("z_cell1").unwrap(),
                CellName::testing_new("cell1")
            );
            assert_eq!(
                aliases.resolve("z_cell2").unwrap(),
                CellName::testing_new("cell2")
            );
            assert_eq!(
                aliases.resolve("z_cell3").unwrap(),
                CellName::testing_new("cell3")
            );
        }

        assert_eq!(cells.find(cell1_path)?, CellName::testing_new("cell1"));
        assert_eq!(cells.find(cell2_path)?, CellName::testing_new("cell2"));
        assert_eq!(cells.find(cell3_path)?, CellName::testing_new("cell3"));
        assert_eq!(
            cells.find(
                &cell2_path
                    .as_project_relative_path()
                    .join(ForwardRelativePath::new("fake/cell3")?)
            )?,
            CellName::testing_new("cell2")
        );
        assert_eq!(
            cells.find(
                &cell3_path
                    .as_project_relative_path()
                    .join(ForwardRelativePath::new("more/foo")?)
            )?,
            CellName::testing_new("cell3")
        );

        assert_eq!(
            cells.get_cell_path(cell1_path)?,
            CellPath::new(
                CellName::testing_new("cell1"),
                ForwardRelativePathBuf::unchecked_new("".to_owned()).into()
            )
        );

        assert_eq!(
            cells.get_cell_path(cell2_path)?,
            CellPath::new(
                CellName::testing_new("cell2"),
                ForwardRelativePathBuf::unchecked_new("".to_owned()).into()
            )
        );

        assert_eq!(
            cells.get_cell_path(
                &cell2_path
                    .as_project_relative_path()
                    .join(ForwardRelativePath::new("fake/cell3")?)
            )?,
            CellPath::new(
                CellName::testing_new("cell2"),
                ForwardRelativePathBuf::unchecked_new("fake/cell3".to_owned()).into()
            )
        );

        Ok(())
    }

    #[test]
    fn test_duplicate_aliases() -> anyhow::Result<()> {
        let mut agg = CellsAggregator::new();

        let cell_root = CellRootPathBuf::new(ProjectRelativePathBuf::try_from("".to_owned())?);
        let alias_path =
            CellRootPathBuf::new(ProjectRelativePathBuf::try_from("random/path".to_owned())?);

        agg.add_cell_entry(
            cell_root.clone(),
            NonEmptyCellAlias::new("root".to_owned()).unwrap(),
            cell_root.clone(),
        )?;
        agg.add_cell_entry(
            cell_root.clone(),
            NonEmptyCellAlias::new("hello".to_owned()).unwrap(),
            alias_path.clone(),
        )?;
        agg.add_cell_entry(
            cell_root.clone(),
            NonEmptyCellAlias::new("cruel".to_owned()).unwrap(),
            alias_path.clone(),
        )?;
        agg.add_cell_entry(
            cell_root,
            NonEmptyCellAlias::new("world".to_owned()).unwrap(),
            alias_path,
        )?;

        // We want the first alias to win (hello), rather than the lexiographically first (cruel)
        let cell_resolver = agg.make_cell_resolver()?;
        assert!(cell_resolver.get(CellName::testing_new("hello")).is_ok());
        assert!(cell_resolver.get(CellName::testing_new("cruel")).is_err());
        Ok(())
    }

    #[test]
    fn test_alias_only_error() -> anyhow::Result<()> {
        let mut agg = CellsAggregator::new();

        let cell_root = CellRootPathBuf::new(ProjectRelativePathBuf::try_from("".to_owned())?);
        assert!(
            agg.add_cell_alias(
                cell_root,
                NonEmptyCellAlias::new("root".to_owned()).unwrap(),
                NonEmptyCellAlias::new("does_not_exist".to_owned()).unwrap(),
            )
            .is_err()
        );
        Ok(())
    }

    #[test]
    fn test_resolve_path_crossing_cell_boundaries() {
        let cell_resolver = CellResolver::testing_with_names_and_paths(&[
            (
                CellName::testing_new("fbsource"),
                CellRootPathBuf::testing_new(""),
            ),
            (
                CellName::testing_new("fbcode"),
                CellRootPathBuf::testing_new("fbcode"),
            ),
            (
                CellName::testing_new("fbcode_macros"),
                CellRootPathBuf::testing_new("fbcode/something/macros"),
            ),
        ]);
        // Test starting with `fbsource//`.
        assert_eq!(
            CellPathRef::testing_new("fbsource//"),
            cell_resolver
                .resolve_path_crossing_cell_boundaries(CellPathRef::testing_new("fbsource//"))
                .unwrap()
        );
        assert_eq!(
            CellPathRef::testing_new("fbcode//"),
            cell_resolver
                .resolve_path_crossing_cell_boundaries(CellPathRef::testing_new("fbsource//fbcode"))
                .unwrap()
        );
        assert_eq!(
            CellPathRef::testing_new("fbcode//something"),
            cell_resolver
                .resolve_path_crossing_cell_boundaries(CellPathRef::testing_new(
                    "fbsource//fbcode/something"
                ))
                .unwrap()
        );
        assert_eq!(
            CellPathRef::testing_new("fbcode_macros//"),
            cell_resolver
                .resolve_path_crossing_cell_boundaries(CellPathRef::testing_new(
                    "fbsource//fbcode/something/macros"
                ))
                .unwrap()
        );
        assert_eq!(
            CellPathRef::testing_new("fbcode_macros//xx"),
            cell_resolver
                .resolve_path_crossing_cell_boundaries(CellPathRef::testing_new(
                    "fbsource//fbcode/something/macros/xx"
                ))
                .unwrap()
        );
        // Now test starting with `fbcode//`.
        assert_eq!(
            CellPathRef::testing_new("fbcode//"),
            cell_resolver
                .resolve_path_crossing_cell_boundaries(CellPathRef::testing_new("fbcode//"))
                .unwrap()
        );
        assert_eq!(
            CellPathRef::testing_new("fbcode//something"),
            cell_resolver
                .resolve_path_crossing_cell_boundaries(CellPathRef::testing_new(
                    "fbcode//something"
                ))
                .unwrap()
        );
        assert_eq!(
            CellPathRef::testing_new("fbcode_macros//"),
            cell_resolver
                .resolve_path_crossing_cell_boundaries(CellPathRef::testing_new(
                    "fbcode//something/macros"
                ))
                .unwrap()
        );
        assert_eq!(
            CellPathRef::testing_new("fbcode_macros//xx"),
            cell_resolver
                .resolve_path_crossing_cell_boundaries(CellPathRef::testing_new(
                    "fbcode//something/macros/xx"
                ))
                .unwrap()
        );
    }
}


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

use std::hash::Hash;
use std::hash::Hasher;

use allocative::Allocative;
use derive_more::Display;
use dupe::Dupe;
use fnv::FnvHasher;
use internment_tweaks::Equiv;
use internment_tweaks::Intern;
use internment_tweaks::StaticInterner;

#[derive(Debug, thiserror::Error)]
enum CellNameError {
    #[error("Cell name must be non-empty")]
    Empty,
}

#[derive(Clone, Debug, Display, Eq, PartialEq, Ord, PartialOrd, Allocative)]
struct CellNameData(Box<str>);

#[allow(clippy::derived_hash_with_manual_eq)]
impl Hash for CellNameData {
    fn hash<H: Hasher>(&self, state: &mut H) {
        CellNameDataRef(&self.0).hash(state)
    }
}

#[derive(Clone, Debug, Display, Hash, Eq, PartialEq)]
struct CellNameDataRef<'a>(&'a str);

impl<'a> Equiv<CellNameData> for CellNameDataRef<'a> {
    fn equivalent(&self, key: &CellNameData) -> bool {
        self.0 == &*key.0
    }
}

impl<'a> From<CellNameDataRef<'a>> for CellNameData {
    fn from(d: CellNameDataRef<'a>) -> Self {
        CellNameData(d.0.into())
    }
}

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

/// A 'CellName' is a canonicalized, human-readable name that corresponds to a
/// 'CellInstance'. There should be a one to one mapping between a 'CellName'
/// and a 'CellInstance'.
///
/// The cell within a fully qualified target like `foo//some:target` is `foo`.
/// The cell name is also restricted to alphabet characters (i.e. shouldn't
/// contain any special characters like `/`), so `foo/bar//some:target` has an
/// invalid cell name of `foo/bar`.
#[derive(
    Clone, Dupe, Copy, Debug, Display, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative
)]
pub struct CellName(Intern<CellNameData>);

impl CellName {
    /// Construct a cell name.
    ///
    /// This function is unchecked because it does not validate that the cell points
    /// to an existing cell. This function should only be used when creating
    /// repository cells at startup.
    pub fn unchecked_new(name: &str) -> anyhow::Result<CellName> {
        if name.is_empty() {
            return Err(CellNameError::Empty.into());
        }
        Ok(CellName(INTERNER.intern(CellNameDataRef(name))))
    }

    pub fn testing_new(name: &str) -> CellName {
        CellName::unchecked_new(name).unwrap()
    }

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


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

use allocative::Allocative;

use crate::cells::cell_path::CellPathRef;
use crate::cells::cell_root_path::CellRootPath;
use crate::cells::name::CellName;
use crate::cells::paths::CellRelativePath;
use crate::cells::paths::CellRelativePathBuf;
use crate::cells::unchecked_cell_rel_path::UncheckedCellRelativePath;

/// Paths to cells which reside inside current cell.
///
/// Target labels cannot cross cell boundaries. This utility helps to identify such targets.
#[derive(Eq, PartialEq, Debug, Allocative, Clone)]
pub struct NestedCells {
    paths: Vec<(CellRelativePathBuf, CellName)>,
}

impl NestedCells {
    fn from_cell_paths_relative_to_this_cell<'a>(
        paths: impl IntoIterator<Item = (&'a CellRelativePath, CellName)>,
    ) -> NestedCells {
        let mut paths: Vec<_> = paths.into_iter().filter(|(p, _)| !p.is_empty()).collect();

        let mut final_paths: Vec<(CellRelativePathBuf, CellName)> = Vec::new();

        paths.sort();

        // Remove redundant paths, e.g. if `paths` contains `a` and `a/b`, then `a/b` is redundant.
        for (path, cell_name) in paths {
            if let Some((last_path, _)) = final_paths.last() {
                if path.starts_with(last_path) {
                    continue;
                }
            }

            final_paths.push((path.to_owned(), cell_name));
        }

        NestedCells { paths: final_paths }
    }

    pub fn from_cell_roots(
        all_cells: &[(CellName, &CellRootPath)],
        this_cell: &CellRootPath,
    ) -> NestedCells {
        Self::from_cell_paths_relative_to_this_cell(all_cells.iter()
            .filter_map(|(cell_name, cell_root_path)| {
                let Some(path_relative_to_this_cell) = cell_root_path.strip_prefix_opt(this_cell) else {
                    return None;
                };

                Some((
                    CellRelativePath::new(path_relative_to_this_cell),
                    *cell_name,
                ))
            }))
    }

    pub fn matches<'a, 'b>(
        &'a self,
        path: &'b UncheckedCellRelativePath,
    ) -> Option<(
        &'a CellRelativePath,
        CellName,
        &'b UncheckedCellRelativePath,
    )> {
        for (cell_path, cell_name) in &self.paths {
            if let Some(rem) = path.remove_prefix(cell_path) {
                return Some((cell_path, *cell_name, rem));
            }
        }
        None
    }

    pub(crate) fn matches_checked<'a, 'b>(
        &'a self,
        path: &'b CellRelativePath,
    ) -> Option<(&'a CellRelativePath, CellPathRef<'b>)> {
        self.matches(UncheckedCellRelativePath::new(path))
            .map(|(cell_path, cell_name, rem)| {
                (
                    cell_path,
                    CellPathRef::new(cell_name, CellRelativePath::unchecked_new(rem.as_str())),
                )
            })
    }
}

#[cfg(test)]
mod tests {

    use crate::cells::name::CellName;
    use crate::cells::nested::NestedCells;
    use crate::cells::paths::CellRelativePath;
    use crate::cells::paths::CellRelativePathBuf;

    #[test]
    fn test_nested_cells() {
        let nested_cells = NestedCells::from_cell_paths_relative_to_this_cell([
            (
                CellRelativePath::testing_new("cell_a/cell_b/cell_c"),
                CellName::testing_new("abc"),
            ),
            (
                CellRelativePath::testing_new("cell_a"),
                CellName::testing_new("a"),
            ),
            (
                CellRelativePath::testing_new("cell_x/cell_y"),
                CellName::testing_new("xy"),
            ),
            (
                CellRelativePath::testing_new("cell_a/cell_b"),
                CellName::testing_new("ab"),
            ),
        ]);

        assert_eq!(
            NestedCells {
                paths: vec![
                    (
                        CellRelativePathBuf::try_from("cell_a".to_owned()).unwrap(),
                        CellName::testing_new("a")
                    ),
                    (
                        CellRelativePathBuf::try_from("cell_x/cell_y".to_owned()).unwrap(),
                        CellName::testing_new("xy")
                    )
                ]
            },
            nested_cells,
        );
    }
}