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

use std::collections::HashMap;
use std::sync::Arc;

use allocative::Allocative;
use buck2_core::bzl::ImportPath;
use buck2_core::cells::build_file_cell::BuildFileCell;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::paths::CellRelativePath;
use buck2_core::cells::paths::CellRelativePathBuf;
use buck2_core::cells::CellAliasResolver;
use buck2_core::package::PackageLabel;
use thiserror::Error;

use crate::parse_import::parse_import;

#[derive(Error, Debug)]
enum PackageImportsError {
    #[error("Expected value to contain `=>`. Got `{0}`.")]
    MissingArrow(String),
    #[error("Expected value to contain `::`. Got `{0}`.")]
    MissingColons(String),
}

#[derive(Debug, Eq, PartialEq, Allocative)]
pub struct ImplicitImport {
    import: ImportPath,
    // Oddly buckv1 allows renaming symbols for these imports.
    symbols: HashMap<String, String>,
}

impl ImplicitImport {
    pub fn import(&self) -> &ImportPath {
        &self.import
    }

    pub fn lookup_alias<'a>(&'a self, name: &'a str) -> &'a str {
        match self.symbols.get(name) {
            Some(v) => v,
            None => name,
        }
    }
}

/// Supports parsing and resolution of package implicit imports.
#[derive(PartialEq, Debug, Allocative)]
pub struct PackageImplicitImports {
    /// It would probably be a little nicer if this were a sequence_trie, but
    /// that doesn't support Borrow in the same way normal maps do, so in it's
    /// current state it's unclear if it would be better.
    mappings: HashMap<CellRelativePathBuf, Arc<ImplicitImport>>,
}

impl PackageImplicitImports {
    /// Mappings are encoded roughly like so:
    ///
    /// full_mappings: `package_mapping`,`package_mapping`,...
    /// package_mapping:
    /// `package_path`=>`import_path`::`symbol_spec`::`symbol_spec`::
    /// `symbol_spec`::... symbol_spec: `alias`=`symbol` | `symbol`
    pub fn new(
        cell_name: BuildFileCell,
        cell_alias_resolver: CellAliasResolver,
        encoded_mappings: Option<&str>,
    ) -> anyhow::Result<Self> {
        let mut mappings = HashMap::new();
        if let Some(value) = encoded_mappings {
            let root_path = CellPath::new(
                cell_name.name(),
                CellRelativePathBuf::unchecked_new("".to_owned()),
            );
            for item in value.split(',') {
                let (dir, import_spec) = item.trim().split_once("=>").ok_or_else(|| {
                    anyhow::anyhow!(PackageImportsError::MissingArrow(item.to_owned()))
                })?;
                let (import, symbol_specs) = import_spec.split_once("::").ok_or_else(|| {
                    anyhow::anyhow!(PackageImportsError::MissingColons(import_spec.to_owned()))
                })?;
                let import_path = parse_import(&cell_alias_resolver, &root_path, import)?;
                // Package implicit imports are only going to be used for a top-level module in
                // the same cell, so we can set that early.
                let import_path = ImportPath::new_with_build_file_cells(import_path, cell_name)?;
                let mut symbols = HashMap::new();
                for spec in symbol_specs.split("::") {
                    let (alias, symbol) = match spec.split_once('=') {
                        Some(v) => v,
                        None => (spec, spec),
                    };
                    symbols.insert(alias.to_owned(), symbol.to_owned());
                }

                mappings.insert(
                    CellRelativePathBuf::try_from(dir.to_owned())?,
                    Arc::new(ImplicitImport {
                        import: import_path,
                        symbols,
                    }),
                );
            }
        }
        Ok(Self { mappings })
    }

    pub fn get(&self, package: PackageLabel) -> Option<&Arc<ImplicitImport>> {
        let package_dir = package.cell_relative_path();
        let mut package_dir: Option<&CellRelativePath> = Some(package_dir);

        while let Some(d) = package_dir {
            if let Some(v) = self.mappings.get(d) {
                return Some(v);
            }
            package_dir = d.parent();
        }
        None
    }
}

#[cfg(test)]
mod tests {

    use buck2_core::cells::alias::NonEmptyCellAlias;
    use buck2_core::cells::name::CellName;
    use buck2_core::cells::CellAliasResolver;
    use dupe::Dupe;

    use super::*;

    #[test]
    fn test() -> anyhow::Result<()> {
        let cell_alias_resolver = CellAliasResolver::new(
            CellName::testing_new("root"),
            vec![("root", "root"), ("cell1", "cell1")]
                .into_iter()
                .map(|(alias, name)| {
                    (
                        NonEmptyCellAlias::new(alias.to_owned()).unwrap(),
                        CellName::testing_new(name),
                    )
                })
                .collect(),
        )?;

        let root_name = CellName::testing_new("root");
        let imports = PackageImplicitImports::new(
            BuildFileCell::new(root_name),
            cell_alias_resolver,
            Some(
                "src=>//:src.bzl::symbols,src/bin=>//:bin.bzl::symbols , other=>@cell1//:other.bzl::alias1=symbol1::alias2=symbol2::symbol3",
            ),
        )?;

        assert_eq!(
            None,
            imports.get(PackageLabel::testing_parse("root//java/src"))
        );

        let expect_import = |cell, path| {
            let package = PackageLabel::testing_new(cell, path);
            match imports.get(package.dupe()) {
                None => panic!("Should've had implicit import for {}", package),
                Some(v) => v,
            }
        };

        let import = expect_import("root", "src/java/com");
        assert_eq!("root//src.bzl", import.import().to_string());
        assert_eq!("symbols", import.lookup_alias("symbols"));
        assert_eq!("other", import.lookup_alias("other"));

        let import = expect_import("root", "src");
        assert_eq!("root//src.bzl", import.import().to_string());

        let import = expect_import("root", "src/bin");
        assert_eq!("root//bin.bzl", import.import().to_string());

        let import = expect_import("root", "other/bin");
        assert_eq!("cell1//other.bzl@root", import.import().to_string());
        assert_eq!("symbol1", import.lookup_alias("alias1"));
        assert_eq!("symbol2", import.lookup_alias("alias2"));
        assert_eq!("symbol3", import.lookup_alias("symbol3"));
        assert_eq!("alias3", import.lookup_alias("alias3"));
        assert_eq!("symbol1", import.lookup_alias("symbol1"));

        assert_eq!(None, imports.get(PackageLabel::testing_parse("root//")));
        assert_eq!(
            None,
            imports.get(PackageLabel::testing_parse("root//third_party/src"))
        );

        Ok(())
    }
}


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

//! Parses imports for load_file() calls in build files.

use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::paths::CellRelativePath;
use buck2_core::cells::paths::CellRelativePathBuf;
use buck2_core::cells::CellAliasResolver;
use buck2_core::fs::paths::file_name::FileName;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
use thiserror::Error;

#[derive(Error, Debug)]
enum ImportParseError {
    #[error(
        "Unable to parse import spec. Expected format `(@<cell>)//package/name:filename.bzl` or `:filename.bzl`. Got `{0}`"
    )]
    MatchFailed(String),
    #[error(
        "Unable to parse import spec. Expected format `(@<cell>)//package/name:filename.bzl` or `:filename.bzl`, but got an empty filename. Got `{0}`"
    )]
    EmptyFileName(String),
    #[error("Unexpected relative import spec. Got `{0}`")]
    ProhibitedRelativeImport(String),
    #[error(
        "Invalid path `{0}` for file relative import path. Should be a forward relative path."
    )]
    InvalidCurrentPathWhenFileRelativeImport(String),
    #[error(
        "Unable to parse import spec. Expected format `(@<cell>)//package/name:filename.bzl` or `:filename.bzl`, but got a path. Got `{0}`"
    )]
    NotAFileName(String),
}

/// Extra options for parsing a load() or load-like path into a `BuckPath`
pub struct ParseImportOptions {
    /// Whether '@' is required at the beginning of the import.
    pub allow_missing_at_symbol: bool,
    /// Whether relative imports (':bar.bzl') are allowed.
    pub allow_relative_imports: bool,
}

// Parses a string of the form `(@<cell>)//dir/name` to the corresponding
// alias and cell relative path.
fn parse_import_cell_path_parts(path: &str, allow_missing_at_symbol: bool) -> Option<(&str, &str)> {
    let (alias, cell_rel_path) = path.split_once("//")?;
    let alias = if alias.is_empty() {
        alias
    } else if !alias.starts_with('@') {
        if !allow_missing_at_symbol {
            return None;
        }
        alias
    } else {
        &alias[1..]
    };
    Some((alias, cell_rel_path))
}

pub fn parse_import(
    cell_resolver: &CellAliasResolver,
    current_path: &CellPath,
    import: &str,
) -> anyhow::Result<CellPath> {
    const OPTS: ParseImportOptions = ParseImportOptions {
        allow_missing_at_symbol: false,
        allow_relative_imports: true,
    };
    parse_import_with_config(cell_resolver, current_path, import, &OPTS)
}

/// Parse import string into a BuckPath, but potentially be more or less flexible with what is
/// accepted.
///
/// Common use case is e.g. allowing "cell//foo:bar.bzl" to be passed on the command line
/// and letting that be turned into an ImportPath eventually, or disallowing relative imports
/// from command line arguments.
///
/// Strings for the `load()` statement in starlark files should use [`parse_import`]
pub fn parse_import_with_config(
    cell_resolver: &CellAliasResolver,
    current_dir: &CellPath,
    import: &str,
    opts: &ParseImportOptions,
) -> anyhow::Result<CellPath> {
    match import.split_once(':') {
        None => {
            // import without `:`, so just try to parse the cell and cell relative paths

            match parse_import_cell_path_parts(import, opts.allow_missing_at_symbol) {
                None => {
                    if opts.allow_relative_imports {
                        let rel_path = ForwardRelativePath::new(import).map_err(|_e| {
                            ImportParseError::InvalidCurrentPathWhenFileRelativeImport(
                                import.to_owned(),
                            )
                        })?;
                        Ok(current_dir.join(rel_path))
                    } else {
                        Err(anyhow::anyhow!(ImportParseError::ProhibitedRelativeImport(
                            import.to_owned()
                        )))
                    }
                }
                Some((alias, cell_relative_path)) => {
                    let cell = cell_resolver.resolve(alias)?;
                    Ok(CellPath::new(
                        cell,
                        CellRelativePathBuf::try_from(cell_relative_path.to_owned())?,
                    ))
                }
            }
        }
        Some((path, filename)) => {
            if filename.is_empty() {
                return Err(anyhow::anyhow!(ImportParseError::EmptyFileName(
                    import.to_owned()
                )));
            }

            let filename = FileName::new(filename)
                .map_err(|_| ImportParseError::NotAFileName(import.to_owned()))?;

            if path.is_empty() {
                if opts.allow_relative_imports {
                    Ok(current_dir.join(filename))
                } else {
                    Err(anyhow::anyhow!(ImportParseError::ProhibitedRelativeImport(
                        import.to_owned()
                    )))
                }
            } else {
                let (alias, cell_relative_path) =
                    parse_import_cell_path_parts(path, opts.allow_missing_at_symbol)
                        .ok_or_else(|| ImportParseError::MatchFailed(import.to_owned()))?;
                let cell = cell_resolver.resolve(alias)?;
                Ok(CellPath::new(
                    cell,
                    <&CellRelativePath>::try_from(cell_relative_path)?.join(filename),
                ))
            }
        }
    }
}

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

    use buck2_core::cells::alias::NonEmptyCellAlias;
    use buck2_core::cells::name::CellName;
    use buck2_core::cells::CellAliasResolver;

    use super::*;

    fn resolver() -> CellAliasResolver {
        let mut m = HashMap::new();
        m.insert(
            NonEmptyCellAlias::new("cell1".to_owned()).unwrap(),
            CellName::testing_new("cell1"),
        );
        m.insert(
            NonEmptyCellAlias::new("alias2".to_owned()).unwrap(),
            CellName::testing_new("cell2"),
        );
        CellAliasResolver::new(CellName::testing_new("root"), m).expect("valid resolver")
    }

    fn path(cell: &str, dir: &str, filename: &str) -> CellPath {
        CellPath::new(
            CellName::testing_new(cell),
            CellRelativePath::unchecked_new(dir).join(FileName::unchecked_new(filename)),
        )
    }

    #[test]
    fn root_package() -> anyhow::Result<()> {
        assert_eq!(
            path("root", "package/path", "import.bzl"),
            parse_import(
                &resolver(),
                &CellPath::testing_new("passport//"),
                "//package/path:import.bzl"
            )?
        );
        Ok(())
    }

    #[test]
    fn cell_package() -> anyhow::Result<()> {
        assert_eq!(
            path("cell1", "package/path", "import.bzl"),
            parse_import(
                &resolver(),
                &CellPath::testing_new("root//"),
                "@cell1//package/path:import.bzl"
            )?
        );
        Ok(())
    }

    #[test]
    fn package_relative() -> anyhow::Result<()> {
        assert_eq!(
            path("cell1", "package/path", "import.bzl"),
            parse_import(
                &resolver(),
                &CellPath::testing_new("cell1//package/path"),
                ":import.bzl"
            )?
        );
        Ok(())
    }

    #[test]
    fn missing_colon() -> anyhow::Result<()> {
        let import = "//package/path/import.bzl".to_owned();
        assert_eq!(
            parse_import(&resolver(), &CellPath::testing_new("lighter//"), &import)?,
            path("root", "package/path", "import.bzl")
        );
        Ok(())
    }

    #[test]
    fn empty_filename() -> anyhow::Result<()> {
        let path = "//package/path:".to_owned();
        match parse_import(&resolver(), &CellPath::testing_new("root//"), &path) {
            Ok(import) => panic!("Expected parse failure for {}, got result {}", path, import),
            Err(e) => {
                assert_eq!(
                    format!("{:#}", e),
                    ImportParseError::EmptyFileName(path.to_owned()).to_string()
                );
            }
        }
        Ok(())
    }

    #[test]
    fn bad_alias() -> anyhow::Result<()> {
        let path = "bad_alias//package/path:".to_owned();
        match parse_import(&resolver(), &CellPath::testing_new("root//"), &path) {
            Ok(import) => panic!("Expected parse failure for {}, got result {}", path, import),
            Err(_) => {
                // TODO: should we verify the contents of the error?
            }
        }
        Ok(())
    }

    #[test]
    fn file_relative_import_given_relative_paths_allowed() -> anyhow::Result<()> {
        assert_eq!(
            path("cell1", "package/path", "bar.bzl"),
            parse_import(
                &resolver(),
                &CellPath::testing_new("cell1//package/path"),
                "bar.bzl",
            )?
        );
        assert_eq!(
            path("cell1", "package/path", "foo/bar.bzl"),
            parse_import(
                &resolver(),
                &CellPath::testing_new("cell1//package/path"),
                "foo/bar.bzl",
            )?
        );
        Ok(())
    }

    #[test]
    fn cell_relative_import_given_relative_paths_allowed() -> anyhow::Result<()> {
        let importer = CellPath::testing_new("cell1//package/path");
        let importee = "foo/bar.bzl";

        assert_eq!(
            parse_import(&resolver(), &importer, importee)?,
            path("cell1", "package/path/foo", "bar.bzl")
        );
        Ok(())
    }

    #[test]
    fn regular_import_given_relative_paths_allowed() -> anyhow::Result<()> {
        assert_eq!(
            path("cell1", "package/path", "import.bzl"),
            parse_import(
                &resolver(),
                &CellPath::testing_new("root//foo/bar"),
                "@cell1//package/path:import.bzl",
            )?
        );
        Ok(())
    }

    #[test]
    fn allows_non_at_symbols() -> anyhow::Result<()> {
        assert_eq!(
            path("cell1", "package/path", "import.bzl"),
            parse_import_with_config(
                &resolver(),
                &CellPath::testing_new("root//"),
                "cell1//package/path:import.bzl",
                &ParseImportOptions {
                    allow_missing_at_symbol: true,
                    allow_relative_imports: true
                }
            )?,
        );
        Ok(())
    }

    #[test]
    fn fails_relative_import_if_disallowed() -> anyhow::Result<()> {
        let imported_file = ":bar.bzl";
        let res = parse_import_with_config(
            &resolver(),
            &CellPath::testing_new("root//"),
            imported_file,
            &ParseImportOptions {
                allow_missing_at_symbol: false,
                allow_relative_imports: false,
            },
        );
        match res {
            Ok(res) => panic!(
                "Expected parse failure for {}, got result {}",
                imported_file, res
            ),
            Err(e) => {
                assert_eq!(
                    format!("{:#}", e),
                    ImportParseError::ProhibitedRelativeImport(imported_file.to_owned())
                        .to_string()
                );
            }
        };
        Ok(())
    }
}


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

use std::borrow::Cow;
use std::hash::Hash;
use std::hash::Hasher;

use allocative::Allocative;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::bzl::ImportPath;
use buck2_core::cells::build_file_cell::BuildFileCell;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::cell_path::CellPathRef;
use buck2_core::cells::name::CellName;
use buck2_core::cells::paths::CellRelativePathBuf;
use buck2_core::fs::paths::file_name::FileName;
use derive_more::Display;
use dupe::Dupe;
use gazebo::variants::UnpackVariants;
use starlark::collections::Equivalent;
use thiserror::Error;

use crate::file_type::StarlarkFileType;

/// Path of a `bxl` file for `bxl` commands
#[derive(
    Clone,
    Hash,
    Eq,
    PartialEq,
    Debug,
    derive_more::Display,
    Ord,
    PartialOrd,
    Allocative
)]
#[display(fmt = "{}", path)]
pub struct BxlFilePath {
    /// The path of this bxl file, including the `bxl` extension
    path: CellPath,
}

#[derive(Debug, Error)]
#[error("Expected a cell path to a `.bxl` file, but got `{0}`")]
struct BxlPathError(CellPath);

impl BxlFilePath {
    pub fn new(path: CellPath) -> anyhow::Result<Self> {
        let err = || BxlPathError(path.clone());

        if path.path().file_name().ok_or_else(err)?.extension() != Some("bxl") {
            return Err(anyhow::anyhow!(err()));
        }

        Ok(Self::unverified_new(path))
    }

    fn unverified_new(path: CellPath) -> Self {
        BxlFilePath { path }
    }

    pub fn testing_new(cell: &str, cell_relative_path: &str) -> Self {
        let path = CellPath::new(
            CellName::testing_new(cell),
            CellRelativePathBuf::try_from(cell_relative_path.to_owned()).unwrap(),
        );
        Self::unverified_new(path)
    }

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

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

    pub fn build_file_cell(&self) -> BuildFileCell {
        BuildFileCell::new(self.cell())
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Hash, Allocative, derive_more::Display)]
#[display(fmt = "{}", path)]
pub struct PackageFilePath {
    /// Including `/PACKAGE`.
    path: CellPath,
}

impl PackageFilePath {
    pub const PACKAGE_FILE_NAME: &'static FileName = FileName::unchecked_new("PACKAGE");

    /// Create for directory containing `PACKAGE` file.
    pub fn for_dir(path: CellPathRef) -> PackageFilePath {
        PackageFilePath {
            path: path.join(Self::PACKAGE_FILE_NAME),
        }
    }

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

    pub fn build_file_cell(&self) -> BuildFileCell {
        BuildFileCell::new(self.cell())
    }

    /// Directory containing this `PACKAGE` file.
    pub fn dir(&self) -> CellPathRef {
        self.path
            .parent()
            .expect("constructor verifies that path is not root")
    }

    pub fn parent_package_file(&self) -> Option<PackageFilePath> {
        self.dir().parent().map(PackageFilePath::for_dir)
    }

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

/// Path to file containing starlark that can be evaluated by the interpreter.
#[derive(Display, Clone, Copy, Dupe, Debug, UnpackVariants)]
#[display(fmt = "{}", self.id())]
pub enum StarlarkPath<'a> {
    /// a build file
    BuildFile(&'a BuildFilePath),
    /// a `PACKAGE` file
    PackageFile(&'a PackageFilePath),
    /// a file to be imported
    LoadFile(&'a ImportPath),
    /// a bxl file to be evaluated
    BxlFile(&'a BxlFilePath),
}

impl<'a> StarlarkPath<'a> {
    pub fn cell(&self) -> CellName {
        match self {
            StarlarkPath::BuildFile(b) => b.cell(),
            StarlarkPath::PackageFile(p) => p.cell(),
            StarlarkPath::LoadFile(l) => l.cell(),
            StarlarkPath::BxlFile(b) => b.cell(),
        }
    }

    pub fn build_file_cell(&self) -> BuildFileCell {
        match self {
            StarlarkPath::BuildFile(b) => b.build_file_cell(),
            StarlarkPath::PackageFile(p) => p.build_file_cell(),
            StarlarkPath::LoadFile(l) => l.build_file_cell(),
            StarlarkPath::BxlFile(b) => b.build_file_cell(),
        }
    }

    pub fn path(&self) -> Cow<'a, CellPath> {
        match self {
            StarlarkPath::BuildFile(b) => Cow::Owned(b.path()),
            StarlarkPath::PackageFile(p) => Cow::Borrowed(p.path()),
            StarlarkPath::LoadFile(l) => Cow::Borrowed(l.path()),
            StarlarkPath::BxlFile(b) => Cow::Borrowed(b.path()),
        }
    }

    pub fn file_type(&self) -> StarlarkFileType {
        match self {
            StarlarkPath::BuildFile(_) => StarlarkFileType::Buck,
            StarlarkPath::PackageFile(_) => StarlarkFileType::Package,
            StarlarkPath::LoadFile(_) => StarlarkFileType::Bzl,
            StarlarkPath::BxlFile(_) => StarlarkFileType::Bxl,
        }
    }
}

#[derive(Clone, Display, Debug, Eq, Hash, PartialEq, Allocative)]
#[display(fmt = "{}", self.borrow())]
pub enum OwnedStarlarkPath {
    /// a build file
    BuildFile(BuildFilePath),
    /// a package file
    PackageFile(PackageFilePath),
    /// a file to be imported
    LoadFile(ImportPath),
    /// a bxl file to be evaluated
    BxlFile(BxlFilePath),
}

impl OwnedStarlarkPath {
    pub fn new(path: StarlarkPath<'_>) -> Self {
        match path {
            StarlarkPath::BuildFile(p) => Self::BuildFile(p.clone()),
            StarlarkPath::PackageFile(p) => Self::PackageFile(p.clone()),
            StarlarkPath::LoadFile(p) => Self::LoadFile(p.clone()),
            StarlarkPath::BxlFile(p) => Self::BxlFile(p.clone()),
        }
    }

    pub fn borrow(&self) -> StarlarkPath<'_> {
        match self {
            OwnedStarlarkPath::BuildFile(p) => StarlarkPath::BuildFile(p),
            OwnedStarlarkPath::PackageFile(p) => StarlarkPath::PackageFile(p),
            OwnedStarlarkPath::LoadFile(p) => StarlarkPath::LoadFile(p),
            OwnedStarlarkPath::BxlFile(p) => StarlarkPath::BxlFile(p),
        }
    }
}

impl<'a> From<StarlarkModulePath<'a>> for StarlarkPath<'a> {
    fn from(s: StarlarkModulePath<'a>) -> Self {
        match s {
            StarlarkModulePath::LoadFile(p) => StarlarkPath::LoadFile(p),
            StarlarkModulePath::BxlFile(p) => StarlarkPath::BxlFile(p),
        }
    }
}

/// Path to file containing starlark that can be evaluated as a loaded module by the interpreter.
#[derive(Display, Clone, Copy, Dupe, Debug, UnpackVariants, Hash, Eq, PartialEq)]
#[display(fmt = "{}", self.id())]
pub enum StarlarkModulePath<'a> {
    /// a file to be imported
    LoadFile(&'a ImportPath),
    /// a bxl file to be evaluated
    BxlFile(&'a BxlFilePath),
}

impl Equivalent<OwnedStarlarkModulePath> for StarlarkModulePath<'_> {
    fn equivalent(&self, key: &OwnedStarlarkModulePath) -> bool {
        *self == key.borrow()
    }
}

impl<'a> StarlarkModulePath<'a> {
    pub fn cell(&self) -> CellName {
        match self {
            StarlarkModulePath::LoadFile(l) => l.cell(),
            StarlarkModulePath::BxlFile(b) => b.cell(),
        }
    }

    pub fn build_file_cell(&self) -> BuildFileCell {
        match self {
            StarlarkModulePath::LoadFile(l) => l.build_file_cell(),
            StarlarkModulePath::BxlFile(b) => b.build_file_cell(),
        }
    }

    pub fn path(&self) -> &CellPath {
        match self {
            StarlarkModulePath::LoadFile(l) => l.path(),
            StarlarkModulePath::BxlFile(b) => b.path(),
        }
    }

    pub fn starlark_path(&self) -> StarlarkPath {
        match self {
            StarlarkModulePath::LoadFile(l) => StarlarkPath::LoadFile(l),
            StarlarkModulePath::BxlFile(b) => StarlarkPath::BxlFile(b),
        }
    }

    pub fn to_owned(&self) -> OwnedStarlarkModulePath {
        OwnedStarlarkModulePath::new(*self)
    }
}

#[derive(Clone, Display, Debug, Eq, PartialEq, Allocative)]
#[display(fmt = "{}", self.borrow())]
pub enum OwnedStarlarkModulePath {
    LoadFile(ImportPath),
    BxlFile(BxlFilePath),
}

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

impl OwnedStarlarkModulePath {
    pub fn new(path: StarlarkModulePath<'_>) -> Self {
        match path {
            StarlarkModulePath::LoadFile(p) => Self::LoadFile(p.clone()),
            StarlarkModulePath::BxlFile(p) => Self::BxlFile(p.clone()),
        }
    }

    pub fn borrow(&self) -> StarlarkModulePath<'_> {
        match self {
            OwnedStarlarkModulePath::LoadFile(p) => StarlarkModulePath::LoadFile(p),
            OwnedStarlarkModulePath::BxlFile(p) => StarlarkModulePath::BxlFile(p),
        }
    }

    pub fn path(&self) -> CellPathRef<'_> {
        match self {
            OwnedStarlarkModulePath::LoadFile(p) => p.path().as_ref(),
            OwnedStarlarkModulePath::BxlFile(p) => p.path().as_ref(),
        }
    }

    pub fn into_starlark_path(self) -> OwnedStarlarkPath {
        match self {
            OwnedStarlarkModulePath::LoadFile(l) => OwnedStarlarkPath::LoadFile(l),
            OwnedStarlarkModulePath::BxlFile(b) => OwnedStarlarkPath::BxlFile(b),
        }
    }
}


/*
 * 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_core::bzl::ImportPath;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::paths::CellRelativePathBuf;
use buck2_core::cells::CellResolver;

#[derive(Debug, derive_more::Display, Clone, Eq, PartialEq, Allocative)]
pub struct PreludePath(ImportPath);

impl PreludePath {
    #[inline]
    pub fn import_path(&self) -> &ImportPath {
        &self.0
    }

    pub fn is_prelude_path(&self, import_path: &CellPath) -> bool {
        import_path.starts_with(self.0.path_parent())
    }

    pub fn testing_new(import_path: ImportPath) -> PreludePath {
        PreludePath(import_path)
    }
}

pub fn prelude_path(cell_resolver: &CellResolver) -> anyhow::Result<PreludePath> {
    let alias_resolver = cell_resolver.root_cell_cell_alias_resolver();
    let prelude_cell = alias_resolver.resolve("prelude")?;
    let prelude_file = CellRelativePathBuf::unchecked_new("prelude.bzl".to_owned());
    ImportPath::new_same_cell(CellPath::new(prelude_cell, prelude_file)).map(PreludePath)
}


/*
 * 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 buck2_events::dispatch::EventDispatcher;
use starlark::PrintHandler;

/// Print handler uses the `EventDispatcher` to emit messages from server to client.
pub struct EventDispatcherPrintHandler(pub EventDispatcher);

impl PrintHandler for EventDispatcherPrintHandler {
    fn println(&self, text: &str) -> anyhow::Result<()> {
        self.0.console_message(text.to_owned());
        Ok(())
    }
}


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

use async_trait::async_trait;
use starlark::eval::Evaluator;

/// A StarlarkDebuggerHandle is a reference to the global debugger server. It's used to get
/// a StarlarkDebugController when about to perform starlark evaluation.
#[async_trait]
pub trait StarlarkDebuggerHandle: Send + Sync + 'static {
    /// Indicates that we are about to start a starlark evaluation. This can be called multiple times on a handle.
    async fn start_eval(
        &self,
        description: &str,
    ) -> anyhow::Result<Box<dyn StarlarkDebugController>>;
}

/// The StarlarkDebugController is used to setup a starlark Evaluator for debugging.
pub trait StarlarkDebugController {
    /// Configures the Evaluator for debugging. Can only be used once.
    fn initialize(&mut self, eval: &mut Evaluator) -> anyhow::Result<()>;
}


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

use std::cmp;
use std::time::Duration;
use std::time::Instant;

use allocative::Allocative;
use anyhow::Context;
use dupe::Dupe;
use starlark::environment::FrozenModule;
use starlark::eval::Evaluator;
use starlark::eval::ProfileData;
use starlark::eval::ProfileMode;

#[derive(Debug, thiserror::Error)]
enum StarlarkProfilerError {
    #[error("will_freeze field was initialized incorrectly (internal error)")]
    IncorrectWillFreeze,
    #[error(
        "Retained memory profiling is available only for analysis profile \
        or bxl profile (which freezes the module)"
    )]
    RetainedMemoryNotFrozen,
    #[error("profile mode are inconsistent (internal error)")]
    InconsistentProfileMode,
}

/// When profiling Starlark file, all dependencies of that file must be
/// "instrumented" otherwise the profiler won't work.
///
/// This struct defines instrumentation level for the module.
#[derive(Debug, PartialEq, Eq, Clone, Dupe, Allocative)]
pub struct StarlarkProfilerInstrumentation {}

impl StarlarkProfilerInstrumentation {
    pub fn new() -> Self {
        Self {}
    }
}

#[derive(Debug, Allocative)]
pub struct StarlarkProfileDataAndStats {
    profile_mode: ProfileMode,
    #[allocative(skip)] // OK to skip because used only when profiling enabled.
    pub profile_data: ProfileData,
    initialized_at: Instant,
    finalized_at: Instant,
    total_retained_bytes: usize,
}

impl StarlarkProfileDataAndStats {
    pub fn elapsed(&self) -> Duration {
        self.finalized_at.duration_since(self.initialized_at)
    }

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

    pub fn merge<'a>(
        datas: impl IntoIterator<Item = &'a StarlarkProfileDataAndStats> + Clone,
    ) -> anyhow::Result<StarlarkProfileDataAndStats> {
        let mut iter = datas.clone().into_iter();
        let first = iter.next().context("empty collection of profile data")?;
        let profile_mode = first.profile_mode.dupe();
        let mut total_retained_bytes = first.total_retained_bytes;
        let mut initialized_at = first.initialized_at;
        let mut finalized_at = first.finalized_at;

        for data in iter {
            if data.profile_mode != profile_mode {
                return Err(StarlarkProfilerError::InconsistentProfileMode.into());
            }
            initialized_at = cmp::min(initialized_at, data.initialized_at);
            finalized_at = cmp::max(finalized_at, data.finalized_at);
            total_retained_bytes += data.total_retained_bytes;
        }

        let profile_data = ProfileData::merge(datas.into_iter().map(|data| &data.profile_data))?;

        Ok(StarlarkProfileDataAndStats {
            profile_mode,
            profile_data,
            initialized_at,
            finalized_at,
            total_retained_bytes,
        })
    }
}

pub struct StarlarkProfiler {
    profile_mode: ProfileMode,
    /// Evaluation will freeze the module.
    /// (And frozen module will be passed to `visit_frozen_module`).
    will_freeze: bool,

    initialized_at: Option<Instant>,
    finalized_at: Option<Instant>,
    profile_data: Option<ProfileData>,
    total_retained_bytes: Option<usize>,
}

impl StarlarkProfiler {
    pub fn new(profile_mode: ProfileMode, will_freeze: bool) -> StarlarkProfiler {
        Self {
            profile_mode,
            will_freeze,
            initialized_at: None,
            finalized_at: None,
            profile_data: None,
            total_retained_bytes: None,
        }
    }

    /// Collect all profiling data.
    pub fn finish(self) -> anyhow::Result<StarlarkProfileDataAndStats> {
        Ok(StarlarkProfileDataAndStats {
            profile_mode: self.profile_mode,
            initialized_at: self
                .initialized_at
                .context("did not initialize (internal error)")?,
            finalized_at: self
                .finalized_at
                .context("did not finalize (internal error)")?,
            total_retained_bytes: self
                .total_retained_bytes
                .context("did not visit heap (internal error)")?,
            profile_data: self
                .profile_data
                .context("profile_data not initialized (internal error)")?,
        })
    }

    /// Instrumentation level required by `bzl` files loaded by the profiled module.
    fn instrumentation(&self) -> Option<StarlarkProfilerInstrumentation> {
        Some(StarlarkProfilerInstrumentation {})
    }

    /// Prepare an Evaluator to capture output relevant to this profiler.
    fn initialize(&mut self, eval: &mut Evaluator) -> anyhow::Result<()> {
        eval.enable_profile(&self.profile_mode)?;
        self.initialized_at = Some(Instant::now());
        Ok(())
    }

    /// Post-analysis, produce the output of this profiler.
    fn evaluation_complete(&mut self, eval: &mut Evaluator) -> anyhow::Result<()> {
        self.finalized_at = Some(Instant::now());
        if !matches!(
            self.profile_mode,
            ProfileMode::HeapSummaryRetained | ProfileMode::HeapFlameRetained
        ) {
            self.profile_data = Some(eval.gen_profile()?);
        }
        Ok(())
    }

    fn visit_frozen_module(&mut self, module: Option<&FrozenModule>) -> anyhow::Result<()> {
        if self.will_freeze != module.is_some() {
            return Err(StarlarkProfilerError::IncorrectWillFreeze.into());
        }

        match self.profile_mode {
            ProfileMode::HeapSummaryRetained | ProfileMode::HeapFlameRetained => {
                let module = module.ok_or(StarlarkProfilerError::RetainedMemoryNotFrozen)?;
                let profile = module.heap_profile()?;
                self.profile_data = Some(profile);
            }
            _ => {}
        }

        let total_retained_bytes = module.map_or(0, |module| {
            module
                .frozen_heap()
                .allocated_summary()
                .total_allocated_bytes()
        });

        self.total_retained_bytes = Some(total_retained_bytes);

        Ok(())
    }
}

/// How individual starlark invocation (`bzl`, `BUCK` or analysis) should be interpreted.
#[derive(Clone, Dupe, Eq, PartialEq, Allocative)]
pub enum StarlarkProfileModeOrInstrumentation {
    None,
    Profile(ProfileMode),
}

impl StarlarkProfileModeOrInstrumentation {
    pub fn profile_mode(&self) -> Option<&ProfileMode> {
        match self {
            StarlarkProfileModeOrInstrumentation::Profile(profile) => Some(profile),
            StarlarkProfileModeOrInstrumentation::None => None,
        }
    }
}

enum StarlarkProfilerOrInstrumentationImpl<'p> {
    None,
    Profiler(&'p mut StarlarkProfiler),
}

/// Modules can be evaluated with profiling or with instrumentation for profiling.
/// This type enapsulates this logic.
pub struct StarlarkProfilerOrInstrumentation<'p>(StarlarkProfilerOrInstrumentationImpl<'p>);

impl<'p> StarlarkProfilerOrInstrumentation<'p> {
    pub fn new(
        profiler: &'p mut StarlarkProfiler,
        instrumentation: Option<StarlarkProfilerInstrumentation>,
    ) -> StarlarkProfilerOrInstrumentation<'p> {
        match (profiler.instrumentation(), instrumentation) {
            (None, None) => StarlarkProfilerOrInstrumentation::disabled(),
            (Some(_), Some(_)) => StarlarkProfilerOrInstrumentation::for_profiler(profiler),
            (None, Some(_)) => StarlarkProfilerOrInstrumentation::disabled(),
            (Some(_), None) => panic!("profiler, but no instrumentation"),
        }
    }

    pub fn for_profiler(profiler: &'p mut StarlarkProfiler) -> Self {
        StarlarkProfilerOrInstrumentation(StarlarkProfilerOrInstrumentationImpl::Profiler(profiler))
    }

    /// No profiling.
    pub fn disabled() -> StarlarkProfilerOrInstrumentation<'p> {
        StarlarkProfilerOrInstrumentation(StarlarkProfilerOrInstrumentationImpl::None)
    }

    pub fn initialize(&mut self, eval: &mut Evaluator) -> anyhow::Result<()> {
        match &mut self.0 {
            StarlarkProfilerOrInstrumentationImpl::None => Ok(()),
            StarlarkProfilerOrInstrumentationImpl::Profiler(profiler) => profiler.initialize(eval),
        }
    }

    pub fn visit_frozen_module(&mut self, module: Option<&FrozenModule>) -> anyhow::Result<()> {
        if let StarlarkProfilerOrInstrumentationImpl::Profiler(profiler) = &mut self.0 {
            profiler.visit_frozen_module(module)
        } else {
            Ok(())
        }
    }

    pub fn evaluation_complete(&mut self, eval: &mut Evaluator) -> anyhow::Result<()> {
        if let StarlarkProfilerOrInstrumentationImpl::Profiler(profiler) = &mut self.0 {
            profiler.evaluation_complete(eval)
        } else {
            Ok(())
        }
    }
}


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

//! A type [`StarlarkPromise`] which provides basic promise-like functionality.
use std::cell::Cell;
use std::cell::RefCell;
use std::mem;

use allocative::Allocative;
use derivative::Derivative;
use derive_more::Display;
use starlark::any::ProvidesStaticType;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::eval::Evaluator;
use starlark::typing::Ty;
use starlark::values::starlark_value;
use starlark::values::tuple::AllocTuple;
use starlark::values::type_repr::StarlarkTypeRepr;
use starlark::values::AllocValue;
use starlark::values::Heap;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::UnpackValue;
use starlark::values::Value;
use starlark::values::ValueLike;
use starlark::values::ValueTyped;
use thiserror::Error;

/// A type that corresponds to a Rust promise.
#[derive(
    ProvidesStaticType,
    NoSerialize,
    StarlarkDocs,
    Display,
    Derivative,
    Trace,
    Allocative
)]
#[derivative(Debug)]
#[display(fmt = "promise()")]
pub struct StarlarkPromise<'v> {
    /// The value of the promise.
    value: RefCell<PromiseValue<'v>>,
    /// Everyone who is downstream of this promise and needs executing when it changes.
    /// These will all be `.map` style nodes.
    /// Note that as a consequence of this back pointer, we can't garbage collect promises
    /// that are generated from this promise, even if they aren't used.
    #[derivative(Debug = "ignore")]
    downstream: RefCell<Vec<ValueTyped<'v, StarlarkPromise<'v>>>>,
    /// Things we want to validate whenever the value of this promise is resolved.
    #[derivative(Debug = "ignore")]
    validate: RefCell<Vec<Validate<'v>>>,
}

#[derive(Allocative, Trace)]
struct Validate<'v>(#[trace(unsafe_ignore)] fn(Value<'v>) -> anyhow::Result<()>);

#[derive(Clone, Debug, Trace, Allocative)]
enum PromiseValue<'v> {
    Unresolved,
    Resolved(Value<'v>),
    Map(ValueTyped<'v, StarlarkPromise<'v>>, Value<'v>),
    Join(PromiseJoin<'v>),
}

#[derive(Clone, Debug, Trace, Allocative)]
struct PromiseJoin<'v> {
    /// Number of items that are currently unresolved
    #[allocative(skip)]
    unresolved: Cell<usize>,
    items: Vec<ValueTyped<'v, StarlarkPromise<'v>>>,
}

impl<'v> PromiseJoin<'v> {
    fn new(items: Vec<ValueTyped<'v, StarlarkPromise<'v>>>) -> Self {
        let unresolved = Cell::new(items.iter().filter(|x| x.get().is_none()).count());
        Self { unresolved, items }
    }

    fn resolve_one(&self) {
        self.unresolved
            .set(self.unresolved.get().checked_sub(1).unwrap());
    }

    fn get(&self) -> Option<Vec<Value<'v>>> {
        if self.unresolved.get() != 0 {
            None
        } else {
            let mut res = Vec::with_capacity(self.items.len());
            for x in &self.items {
                res.push(x.get().expect("invariant broken in promise join"))
            }
            Some(res)
        }
    }
}

#[derive(Debug, Error)]
enum PromiseError {
    #[error("Can't .resolve on a promise produced with .map")]
    CantResolveMap,
    #[error("Can't .resolve on a promise which already has a value")]
    CantResolveTwice,
}

impl<'v> StarlarkPromise<'v> {
    /// The result of calling `type()` on promise.
    pub const TYPE: &'static str = "promise";

    /// Create a new unresolved promise.
    /// Must have [`StarlarkPromise::resolve`] called on it later.
    pub fn new_unresolved() -> Self {
        Self {
            value: RefCell::new(PromiseValue::Unresolved),
            downstream: RefCell::new(Vec::new()),
            validate: RefCell::new(Vec::new()),
        }
    }

    /// Create a new resolved promise.
    pub fn new_resolved(value: Value<'v>) -> Self {
        Self {
            value: RefCell::new(PromiseValue::Resolved(value)),
            ..Self::new_unresolved()
        }
    }

    /// Get the value from a resolved promise, or [`None`] otherwise.
    pub fn get(&self) -> Option<Value<'v>> {
        match &*self.value.borrow() {
            PromiseValue::Resolved(x) => Some(*x),
            _ => None,
        }
    }

    /// A recursive version of [`StarlarkPromise::get`], which continues to see through
    /// promises while they are resolved.
    /// The returned value will either be an unresolved promise, or not a promise.
    pub fn get_recursive(mut value: Value<'v>) -> Value<'v> {
        while let Some(promise) = StarlarkPromise::from_value(value) {
            if let Some(x) = promise.get() {
                value = x;
            } else {
                // We have an unresolved promise, stop looping
                break;
            }
        }
        value
    }

    fn apply(
        f: Value<'v>,
        x: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        eval.eval_function(f, &[x], &[])
    }

    fn map(
        x: ValueTyped<'v, StarlarkPromise<'v>>,
        f: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<ValueTyped<'v, StarlarkPromise<'v>>> {
        match x.get() {
            Some(x) => Ok(eval
                .heap()
                .alloc_typed(Self::new_resolved(Self::apply(f, x, eval)?))),
            _ => {
                let res = eval.heap().alloc_typed(Self {
                    value: RefCell::new(PromiseValue::Map(x, f)),
                    ..Self::new_unresolved()
                });
                x.downstream.borrow_mut().push(res);
                Ok(res)
            }
        }
    }

    fn join(
        args: Vec<ValueTyped<'v, StarlarkPromise<'v>>>,
        heap: &'v Heap,
    ) -> ValueTyped<'v, StarlarkPromise<'v>> {
        let join = PromiseJoin::new(args);
        match join.get() {
            Some(values) => heap.alloc_typed(Self::new_resolved(heap.alloc(AllocTuple(values)))),
            None => {
                let promise = Self::new_unresolved();
                let value = heap.alloc_typed(promise);
                for arg in &join.items {
                    arg.downstream.borrow_mut().push(value);
                }
                *value.value.borrow_mut() = PromiseValue::Join(join);
                value
            }
        }
    }

    /// Validate the type of a promise. Will execute once the promise is resolved.
    pub fn validate(&self, f: fn(Value<'v>) -> anyhow::Result<()>) -> anyhow::Result<()> {
        match self.get() {
            Some(x) => f(x),
            _ => {
                self.validate.borrow_mut().push(Validate(f));
                Ok(())
            }
        }
    }

    /// Resolve a promise. Errors if the promise was produced by `.map` or the promise has
    /// already been resolved.
    pub fn resolve(&self, x: Value<'v>, eval: &mut Evaluator<'v, '_>) -> anyhow::Result<()> {
        if matches!(&*self.value.borrow(), PromiseValue::Map(..)) {
            return Err(PromiseError::CantResolveMap.into());
        }
        self.resolve_rec(x, eval)
    }

    fn resolve_rec(&self, x: Value<'v>, eval: &mut Evaluator<'v, '_>) -> anyhow::Result<()> {
        if matches!(&*self.value.borrow(), PromiseValue::Resolved(_)) {
            return Err(PromiseError::CantResolveTwice.into());
        }
        for f in self.validate.borrow().iter() {
            f.0(x)?;
        }
        *self.value.borrow_mut() = PromiseValue::Resolved(x);
        // We clear validate/downstream because we don't need them anymore, so might as well save the memory
        self.validate.borrow_mut().clear();
        let mut downstream = Vec::new();
        mem::swap(&mut *self.downstream.borrow_mut(), &mut downstream);
        for d in downstream {
            // Make sure we drop the borrow BEFORE calling resolve_rec
            let borrow = d.value.borrow();
            match &*borrow {
                PromiseValue::Map(_, f) => {
                    let f = *f;
                    drop(borrow);
                    d.resolve_rec(Self::apply(f, x, eval)?, eval)?;
                }
                PromiseValue::Join(join) => {
                    join.resolve_one();
                    if let Some(elems) = join.get() {
                        drop(borrow);
                        d.resolve_rec(eval.heap().alloc(AllocTuple(elems)), eval)?;
                    }
                }
                _ => panic!("Impossible to reach a downstream promise that is not a map or join"),
            }
        }
        Ok(())
    }

    /// Downcast the value.
    pub fn from_value(x: Value<'v>) -> Option<&'v Self> {
        x.downcast_ref()
    }
}

// We can't use starlark_complex_value! because there is no frozen form of a promise

impl<'v> AllocValue<'v> for StarlarkPromise<'v> {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        // FIXME: need to be able to freeze things that are resolved
        heap.alloc_complex_no_freeze(self)
    }
}

impl<'v> StarlarkTypeRepr for &'v StarlarkPromise<'v> {
    fn starlark_type_repr() -> Ty {
        StarlarkPromise::get_type_starlark_repr()
    }
}

impl<'v> UnpackValue<'v> for &'v StarlarkPromise<'v> {
    fn unpack_value(x: Value<'v>) -> Option<Self> {
        StarlarkPromise::from_value(x)
    }
}

#[starlark_value(type = StarlarkPromise::TYPE)]
impl<'v> StarlarkValue<'v> for StarlarkPromise<'v> {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(promise_methods)
    }
}

#[starlark_module]
fn promise_methods(builder: &mut MethodsBuilder) {
    /// Given a promise, apply a function to the value it contains, producing a promise with the resulting value.
    fn map<'v>(
        this: ValueTyped<'v, StarlarkPromise<'v>>,
        #[starlark(require = pos)] func: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<ValueTyped<'v, StarlarkPromise<'v>>> {
        StarlarkPromise::map(this, func, eval)
    }

    /// Join a set of promises together into a single promise.
    ///
    /// Given a series of promises, `p4 = p1.join(p2, p3)` will produce a promise
    /// where the value is promise that resolves to a tuple containing the three values,
    /// those from `p1`, `p2` and `p3` respectively.
    fn join<'v>(
        this: ValueTyped<'v, StarlarkPromise<'v>>,
        #[starlark(args)] mut args: Vec<ValueTyped<'v, StarlarkPromise<'v>>>,
        heap: &'v Heap,
    ) -> anyhow::Result<ValueTyped<'v, StarlarkPromise<'v>>> {
        args.insert(0, this);
        Ok(StarlarkPromise::join(args, heap))
    }
}

#[cfg(test)]
mod tests {
    use std::cell::RefCell;

    use starlark::any::ProvidesStaticType;
    use starlark::environment::GlobalsBuilder;
    use starlark::environment::Module;
    use starlark::syntax::AstModule;
    use starlark::syntax::Dialect;
    use starlark::values::none::NoneType;
    use starlark::values::tuple::TupleRef;
    use starlark::values::Value;

    use super::*;

    #[derive(
        ProvidesStaticType,
        NoSerialize,
        Trace,
        Default,
        Debug,
        Display,
        Allocative
    )]
    #[display(fmt = "{:?}", self)]
    struct Promises<'v>(RefCell<Vec<(String, ValueTyped<'v, StarlarkPromise<'v>>)>>);

    #[starlark_value(type = "promises")]
    impl<'v> StarlarkValue<'v> for Promises<'v> {}

    #[starlark_module]
    fn helpers(builder: &mut GlobalsBuilder) {
        fn promise_unresolved<'v>(
            name: String,
            eval: &mut Evaluator<'v, '_>,
        ) -> anyhow::Result<ValueTyped<'v, StarlarkPromise<'v>>> {
            let promises = get_promises(eval.module());
            let promise = eval.heap().alloc_typed(StarlarkPromise::new_unresolved());
            promises.0.borrow_mut().push((name, promise));
            Ok(promise)
        }

        fn promise_resolved<'v>(value: Value<'v>) -> anyhow::Result<StarlarkPromise<'v>> {
            Ok(StarlarkPromise::new_resolved(value))
        }

        fn promise_validate<'v>(
            promise: ValueTyped<'v, StarlarkPromise<'v>>,
        ) -> anyhow::Result<NoneType> {
            promise.validate(|x| {
                if x.unpack_str() == Some("ok") {
                    Ok(())
                } else {
                    Err(anyhow::anyhow!("VALIDATE_FAILED"))
                }
            })?;
            Ok(NoneType)
        }
    }

    fn alloc_promises<'v>(modu: &'v Module) {
        modu.set(
            "__promises__",
            modu.heap().alloc_complex_no_freeze(Promises::default()),
        )
    }

    fn get_promises<'v>(modu: &'v Module) -> &'v Promises<'v> {
        modu.get("__promises__").unwrap().downcast_ref().unwrap()
    }

    fn assert_promise<'v>(modu: &'v Module, content: &str) -> anyhow::Result<Value<'v>> {
        alloc_promises(modu);
        let globals = GlobalsBuilder::extended().with(helpers).build();
        let ast = AstModule::parse("test.bzl", content.to_owned(), &Dialect::Extended)?;
        let mut eval = Evaluator::new(modu);
        let res = eval.eval_module(ast, &globals)?;
        let promises = get_promises(modu);
        for (key, promise) in promises.0.borrow().iter() {
            promise.resolve(modu.heap().alloc(key), &mut eval)?;
        }
        Ok(res)
    }

    fn assert_promise_err<'v>(modu: &'v Module, content: &str, err: &str) -> anyhow::Error {
        match assert_promise(modu, content) {
            Ok(_) => panic!("Expected an error, got a result"),
            Err(e) => {
                if format!("{:#}", e).contains(err) {
                    e
                } else {
                    panic!(
                        "Wrong error message, expected to see `{}`, got `{:?}`",
                        err, e
                    )
                }
            }
        }
    }

    #[test]
    fn test_promise() {
        let modu = Module::new();
        let res = assert_promise(
            &modu,
            r#"
a = promise_unresolved("test")
b = a.map(lambda x: x.upper())
c = b.map(lambda x: x + "!")
d = a.map(lambda x: x.title())
e = promise_resolved("more")
f = e.map(lambda x: x.upper())
(a,b,c,d,e,f)
"#,
        )
        .unwrap();
        let wants = &["test", "TEST", "TEST!", "Test", "more", "MORE"];
        for (want, got) in wants
            .iter()
            .zip(TupleRef::from_value(res).unwrap().content())
        {
            assert_eq!(
                StarlarkPromise::from_value(*got)
                    .unwrap()
                    .get()
                    .unwrap()
                    .unpack_str()
                    .unwrap(),
                *want
            );
        }
    }

    #[test]
    fn test_promise_validate() {
        let modu = Module::new();
        assert_promise(
            &modu,
            r#"
p = promise_unresolved("ok")
promise_validate(p)
p
"#,
        )
        .unwrap();
        assert_promise_err(
            &modu,
            r#"
p = promise_unresolved("test")
promise_validate(p)
p
"#,
            "VALIDATE_FAILED",
        );
    }

    #[test]
    fn test_promise_join() {
        let modu = Module::new();
        let res = assert_promise(
            &modu,
            r#"
p1 = promise_resolved("a")
p2 = promise_resolved("b")
p3 = promise_resolved("c")
p1.join(p2, p3)
"#,
        )
        .unwrap();
        assert_eq!(
            StarlarkPromise::from_value(res)
                .unwrap()
                .get()
                .unwrap()
                .to_string(),
            "(\"a\", \"b\", \"c\")"
        );
        let res = assert_promise(
            &modu,
            r#"
p1 = promise_resolved("a")
p2 = promise_unresolved("B")
p3 = promise_unresolved("C")
p1.join(p2, p3)
"#,
        )
        .unwrap();
        assert_eq!(
            StarlarkPromise::from_value(res)
                .unwrap()
                .get()
                .unwrap()
                .to_string(),
            "(\"a\", \"B\", \"C\")"
        );
    }
}


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

//! The dice module contains the interpreter's integration with dice. This
//! module contains the extension traits that we implement for `Arc<DiceCtx>`
//! (the implementations of the traits are in the submodules).
//!
//! Several of these extension traits provide implementations of our delegate/DI
//! traits that are themselves build on dice (ex DiceInterpreterFileOps
//! implements InterpreterFileOps by basically putting DefaultInterpreterFileOps
//! onto the dice graph).

pub mod starlark_debug;
pub mod starlark_profiler;
pub mod starlark_provider;
pub mod starlark_types;


/*
 * 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 dice::DiceComputations;
use dice::UserComputationData;

use crate::starlark_debug::StarlarkDebuggerHandle;

pub trait HasStarlarkDebugger {
    fn get_starlark_debugger_handle(&self) -> Option<&dyn StarlarkDebuggerHandle>;
}

pub trait SetStarlarkDebugger {
    fn set_starlark_debugger_handle(&mut self, hook: Option<Box<dyn StarlarkDebuggerHandle>>);
}

impl HasStarlarkDebugger for DiceComputations {
    fn get_starlark_debugger_handle(&self) -> Option<&dyn StarlarkDebuggerHandle> {
        self.per_transaction_data()
            .data
            .get::<StarlarkDebuggerHookHolder>()
            .expect("Starlark debugger hook should be set")
            .hook
            .as_deref()
    }
}

impl SetStarlarkDebugger for UserComputationData {
    fn set_starlark_debugger_handle(&mut self, hook: Option<Box<dyn StarlarkDebuggerHandle>>) {
        self.data.set(StarlarkDebuggerHookHolder { hook })
    }
}

struct StarlarkDebuggerHookHolder {
    hook: Option<Box<dyn StarlarkDebuggerHandle>>,
}


/*
 * 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 async_trait::async_trait;
use buck2_common::result::SharedResult;
use dice::DiceComputations;
use dice::DiceTransactionUpdater;
use dice::InjectedKey;
use dice::Key;
use dupe::Dupe;
use more_futures::cancellation::CancellationContext;
use starlark::eval::ProfileMode;

use crate::starlark_profiler::StarlarkProfileModeOrInstrumentation;

#[derive(Debug, thiserror::Error)]
enum StarlarkProfilerError {
    #[error("profiler is not configured to profile last element (internal error)")]
    ProfilerConfigurationNotLast,
}

/// Global profiling configuration.
#[derive(PartialEq, Eq, Clone, Dupe, Debug, Allocative)]
#[derive(Default)]
pub enum StarlarkProfilerConfiguration {
    /// No profiling.
    #[default]
    None,
    /// Profile loading of one `BUCK`, everything else is instrumented.
    ProfileLastLoading(ProfileMode),
    /// Profile analysis of the last target, everything else is instrumented.
    ProfileLastAnalysis(ProfileMode),
    /// Profile analysis targets recursively.
    ProfileAnalysisRecursively(ProfileMode),
    /// Profile BXL
    ProfileBxl(ProfileMode),
}

impl StarlarkProfilerConfiguration {
    pub fn profile_last_bxl(&self) -> anyhow::Result<&ProfileMode> {
        match self {
            StarlarkProfilerConfiguration::None
            | StarlarkProfilerConfiguration::ProfileLastAnalysis(_)
            | StarlarkProfilerConfiguration::ProfileAnalysisRecursively(_)
            | StarlarkProfilerConfiguration::ProfileLastLoading(_) => {
                Err(StarlarkProfilerError::ProfilerConfigurationNotLast.into())
            }
            StarlarkProfilerConfiguration::ProfileBxl(profile_mode) => Ok(profile_mode),
        }
    }

    pub fn profile_last_loading(&self) -> anyhow::Result<&ProfileMode> {
        match self {
            StarlarkProfilerConfiguration::None
            | StarlarkProfilerConfiguration::ProfileLastAnalysis(_)
            | StarlarkProfilerConfiguration::ProfileAnalysisRecursively(_)
            | StarlarkProfilerConfiguration::ProfileBxl(_) => {
                Err(StarlarkProfilerError::ProfilerConfigurationNotLast.into())
            }
            StarlarkProfilerConfiguration::ProfileLastLoading(profile_mode) => Ok(profile_mode),
        }
    }

    pub fn profile_last_analysis(&self) -> anyhow::Result<&ProfileMode> {
        match self {
            StarlarkProfilerConfiguration::None
            | StarlarkProfilerConfiguration::ProfileLastLoading(_)
            | StarlarkProfilerConfiguration::ProfileBxl(_) => {
                Err(StarlarkProfilerError::ProfilerConfigurationNotLast.into())
            }
            StarlarkProfilerConfiguration::ProfileLastAnalysis(profile_mode)
            | StarlarkProfilerConfiguration::ProfileAnalysisRecursively(profile_mode) => {
                Ok(profile_mode)
            }
        }
    }

    /// Profile mode for intermediate target analysis.
    pub fn profile_mode_for_intermediate_analysis(&self) -> StarlarkProfileModeOrInstrumentation {
        match self {
            StarlarkProfilerConfiguration::None
            | StarlarkProfilerConfiguration::ProfileLastLoading(_)
            | StarlarkProfilerConfiguration::ProfileLastAnalysis(_)
            | StarlarkProfilerConfiguration::ProfileBxl(_) => {
                StarlarkProfileModeOrInstrumentation::None
            }
            StarlarkProfilerConfiguration::ProfileAnalysisRecursively(profile_mode) => {
                StarlarkProfileModeOrInstrumentation::Profile(profile_mode.dupe())
            }
        }
    }
}

#[derive(
    Debug,
    derive_more::Display,
    Copy,
    Clone,
    Dupe,
    Eq,
    PartialEq,
    Hash,
    Allocative
)]
#[display(fmt = "{:?}", self)]
struct StarlarkProfilerConfigurationKey;

#[derive(
    Debug,
    derive_more::Display,
    Copy,
    Clone,
    Dupe,
    Eq,
    PartialEq,
    Hash,
    Allocative
)]
#[display(fmt = "{:?}", self)]
pub struct StarlarkProfileModeForIntermediateAnalysisKey;

#[async_trait]
impl Key for StarlarkProfilerConfigurationKey {
    type Value = SharedResult<StarlarkProfilerConfiguration>;

    async fn compute(
        &self,
        ctx: &DiceComputations,
        _cancellations: &CancellationContext,
    ) -> Self::Value {
        let configuration = get_starlark_profiler_instrumentation_override(ctx).await?;
        Ok(configuration)
    }

    fn equality(x: &Self::Value, y: &Self::Value) -> bool {
        match (x, y) {
            (Ok(x), Ok(y)) => x == y,
            _ => false,
        }
    }
}

#[async_trait]
impl Key for StarlarkProfileModeForIntermediateAnalysisKey {
    type Value = SharedResult<StarlarkProfileModeOrInstrumentation>;

    async fn compute(
        &self,
        ctx: &DiceComputations,
        _cancellation: &CancellationContext,
    ) -> SharedResult<StarlarkProfileModeOrInstrumentation> {
        let configuration = get_starlark_profiler_configuration(ctx).await?;
        Ok(configuration.profile_mode_for_intermediate_analysis())
    }

    fn equality(x: &Self::Value, y: &Self::Value) -> bool {
        match (x, y) {
            (Ok(x), Ok(y)) => x == y,
            _ => false,
        }
    }
}

/// Global Starlark compiler instrumentation level.
///
/// We profile only leaf computations (`BUCK` files or analysis),
/// and this key defines instrumentation of all the Starlark files,
/// regardless of whether profiled entity depends on them or not.
/// It's easier to implement with single global key,
/// the downside is we invalidate parse results when we switch
/// between normal operation/profiling.
#[derive(
    Debug,
    derive_more::Display,
    Copy,
    Clone,
    Dupe,
    Eq,
    PartialEq,
    Hash,
    Allocative
)]
#[display(fmt = "{:?}", self)]
pub struct StarlarkProfilerInstrumentationOverrideKey;

impl InjectedKey for StarlarkProfilerInstrumentationOverrideKey {
    type Value = StarlarkProfilerConfiguration;

    fn equality(x: &Self::Value, y: &Self::Value) -> bool {
        x == y
    }
}

#[async_trait]
pub trait SetStarlarkProfilerInstrumentation {
    fn set_starlark_profiler_instrumentation_override(
        &mut self,
        instrumentation: StarlarkProfilerConfiguration,
    ) -> anyhow::Result<()>;
}

#[async_trait]
pub trait GetStarlarkProfilerInstrumentation {
    /// Profile mode for non-final targe analysis.
    async fn get_profile_mode_for_intermediate_analysis(
        &self,
    ) -> anyhow::Result<StarlarkProfileModeOrInstrumentation>;
}

#[async_trait]
impl SetStarlarkProfilerInstrumentation for DiceTransactionUpdater {
    fn set_starlark_profiler_instrumentation_override(
        &mut self,
        instrumentation: StarlarkProfilerConfiguration,
    ) -> anyhow::Result<()> {
        Ok(self.changed_to([(StarlarkProfilerInstrumentationOverrideKey, instrumentation)])?)
    }
}

async fn get_starlark_profiler_instrumentation_override(
    ctx: &DiceComputations,
) -> anyhow::Result<StarlarkProfilerConfiguration> {
    Ok(ctx
        .compute(&StarlarkProfilerInstrumentationOverrideKey)
        .await?)
}

/// Global profiler configuration.
///
/// This function is not exposed outside,
/// because accessing full configuration may invalidate too much.
async fn get_starlark_profiler_configuration(
    ctx: &DiceComputations,
) -> anyhow::Result<StarlarkProfilerConfiguration> {
    Ok(ctx.compute(&StarlarkProfilerConfigurationKey).await??)
}

#[async_trait]
impl GetStarlarkProfilerInstrumentation for DiceComputations {
    async fn get_profile_mode_for_intermediate_analysis(
        &self,
    ) -> anyhow::Result<StarlarkProfileModeOrInstrumentation> {
        Ok(self
            .compute(&StarlarkProfileModeForIntermediateAnalysisKey)
            .await??)
    }
}


/*
 * 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 dice::DiceComputations;
use starlark::environment::FrozenModule;
use starlark::environment::Module;
use starlark::eval::Evaluator;

use crate::dice::starlark_debug::HasStarlarkDebugger;
use crate::factory::StarlarkEvaluatorProvider;
use crate::starlark_debug::StarlarkDebugController;
use crate::starlark_profiler::StarlarkProfilerOrInstrumentation;

/// This constructs an appropriate StarlarkEvaluatorProvider to set up
/// profiling/instrumentation/debugging in a starlark Evaluator for buck.
///
/// Taking this via a closure ensures that the Evaluator isn't used in an
/// async context and allows us to do things like the block_in_place required
/// when debugging.
///
/// The description is used for the thread name when debugging.
///
/// The provided closure will be invoked and passed an appropriate
/// StarlarkEvaluatorProvider.
pub async fn with_starlark_eval_provider<R>(
    ctx: &DiceComputations,
    profiler_instrumentation: &mut StarlarkProfilerOrInstrumentation<'_>,
    description: String,
    closure: impl FnOnce(&mut dyn StarlarkEvaluatorProvider) -> anyhow::Result<R>,
) -> anyhow::Result<R> {
    let debugger_handle = ctx.get_starlark_debugger_handle();
    let debugger = match debugger_handle {
        Some(v) => Some(v.start_eval(&description).await?),
        None => None,
    };

    struct EvalProvider<'a, 'b> {
        profiler: &'a mut StarlarkProfilerOrInstrumentation<'b>,
        debugger: Option<Box<dyn StarlarkDebugController>>,
    }

    impl StarlarkEvaluatorProvider for EvalProvider<'_, '_> {
        fn make<'v, 'a>(&mut self, module: &'v Module) -> anyhow::Result<Evaluator<'v, 'a>> {
            let mut eval = Evaluator::new(module);
            self.profiler.initialize(&mut eval)?;
            if let Some(v) = &mut self.debugger {
                v.initialize(&mut eval)?;
            }
            Ok(eval)
        }

        fn evaluation_complete(&mut self, eval: &mut Evaluator) -> anyhow::Result<()> {
            self.profiler.evaluation_complete(eval)
        }

        fn visit_frozen_module(&mut self, module: Option<&FrozenModule>) -> anyhow::Result<()> {
            self.profiler.visit_frozen_module(module)
        }
    }

    {
        let mut provider = EvalProvider {
            profiler: profiler_instrumentation,
            debugger,
        };

        // If we're debugging, we need to move this to a tokio blocking task.
        //
        // This is required because the debugger itself is running on the
        // tokio worker tasks, and if we have a starlark breakpoint in common
        // code we could get a lot of evaluators all blocked waiting on the debugger
        // and those could block all the tokio worker tasks and the debugger wouldn't
        // even get a chance to resume them.
        //
        // It's the debuggers responsibility to ensure that we don't run too many
        // evaluations concurrently (in the non-debugger case they are limited by the
        // tokio worker tasks, but once in a blocking task that limit is greatly
        // increased).

        // TODO(cjhopman): It would be nicer if we could have this functionality be
        // provided by the debugger handle, but I couldn't figure out a nice clean
        // way to do that. Potentially the thing would be to invert the dependencies
        // so we could operate against a concrete type rather than injecting a trait
        // implementation.
        if debugger_handle.is_some() {
            tokio::task::block_in_place(move || closure(&mut provider))
        } else {
            closure(&mut provider)
        }
    }
}


/*
 * 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 async_trait::async_trait;
use dice::DiceComputations;
use dice::DiceTransactionUpdater;
use dice::InjectedKey;
use dupe::Dupe;

#[derive(
    Debug,
    derive_more::Display,
    Copy,
    Clone,
    Dupe,
    Eq,
    PartialEq,
    Hash,
    Allocative
)]
#[display(fmt = "{:?}", self)]
struct DisableStarlarkTypesKey;

impl InjectedKey for DisableStarlarkTypesKey {
    type Value = bool;

    fn equality(x: &bool, y: &bool) -> bool {
        x == y
    }
}

pub trait SetDisableStarlarkTypes {
    fn set_disable_starlark_types(&mut self, disable_starlark_types: bool) -> anyhow::Result<()>;
}

impl SetDisableStarlarkTypes for DiceTransactionUpdater {
    fn set_disable_starlark_types(&mut self, disable_starlark_types: bool) -> anyhow::Result<()> {
        Ok(self.changed_to([(DisableStarlarkTypesKey, disable_starlark_types)])?)
    }
}

#[async_trait]
pub trait GetDisableStarlarkTypes {
    async fn get_disable_starlark_types(&self) -> anyhow::Result<bool>;
}

#[async_trait]
impl GetDisableStarlarkTypes for DiceComputations {
    async fn get_disable_starlark_types(&self) -> anyhow::Result<bool> {
        Ok(self.compute(&DisableStarlarkTypesKey).await?)
    }
}


/*
 * 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 xcode;

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

#[derive(Copy, Clone, Dupe, Debug, PartialEq, Allocative)]
pub enum InterpreterHostPlatform {
    Linux,
    MacOS,
    Windows,
    FreeBsd,
    Unknown,
}

#[derive(Copy, Clone, Dupe, Debug, PartialEq, Allocative)]
pub enum InterpreterHostArchitecture {
    AArch64,
    X86_64,
    Arm,
    X86,
    Mips,
    Mips64,
    PowerPc,
    PowerPc64,
    Unknown,
}


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

use allocative::Allocative;
use anyhow::Context;
use buck2_core::fs::fs_util;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use regex::Regex;
use serde::Deserialize;
use thiserror::Error;

#[derive(Error, Debug)]
enum XcodeVersionError {
    #[error("XCode select symlink `{}` resolved to path without parent: `{}`", XCODE_SELECT_SYMLINK, _0.display())]
    XcodeSelectSymlinkResolvedToPathWithoutParent(AbsNormPathBuf),
    #[error("Expected short version `{0}` to contain at least major and minor versions")]
    MalformedShortVersion(String),
    #[error("Expected valid format for 'version-build' (e.g., 14.3.0-14C18 or 14.1-14B47b)")]
    MalformedVersionBuildString,
}

const XCODE_SELECT_SYMLINK: &str = "/var/db/xcode_select_link";

/// Only fields we care about from Xcode version.plist.
#[derive(Deserialize)]
#[allow(non_snake_case)]
struct XcodeVersionPlistSchema {
    CFBundleShortVersionString: String,
    ProductBuildVersion: String,
}

/// Versioning information for the currently selected Xcode on the host machine.
#[derive(Debug, Default, PartialEq, Clone, Allocative)]
pub struct XcodeVersionInfo {
    /// e.g. "14.0.1"
    pub version_string: String,
    /// The "14" in "14.0.1"
    pub major_version: String,
    /// The "0" in "14.0.1"
    pub minor_version: String,
    /// The "1" in "14.0.1"
    pub patch_version: String,
    /// Xcode-specific build number like "14A309"
    pub build_number: String,
}

impl XcodeVersionInfo {
    // Construct from version.plist in root of Xcode install dir.
    pub fn new() -> anyhow::Result<Option<Self>> {
        let resolved_xcode_path =
            fs_util::canonicalize_if_exists(PathBuf::from(XCODE_SELECT_SYMLINK))
                .context("resolve selected xcode link")?;
        let resolved_xcode_path = match resolved_xcode_path {
            Some(p) => p,
            None => return Ok(None),
        };
        let plist_parent_path = resolved_xcode_path.parent().ok_or_else(|| {
            XcodeVersionError::XcodeSelectSymlinkResolvedToPathWithoutParent(
                resolved_xcode_path.clone(),
            )
        })?;
        let plist_path = plist_parent_path.as_path().join("version.plist");
        Self::from_plist(&plist_path)
    }

    pub(crate) fn from_plist(plist_path: &Path) -> anyhow::Result<Option<Self>> {
        let plist = plist::from_file::<_, XcodeVersionPlistSchema>(plist_path);

        let plist = match plist {
            Ok(plist) => plist,
            Err(e)
                if e.as_io()
                    .map_or(false, |e| e.kind() == io::ErrorKind::NotFound) =>
            {
                return Ok(None);
            }
            Err(e) => {
                return Err(
                    anyhow::Error::from(e).context("Error deserializing Xcode `version.plist`")
                );
            }
        };

        let version_parts = &mut plist.CFBundleShortVersionString.split('.');
        let major = version_parts
            .next()
            .ok_or_else(|| {
                XcodeVersionError::MalformedShortVersion(plist.CFBundleShortVersionString.clone())
            })
            .map(|v| v.to_owned())?;
        let minor = version_parts.next().unwrap_or("0").to_owned();
        let patch = version_parts.next().unwrap_or("0").to_owned();
        let build_number = plist.ProductBuildVersion;

        Ok(Some(Self {
            version_string: plist.CFBundleShortVersionString,
            major_version: major,
            minor_version: minor,
            patch_version: patch,
            build_number,
        }))
    }

    /// Construct from a string, formatted as: "version-build"
    /// (e.g., 14.3.0-14C18 or 14.1-14B47b)
    pub fn from_version_and_build(version_and_build: &str) -> anyhow::Result<Self> {
        let re = Regex::new(r"^((\d+)\.(\d+)(?:\.(\d+))?)\-([[:alnum:]]+)$").unwrap();
        if !re.is_match(version_and_build) {
            return Err(XcodeVersionError::MalformedVersionBuildString.into());
        }

        let caps = re.captures(version_and_build).unwrap();
        let version = caps.get(1).map_or("", |m| m.as_str());
        let major = caps.get(2).map_or("", |m| m.as_str());
        let minor = caps.get(3).map_or("", |m| m.as_str());
        let patch = caps.get(4).map_or("0", |m| m.as_str());
        let build = caps.get(5).map_or("", |m| m.as_str());

        Ok(Self {
            version_string: version.to_owned(),
            major_version: major.to_owned(),
            minor_version: minor.to_owned(),
            patch_version: patch.to_owned(),
            build_number: build.to_owned(),
        })
    }
}

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

    use super::*;

    fn write_plist(plist_content: &str) -> (tempfile::TempDir, PathBuf) {
        let workspace = tempfile::tempdir().expect("failed to create tempdir");
        let fake_xcode_dir = workspace.path().join("xcode_foo_bar.app").join("Contents");
        fs::DirBuilder::new()
            .recursive(true)
            .create(&fake_xcode_dir)
            .unwrap();
        let plist_path = fake_xcode_dir.join("version.plist");
        fs::write(&plist_path, plist_content).expect("failed to write plist");
        (workspace, plist_path)
    }

    #[test]
    fn test_resolves_version_from_plist() {
        let (_t, plist) = write_plist(
            r#"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>BuildVersion</key>
        <string>2</string>
        <key>CFBundleShortVersionString</key>
        <string>14.1.2</string>
        <key>CFBundleVersion</key>
        <string>21534.1</string>
        <key>ProductBuildVersion</key>
        <string>14B47b</string>
        <key>ProjectName</key>
        <string>IDEFrameworks</string>
        <key>SourceVersion</key>
        <string>21534001000000000</string>
</dict>
</plist>
        "#,
        );

        let got = XcodeVersionInfo::from_plist(&plist)
            .expect("failed to parse version info")
            .unwrap();
        let want = XcodeVersionInfo {
            version_string: "14.1.2".to_owned(),
            major_version: "14".to_owned(),
            minor_version: "1".to_owned(),
            patch_version: "2".to_owned(),
            build_number: "14B47b".to_owned(),
        };
        assert_eq!(want, got);
    }

    #[test]
    fn test_resolves_version_from_plist_no_patch_version() {
        let (_t, plist) = write_plist(
            r#"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>BuildVersion</key>
        <string>2</string>
        <key>CFBundleShortVersionString</key>
        <string>14.1</string>
        <key>CFBundleVersion</key>
        <string>21534.1</string>
        <key>ProductBuildVersion</key>
        <string>14B47b</string>
        <key>ProjectName</key>
        <string>IDEFrameworks</string>
        <key>SourceVersion</key>
        <string>21534001000000000</string>
</dict>
</plist>
        "#,
        );

        let got = XcodeVersionInfo::from_plist(&plist)
            .expect("failed to parse version info")
            .unwrap();
        let want = XcodeVersionInfo {
            version_string: "14.1".to_owned(),
            major_version: "14".to_owned(),
            minor_version: "1".to_owned(),
            patch_version: "0".to_owned(),
            build_number: "14B47b".to_owned(),
        };
        assert_eq!(want, got);
    }

    #[test]
    fn test_resolves_version_from_plist_no_minor_no_patch_version() {
        let (_t, plist) = write_plist(
            r#"
            <?xml version="1.0" encoding="UTF-8"?>
            <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
            <plist version="1.0">
            <dict>
                <key>BuildVersion</key>
                <string>35</string>
                <key>CFBundleShortVersionString</key>
                <string>14</string>
                <key>CFBundleVersion</key>
                <string>21335</string>
                <key>ProductBuildVersion</key>
                <string>14A309</string>
                <key>ProjectName</key>
                <string>IDEFrameworks</string>
                <key>SourceVersion</key>
                <string>21335000000000000</string>
            </dict>
            </plist>
        "#,
        );

        let got = XcodeVersionInfo::from_plist(&plist)
            .expect("failed to parse version info")
            .unwrap();
        let want = XcodeVersionInfo {
            version_string: "14".to_owned(),
            major_version: "14".to_owned(),
            minor_version: "0".to_owned(),
            patch_version: "0".to_owned(),
            build_number: "14A309".to_owned(),
        };
        assert_eq!(want, got);
    }

    #[test]
    fn test_no_plist() {
        let workspace = tempfile::tempdir().expect("failed to create tempdir");
        assert!(
            XcodeVersionInfo::from_plist(&workspace.path().join("foo.plist"))
                .unwrap()
                .is_none()
        );
    }

    #[test]
    fn test_resolves_version_from_version_and_build_string() {
        let version_build = "14.3.1-14C18";
        let got = XcodeVersionInfo::from_version_and_build(version_build)
            .expect("failed to parse version info");
        let want = XcodeVersionInfo {
            version_string: "14.3.1".to_owned(),
            major_version: "14".to_owned(),
            minor_version: "3".to_owned(),
            patch_version: "1".to_owned(),
            build_number: "14C18".to_owned(),
        };
        assert_eq!(want, got);

        let version_build2 = "14.1-14B47b";
        let got2 = XcodeVersionInfo::from_version_and_build(version_build2)
            .expect("failed to parse version info");
        let want2 = XcodeVersionInfo {
            version_string: "14.1".to_owned(),
            major_version: "14".to_owned(),
            minor_version: "1".to_owned(),
            patch_version: "0".to_owned(),
            build_number: "14B47b".to_owned(),
        };
        assert_eq!(want2, got2);
    }
}


/*
 * 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 more;
pub mod transition;


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

use buck2_util::late_binding::LateBinding;
use starlark::environment::GlobalsBuilder;

/// Functions defined in `buck2_build_api`.
pub struct MoreFunctions {
    pub register_builtin_providers: fn(&mut GlobalsBuilder),
    pub register_cmd_args: fn(&mut GlobalsBuilder),
    pub register_command_executor_config: fn(&mut GlobalsBuilder),
    pub register_provider: fn(&mut GlobalsBuilder),
    pub register_rule_defs: fn(&mut GlobalsBuilder),
    pub register_transitive_set: fn(&mut GlobalsBuilder),
}

pub static MORE_FUNCTIONS: LateBinding<MoreFunctions> = LateBinding::new("MORE_FUNCTIONS");


/*
 * 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 buck2_util::late_binding::LateBinding;
use starlark::environment::GlobalsBuilder;

pub static REGISTER_TRANSITION: LateBinding<fn(&mut GlobalsBuilder)> =
    LateBinding::new("REGISTER_TRANSITION");


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

use allocative::Allocative;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::cells::cell_path::CellPathRef;
use buck2_core::cells::name::CellName;
use buck2_core::cells::paths::CellRelativePathBuf;
use derive_more::Display;
use starlark::any::ProvidesStaticType;
use starlark::values::starlark_value;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;

#[derive(Debug, PartialEq, Display, ProvidesStaticType, NoSerialize, Allocative)]
pub struct CellRoot(CellPath);

impl CellRoot {
    pub fn new(name: CellName) -> Self {
        Self(CellPath::new(
            name,
            CellRelativePathBuf::unchecked_new("".to_owned()),
        ))
    }

    pub fn cell_path(&self) -> CellPathRef {
        self.0.as_ref()
    }
}

starlark_simple_value!(CellRoot);

#[starlark_value(type = "cell_root")]
impl<'v> StarlarkValue<'v> for CellRoot {}


/*
 * 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_core::configuration::data::ConfigurationData;
use derive_more::Display;
use starlark::any::ProvidesStaticType;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::values::starlark_value;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;

#[derive(Debug, PartialEq, Display, ProvidesStaticType, NoSerialize, Allocative)]
pub struct StarlarkConfiguration(pub ConfigurationData);

starlark_simple_value!(StarlarkConfiguration);

#[starlark_value(type = "configuration")]
impl<'v> StarlarkValue<'v> for StarlarkConfiguration {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(configuration_methods)
    }
}

#[starlark_module]
fn configuration_methods(builder: &mut MethodsBuilder) {
    #[starlark(attribute)]
    fn name<'v>(this: &'v StarlarkConfiguration) -> anyhow::Result<&'v str> {
        Ok(this.0.short_name())
    }

    #[starlark(attribute)]
    fn hash<'v>(this: &StarlarkConfiguration) -> anyhow::Result<&'v str> {
        Ok(this.0.output_hash().as_str())
    }
}


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

use std::hash::Hash;

use allocative::Allocative;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_core::provider::label::NonDefaultProvidersName;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::provider::label::ProvidersName;
use derive_more::Display;
use dupe::Dupe;
use serde::Serialize;
use serde::Serializer;
use starlark::any::ProvidesStaticType;
use starlark::coerce::Coerce;
use starlark::collections::StarlarkHasher;
use starlark::docs::StarlarkDocs;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::values::starlark_value;
use starlark::values::AllocValue;
use starlark::values::Freeze;
use starlark::values::Heap;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::Value;

use crate::types::cell_root::CellRoot;
use crate::types::label_relative_path::LabelRelativePath;
use crate::types::target_label::StarlarkConfiguredTargetLabel;
use crate::types::target_label::StarlarkTargetLabel;

impl Label {
    pub fn label(&self) -> &ConfiguredProvidersLabel {
        &self.label
    }
}

/// Container for `ConfiguredProvidersLabel` that gives users access to things like package, cell, etc. This can also be properly stringified by our forthcoming `CommandLine` object
#[derive(
    Clone,
    Debug,
    Coerce,
    Display,
    Trace,
    Freeze,
    ProvidesStaticType,
    StarlarkDocs,
    Allocative
)]
#[display(fmt = "{}", label)]
#[repr(C)]
pub struct Label {
    #[freeze(identity)]
    label: ConfiguredProvidersLabel,
}

starlark_simple_value!(Label);

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

impl Label {
    pub fn new(label: ConfiguredProvidersLabel) -> Self {
        Label { label }
    }

    pub fn inner(&self) -> &ConfiguredProvidersLabel {
        &self.label
    }
}

#[starlark_value(type = "label")]
impl<'v> StarlarkValue<'v> for Label
where
    Self: ProvidesStaticType<'v>,
{
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(configured_label_methods)
    }

    fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
        Ok(match Label::from_value(other) {
            Some(other) => self.label == other.label,
            None => false,
        })
    }

    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        self.label.hash(hasher);
        Ok(())
    }
}

/// A label is used to represent a configured target.
#[starlark_module]
fn configured_label_methods(builder: &mut MethodsBuilder) {
    /// For the label `fbcode//buck2/hello:world (ovr_config//platform/linux:x86_64-fbcode-46b26edb4b80a905)` this gives back `buck2/hello`
    #[starlark(attribute)]
    fn package<'v>(this: &'v Label) -> anyhow::Result<&'v str> {
        Ok(this.label.target().pkg().cell_relative_path().as_str())
    }

    /// For the label `fbcode//buck2/hello:world (ovr_config//platform/linux:x86_64-fbcode-46b26edb4b80a905)` this gives back `world`
    #[starlark(attribute)]
    fn name<'v>(this: &'v Label) -> anyhow::Result<&'v str> {
        Ok(this.label.target().name().as_str())
    }

    #[starlark(attribute)]
    fn sub_target<'v>(this: &'v Label) -> anyhow::Result<Option<Vec<&'v str>>> {
        Ok(match this.label.name() {
            ProvidersName::Default => None,
            ProvidersName::NonDefault(box NonDefaultProvidersName::Named(s)) => {
                Some(s.iter().map(|p| p.as_str()).collect())
            }
            ProvidersName::NonDefault(box NonDefaultProvidersName::UnrecognizedFlavor(_)) => {
                unreachable!(
                    "This should have been an error when looking up the corresponding analysis (`{}`)",
                    this.label
                )
            }
        })
    }

    /// For the label `fbcode//buck2/hello:world (ovr_config//platform/linux:x86_64-fbcode-46b26edb4b80a905)` this gives back `fbcode/buck2/hello`
    #[starlark(attribute)]
    fn path<'v>(this: &Label) -> anyhow::Result<LabelRelativePath> {
        Ok(LabelRelativePath(this.label.target().pkg().to_cell_path()))
    }

    /// For the label `fbcode//buck2/hello:world (ovr_config//platform/linux:x86_64-fbcode-46b26edb4b80a905)` this gives back `fbcode`
    #[starlark(attribute)]
    fn cell<'v>(this: &'v Label) -> anyhow::Result<&'v str> {
        Ok(this.label.target().pkg().cell_name().as_str())
    }

    #[starlark(attribute)]
    fn cell_root<'v>(this: &Label) -> anyhow::Result<CellRoot> {
        Ok(CellRoot::new(this.label.target().pkg().cell_name()))
    }

    /// For the label `fbcode//buck2/hello:world (ovr_config//platform/linux:x86_64-fbcode-46b26edb4b80a905)` this returns the unconfigured underlying target label (`fbcode//buck2/hello:world`)
    fn raw_target(this: &Label) -> anyhow::Result<StarlarkTargetLabel> {
        Ok(StarlarkTargetLabel::new(
            (*this.label.target().unconfigured()).dupe(),
        ))
    }

    /// Returns the underlying configured target label, dropping the sub target
    fn configured_target(this: &Label) -> anyhow::Result<StarlarkConfiguredTargetLabel> {
        Ok(StarlarkConfiguredTargetLabel::new(
            (*this.label.target()).dupe(),
        ))
    }
}

impl StarlarkProvidersLabel {
    pub fn label(&self) -> &ProvidersLabel {
        &self.label
    }
}

/// Container for `ProvidersLabel` that gives users access to things like package, cell, etc.
#[derive(
    Clone,
    Debug,
    Coerce,
    Display,
    Trace,
    Freeze,
    ProvidesStaticType,
    Serialize,
    Allocative
)]
#[display(fmt = "{}", label)]
#[repr(C)]
pub struct StarlarkProvidersLabel {
    #[freeze(identity)]
    #[serde(flatten)]
    label: ProvidersLabel,
}

starlark_simple_value!(StarlarkProvidersLabel);

impl StarlarkProvidersLabel {
    pub fn new(label: ProvidersLabel) -> Self {
        StarlarkProvidersLabel { label }
    }
}

#[starlark_value(type = "providers_label")]
impl<'v> StarlarkValue<'v> for StarlarkProvidersLabel
where
    Self: ProvidesStaticType<'v>,
{
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(label_methods)
    }

    fn equals(&self, other: Value<'v>) -> anyhow::Result<bool> {
        if let Some(other) = StarlarkProvidersLabel::from_value(other) {
            Ok(self.label == other.label)
        } else {
            Ok(false)
        }
    }

    fn write_hash(&self, hasher: &mut StarlarkHasher) -> anyhow::Result<()> {
        self.label.hash(hasher);
        Ok(())
    }
}

#[starlark_module]
fn label_methods(builder: &mut MethodsBuilder) {
    #[starlark(attribute)]
    fn name<'v>(this: &'v StarlarkProvidersLabel) -> anyhow::Result<&'v str> {
        Ok(this.label.target().name().as_str())
    }

    #[starlark(attribute)]
    fn sub_target<'v>(this: &'v StarlarkProvidersLabel) -> anyhow::Result<Option<Vec<&'v str>>> {
        Ok(match this.label.name() {
            ProvidersName::Default => None,
            ProvidersName::NonDefault(box NonDefaultProvidersName::Named(s)) => {
                Some(s.iter().map(|p| p.as_str()).collect())
            }
            ProvidersName::NonDefault(box NonDefaultProvidersName::UnrecognizedFlavor(_)) => {
                unreachable!(
                    "This should have been an error when looking up the corresponding analysis (`{}`)",
                    this.label
                )
            }
        })
    }

    #[starlark(attribute)]
    fn path<'v>(this: &StarlarkProvidersLabel, heap: &Heap) -> anyhow::Result<Value<'v>> {
        let path = LabelRelativePath(this.label.target().pkg().to_cell_path());
        Ok(path.alloc_value(heap))
    }

    #[starlark(attribute)]
    fn cell<'v>(this: &'v StarlarkProvidersLabel) -> anyhow::Result<&'v str> {
        let cell = this.label.target().pkg().cell_name().as_str();
        Ok(cell)
    }

    /// Returns the unconfigured underlying target label.
    fn raw_target(this: &StarlarkProvidersLabel) -> anyhow::Result<StarlarkTargetLabel> {
        Ok(StarlarkTargetLabel::new((*this.label.target()).dupe()))
    }
}

#[cfg(test)]
mod tests {
    // Tests live in `buck2_build_api` crate because tests depend on `Tester` type
    // which depends on `buck2_build_api` heavily.
}


/*
 * 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_core::cells::cell_path::CellPath;
use derive_more::Display;
use starlark::any::ProvidesStaticType;
use starlark::environment::Methods;
use starlark::environment::MethodsBuilder;
use starlark::environment::MethodsStatic;
use starlark::values::starlark_value;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;

#[derive(
    Debug,
    PartialEq,
    Display,
    ProvidesStaticType,
    NoSerialize,
    Allocative,
    StarlarkDocs
)]
pub struct LabelRelativePath(pub CellPath);

starlark_simple_value!(LabelRelativePath);

#[starlark_value(type = "label_relative_path")]
impl<'v> StarlarkValue<'v> for LabelRelativePath {
    fn get_methods() -> Option<&'static Methods> {
        static RES: MethodsStatic = MethodsStatic::new();
        RES.methods(label_relative_path_methods)
    }
}

#[starlark_module]
fn label_relative_path_methods(builder: &mut MethodsBuilder) {
    fn add(this: &LabelRelativePath, arg: &str) -> anyhow::Result<LabelRelativePath> {
        Ok(LabelRelativePath((this).0.join_normalized(arg)?))
    }
}


/*
 * 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 cell_root;
pub mod configuration;
pub mod label;
pub mod label_relative_path;
pub mod provider;
pub mod rule;
pub mod target_label;
pub mod transition;


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

use buck2_util::late_binding::LateBinding;
use starlark::values::FrozenValue;

/// `rule()` value `impl` field.
pub static FROZEN_RULE_GET_IMPL: LateBinding<fn(FrozenValue) -> anyhow::Result<FrozenValue>> =
    LateBinding::new("FROZEN_RULE_GET_IMPL");