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

use std::sync::Arc;

use buck2_common::legacy_configs::testing::TestConfigParserFileOps;
use buck2_common::legacy_configs::LegacyBuckConfig;
use buck2_common::legacy_configs::LegacyBuckConfigs;
use buck2_common::package_listing::listing::testing::PackageListingExt;
use buck2_common::package_listing::listing::PackageListing;
use buck2_common::result::SharedResult;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::bzl::ImportPath;
use buck2_core::cells::alias::NonEmptyCellAlias;
use buck2_core::cells::build_file_cell::BuildFileCell;
use buck2_core::cells::cell_root_path::CellRootPathBuf;
use buck2_core::cells::name::CellName;
use buck2_core::cells::*;
use buck2_core::fs::paths::abs_norm_path::AbsNormPath;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_interpreter::extra::InterpreterHostArchitecture;
use buck2_interpreter::extra::InterpreterHostPlatform;
use buck2_interpreter::factory::StarlarkPassthroughProvider;
use buck2_interpreter::file_loader::LoadedModule;
use buck2_interpreter::file_loader::LoadedModules;
use buck2_interpreter::import_paths::ImplicitImportPaths;
use buck2_interpreter::path::OwnedStarlarkModulePath;
use buck2_interpreter::path::StarlarkModulePath;
use buck2_interpreter::path::StarlarkPath;
use buck2_interpreter::prelude_path::PreludePath;
use buck2_node::nodes::eval_result::EvaluationResult;
use buck2_node::nodes::targets_map::TargetsMap;
use dupe::Dupe;
use indoc::indoc;
use maplit::hashmap;
use starlark::environment::GlobalsBuilder;
use starlark::starlark_module;
use starlark::values::Value;

use crate::interpreter::configuror::AdditionalGlobalsFn;
use crate::interpreter::configuror::BuildInterpreterConfiguror;
use crate::interpreter::global_interpreter_state::GlobalInterpreterState;
use crate::interpreter::interpreter_for_cell::InterpreterForCell;
use crate::interpreter::interpreter_for_cell::ParseResult;
use crate::super_package::data::SuperPackage;

/// Simple container that allows us to instrument things like imports
#[derive(Debug)]
pub struct Tester {
    cell_alias_resolver: CellAliasResolver,
    cell_resolver: CellResolver,
    configs: LegacyBuckConfigs,
    loaded_modules: LoadedModules,
    additional_globals: Vec<AdditionalGlobalsFn>,
    prelude_path: Option<PreludePath>,
}

/// These functions will be available in the starlark environment for all code running through a Tester.
#[starlark_module]
pub fn common_helpers(builder: &mut GlobalsBuilder) {
    /// Returns the string that pprint() will produce
    fn pprint_str<'v>(value: Value<'v>) -> anyhow::Result<String> {
        Ok(format!("{:#}", value))
    }
}

/// Helpers required to help drive the interpreter
pub type CellsData = (CellAliasResolver, CellResolver, LegacyBuckConfigs);

/// The same as `run_starlark_test`, but just make sure the parse succeeds;
/// ignore the targets
pub fn run_simple_starlark_test(content: &str) -> anyhow::Result<()> {
    let mut tester = Tester::new()?;
    match tester.run_starlark_test(content) {
        Ok(_) => Ok(()),
        Err(e) => Err(anyhow::Error::from(e)),
    }
}

pub fn cells(extra_root_config: Option<&str>) -> anyhow::Result<CellsData> {
    let mut agg = CellsAggregator::new();
    agg.add_cell_entry(
        CellRootPathBuf::new(ProjectRelativePathBuf::try_from("".to_owned())?),
        NonEmptyCellAlias::new("root".to_owned()).unwrap(),
        CellRootPathBuf::new(ProjectRelativePathBuf::try_from("".to_owned())?),
    )?;
    let resolver = agg.make_cell_resolver()?;
    let root_path = if cfg!(windows) {
        AbsNormPath::new("c:/root").unwrap()
    } else {
        AbsNormPath::new("/root").unwrap()
    };

    let configs = hashmap![
        CellName::testing_new("root") =>
        LegacyBuckConfig::parse_with_file_ops(
            root_path,
            &mut TestConfigParserFileOps::new(&[
                (
                    "/root",
                    indoc!(
                        r#"
                        [section]
                            key = value
                            other = 1
                            multiline = hello \
                                        world!
                        [config]
                            key = okay

                        <file:extra_cfg>
                    "#
                    ),
                ),
                ("/extra_cfg", extra_root_config.unwrap_or("")),
            ])?,
            &[],
        )?
    ];

    Ok((
        resolver
            .get(CellName::testing_new("root"))?
            .cell_alias_resolver()
            .dupe(),
        resolver,
        LegacyBuckConfigs::new(configs),
    ))
}

pub fn expect_error<T>(result: SharedResult<T>, content: &str, expected: &str) {
    match result {
        Ok(_) => {
            eprintln!(
                "Expected starlark failure, got success.\nCode contents:\n{}",
                content
            );
            panic!();
        }
        Err(e) => {
            let returned = e.to_string();
            if !returned.contains(expected) {
                eprintln!(
                    "Could not find expected error string.\nExpected:\n{}\n\nError:\n{}\n\nCode contents:\n{}",
                    expected, returned, content
                );
                panic!();
            }
        }
    }
}

impl Tester {
    pub fn new() -> anyhow::Result<Self> {
        Self::with_cells(cells(None)?)
    }

    pub fn with_cells(cells_data: CellsData) -> anyhow::Result<Self> {
        let (cell_alias_resolver, cell_resolver, configs) = cells_data;
        Ok(Self {
            cell_alias_resolver,
            cell_resolver,
            configs,
            loaded_modules: LoadedModules::default(),
            additional_globals: Vec::new(),
            prelude_path: None,
        })
    }

    pub fn additional_globals(
        &mut self,
        additional_globals: impl Fn(&mut GlobalsBuilder) + Sync + Send + 'static,
    ) {
        self.additional_globals
            .push(AdditionalGlobalsFn(Arc::new(additional_globals)));
    }

    pub fn set_prelude(&mut self, prelude_import: ImportPath) {
        self.prelude_path = Some(PreludePath::testing_new(prelude_import));
    }

    fn interpreter(&self) -> anyhow::Result<Arc<InterpreterForCell>> {
        let import_paths = ImplicitImportPaths::parse(
            self.configs
                .get(self.cell_alias_resolver.resolve_self())
                .unwrap(),
            BuildFileCell::new(self.cell_alias_resolver.resolve_self()),
            &self.cell_alias_resolver,
        )?;
        let additional_globals = self.additional_globals.clone();
        Ok(Arc::new(InterpreterForCell::new(
            self.cell_alias_resolver.dupe(),
            Arc::new(GlobalInterpreterState::new(
                &self.configs,
                self.cell_resolver.dupe(),
                BuildInterpreterConfiguror::new(
                    self.prelude_path.clone(),
                    InterpreterHostPlatform::Linux,
                    InterpreterHostArchitecture::X86_64,
                    None,
                    false,
                    false,
                    |_| {},
                    |_| {},
                    |_| {},
                    |_| {},
                    Some(AdditionalGlobalsFn(Arc::new(move |globals_builder| {
                        common_helpers(globals_builder);
                        for additional_globals in &additional_globals {
                            (additional_globals.0)(globals_builder)
                        }
                    }))),
                )?,
                false,
            )?),
            Arc::new(import_paths),
        )?))
    }

    pub fn parse(&self, import: StarlarkPath, content: &str) -> ParseResult {
        self.interpreter()
            .unwrap()
            .parse(import, content.to_owned())
            .unwrap()
    }

    /// Evaluate an import, and add it to the existing loaded_modules() map to be
    /// used with `eval_build_file`
    pub fn add_import(&mut self, path: &ImportPath, content: &str) -> anyhow::Result<LoadedModule> {
        let loaded = self.eval_import(path, content, self.loaded_modules.clone())?;
        self.loaded_modules
            .map
            .insert(StarlarkModulePath::LoadFile(path).to_owned(), loaded.dupe());
        Ok(loaded)
    }

    /// Evaluate an import without adding it to the accumulated `Tester`
    /// state, and with a specified set of modules loaded into the
    /// environment
    pub fn eval_import(
        &self,
        path: &ImportPath,
        content: &str,
        loaded_modules: LoadedModules,
    ) -> anyhow::Result<LoadedModule> {
        let interpreter = self.interpreter()?;
        let ParseResult(ast, _) =
            interpreter.parse(StarlarkPath::LoadFile(path), content.to_owned())?;
        let buckconfig = self
            .configs
            .get(self.cell_alias_resolver.resolve_self())
            .unwrap();
        let root_buckconfig = self.configs.get(self.cell_resolver.root_cell()).unwrap();
        let mut provider = StarlarkPassthroughProvider;
        let env = interpreter.eval_module(
            StarlarkModulePath::LoadFile(path),
            buckconfig,
            root_buckconfig,
            ast,
            loaded_modules.clone(),
            &mut provider,
        )?;
        Ok(LoadedModule::new(
            OwnedStarlarkModulePath::LoadFile(path.clone()),
            loaded_modules,
            env,
        ))
    }

    /// Evaluate a build file, adding anything from `add_import` to the
    /// environment
    pub fn eval_build_file(
        &self,
        path: &BuildFilePath,
        content: &str,
        package_listing: PackageListing,
    ) -> anyhow::Result<EvaluationResult> {
        self.eval_build_file_with_loaded_modules(
            path,
            content,
            self.loaded_modules.clone(),
            package_listing,
        )
    }

    /// Evaluate a build file, but only add a specific set of loaded modules to
    /// the environment
    pub fn eval_build_file_with_loaded_modules(
        &self,
        path: &BuildFilePath,
        content: &str,
        loaded_modules: LoadedModules,
        package_listing: PackageListing,
    ) -> anyhow::Result<EvaluationResult> {
        let interpreter = self.interpreter()?;
        let ParseResult(ast, _) =
            interpreter.parse(StarlarkPath::BuildFile(path), content.to_owned())?;
        let buckconfig = self
            .configs
            .get(self.cell_alias_resolver.resolve_self())
            .unwrap();
        let root_buckconfig = self.configs.get(self.cell_resolver.root_cell()).unwrap();
        let mut provider = StarlarkPassthroughProvider;
        let eval_result = interpreter.eval_build_file(
            path,
            buckconfig,
            root_buckconfig,
            package_listing,
            SuperPackage::default(),
            false,
            ast,
            loaded_modules,
            &mut provider,
        )?;
        Ok(eval_result)
    }

    pub fn build_file_path() -> BuildFilePath {
        BuildFilePath::testing_new("root//some/package:BUCK")
    }

    /// Run a starlark test with a basic environment. See
    /// `run_starlark_test()` above.
    pub fn run_starlark_test(&mut self, content: &str) -> SharedResult<TargetsMap> {
        let import_path = ImportPath::testing_new("root//some/package:defs.bzl");
        self.add_import(
            &import_path,
            &(indoc!(
                r#"
        def assert_eq(a, b):
            if a != b:
                fail("expected: %s got %s" % (a, b))

        "#
            )
            .to_owned()
                + content),
        )?;

        let buildfile_path = Self::build_file_path();
        let res = self.eval_build_file(
            &buildfile_path,
            indoc!(
                r#"
            load(":defs.bzl", "test")
            test()
            "#
            ),
            PackageListing::testing_files(&["file1.java", "file2.java"]),
        )?;
        Ok(res.targets().clone())
    }

    pub fn run_starlark_test_expecting_error(&mut self, content: &str, expected: &str) {
        expect_error(self.run_starlark_test(content), content, expected);
    }

    /// Try to evaluate some content in a .bzl file. Returns `()` if
    /// evaluation was successful. This can be handy if the .bzl
    /// evaluation environment is different from the build file
    /// environment.
    pub fn run_starlark_bzl_test(&mut self, content: &str) -> SharedResult<()> {
        let import_path = ImportPath::testing_new("root//some/package:defs.bzl");
        let template = indoc!(
            r#"
            def _assert_eq_ignore_hash_for_strings(a, b):
                (a0, a1, a2) = a.partition("<HASH>")
                # No hash in string
                if a1 == "":
                    assert_eq(a0, b)
                    return

                if not b.startswith(a0):
                    fail("expected %s to match %s", (b, a))
                if not b.endswith(a2):
                    fail("expected %s to match %s", (b, a))
                hash = b[len(a0):-len(a2)]
                if len(hash) != len("0123456789abcdef"):
                    fail("expected %s to match %s", (b, a))

            def assert_eq_ignore_hash(a, b):
                if type(a) == type(""):
                    a = [a]
                    b = [b]

                assert_eq(len(a), len(b))
                for i in range(len(a)):
                    _assert_eq_ignore_hash_for_strings(a[i], b[i])

            def assert_eq(a, b):
                if a != b:
                    fail("expected: %s got %s" % (a, b))

            def assert_ne(a, b):
                if a == b:
                    fail("expected: %s to not equal %s" % (a, b))"#
        );

        self.add_import(&import_path, &format!("{}\n\n{}", template, content))?;

        let test_path = ImportPath::testing_new("root//some/package:test.bzl");
        let test_content = indoc!(
            r#"
            load("//some/package:defs.bzl", "test")
            test()
            "#
        );
        self.add_import(&test_path, test_content)
            .map(|_| ())
            .map_err(|e| e.into())
    }

    pub fn run_starlark_bzl_test_expecting_error(&mut self, content: &str, expected: &str) {
        expect_error(self.run_starlark_bzl_test(content), content, expected);
    }
}


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

use starlark::environment::GlobalsBuilder;
use starlark::starlark_module;
use starlark::values::list::AllocList;
use starlark::values::Heap;
use starlark::values::Value;

#[starlark_module]
pub(crate) fn register_dedupe(builder: &mut GlobalsBuilder) {
    /// Remove duplicates in a list. Uses identity of value (pointer),
    /// rather than by equality. In many cases you should use a transitive set instead.
    fn dedupe<'v>(
        #[starlark(require = pos)] val: Value<'v>,
        heap: &'v Heap,
    ) -> anyhow::Result<Value<'v>> {
        let mut seen = HashSet::new();
        let mut res = Vec::new();
        for v in val.iterate(heap)? {
            let p = v.identity();
            if !seen.contains(&p) {
                seen.insert(p);
                res.push(v);
            }
        }
        Ok(heap.alloc(AllocList(res)))
    }
}

#[cfg(test)]
mod tests {

    use starlark::assert::Assert;

    use crate::interpreter::functions::dedupe::register_dedupe;

    #[test]
    fn test_dedupe() {
        let mut a = Assert::new();
        a.globals_add(register_dedupe);
        a.pass(
            r#"
assert_eq(dedupe([1,2,3]), [1,2,3])
assert_eq(dedupe([1,2,3,2,1]), [1,2,3])
a = [1]
b = [1]
assert_eq(dedupe([a,b,a]), [a,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_interpreter::extra::xcode::XcodeVersionInfo;
use buck2_interpreter::extra::InterpreterHostArchitecture;
use buck2_interpreter::extra::InterpreterHostPlatform;
use derivative::Derivative;
use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::structs::AllocStruct;
use starlark::values::structs::StructRef;
use starlark::values::AllocFrozenValue;
use starlark::values::FrozenHeap;
use starlark::values::FrozenValue;
use starlark::values::OwnedFrozenValue;
use starlark::values::ValueOfUnchecked;

use crate::interpreter::build_context::BuildContext;

fn new_host_info(
    host_platform: InterpreterHostPlatform,
    host_architecture: InterpreterHostArchitecture,
    xcode_info: Option<&XcodeVersionInfo>,
) -> OwnedFrozenValue {
    let heap = FrozenHeap::new();

    fn new_struct<V: AllocFrozenValue + Copy>(
        heap: &FrozenHeap,
        values: &[(&str, V)],
    ) -> FrozenValue {
        heap.alloc(AllocStruct(values.iter().copied()))
    }

    let platform = |name, x| (name, host_platform == x);
    let os = new_struct(
        &heap,
        &[
            platform("is_linux", InterpreterHostPlatform::Linux),
            platform("is_macos", InterpreterHostPlatform::MacOS),
            platform("is_windows", InterpreterHostPlatform::Windows),
            platform("is_freebsd", InterpreterHostPlatform::FreeBsd),
            platform("is_unknown", InterpreterHostPlatform::Unknown),
        ],
    );

    let host = |name, x| (name, host_architecture == x);
    let arch = new_struct(
        &heap,
        &[
            host("is_x86_64", InterpreterHostArchitecture::X86_64),
            host("is_aarch64", InterpreterHostArchitecture::AArch64),
            host("is_arm", InterpreterHostArchitecture::Arm),
            ("is_armeb", false),
            host("is_i386", InterpreterHostArchitecture::X86),
            host("is_mips", InterpreterHostArchitecture::Mips),
            host("is_mips64", InterpreterHostArchitecture::Mips64),
            ("is_mipsel", false),
            ("is_mipsel64", false),
            host("is_powerpc", InterpreterHostArchitecture::PowerPc),
            host("is_ppc64", InterpreterHostArchitecture::PowerPc64),
            host("is_unknown", InterpreterHostArchitecture::Unknown),
        ],
    );

    let xcode = {
        let mk_value = |sel: fn(&XcodeVersionInfo) -> &String| match xcode_info {
            Some(i) => heap.alloc(sel(i).as_str()),
            None => FrozenValue::new_none(),
        };

        new_struct(
            &heap,
            &[
                ("version_string", mk_value(|x| &x.version_string)),
                ("major_version", mk_value(|x| &x.major_version)),
                ("minor_version", mk_value(|x| &x.minor_version)),
                ("patch_version", mk_value(|x| &x.patch_version)),
                ("build_number", mk_value(|x| &x.build_number)),
            ],
        )
    };

    let info = new_struct(
        &heap,
        &[
            ("os", os),
            ("arch", arch),
            // TODO(cjhopman): Remove in favour of version_info() in Buck v1 and v2
            // We want to be able to determine if we are on Buck v2 or not, this mechanism
            // is quick, cheap and Buck v1 compatible.
            ("buck2", FrozenValue::new_bool(true)),
            ("xcode", xcode),
        ],
    );

    // Safe because the value info was allocated into the heap
    unsafe { OwnedFrozenValue::new(heap.into_ref(), info) }
}

#[starlark_module]
pub fn register_host_info(builder: &mut GlobalsBuilder) {
    /// The `host_info()` function is used to get the current OS and processor architecture on the host. The structure returned is laid out thusly:
    ///
    /// ```python
    /// struct(
    ///     os=struct(
    ///         is_linux=True|False,
    ///         is_macos=True|False,
    ///         is_windows=True|False,
    ///         is_freebsd=True|False,
    ///         is_unknown=True|False,
    ///     ),
    ///     arch=struct(
    ///         is_aarch64=True|False,
    ///         is_arm=True|False,
    ///         is_armeb=True|False,
    ///         is_i386=True|False,
    ///         is_mips=True|False,
    ///         is_mips64=True|False,
    ///         is_mipsel=True|False,
    ///         is_mipsel64=True|False,
    ///         is_powerpc=True|False,
    ///         is_ppc64=True|False,
    ///         is_x86_64=True|False,
    ///         is_unknown=True|False,
    ///     ),
    /// )
    /// ```
    #[starlark(speculative_exec_safe)]
    fn host_info<'v>(
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<ValueOfUnchecked<'v, StructRef<'v>>> {
        // Keeping this `speculative_exec_safe` is safe because BuildContext's `HostInfo`,
        // even when evaluated speculatively, is going to be the same across all interpreters
        // that might reuse each other's output.
        let host_info = &BuildContext::from_context(eval)?.host_info;
        Ok(ValueOfUnchecked::new(
            host_info.value.owned_value(eval.frozen_heap()),
        ))
    }
}

#[derive(Derivative, Clone, Debug, Allocative)]
#[derivative(PartialEq)]
pub struct HostInfo {
    // These first three fields are for equality only, otherwise not used
    platform: InterpreterHostPlatform,
    arch: InterpreterHostArchitecture,
    xcode: Option<XcodeVersionInfo>,
    // The actual value which we ignore for equality, which is OK because of above
    #[derivative(PartialEq = "ignore")]
    value: OwnedFrozenValue,
}

impl HostInfo {
    pub(crate) fn new(
        platform: InterpreterHostPlatform,
        arch: InterpreterHostArchitecture,
        xcode: Option<XcodeVersionInfo>,
    ) -> Self {
        let value = new_host_info(platform, arch, xcode.as_ref());
        Self {
            platform,
            arch,
            xcode,
            value,
        }
    }
}


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

use starlark::collections::SmallMap;
use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::none::NoneType;
use starlark::values::Value;

#[starlark_module]
pub fn register_load_symbols(builder: &mut GlobalsBuilder) {
    /// Used in a `.bzl` file to set exported symbols. In most cases just defining
    /// the symbol as a top-level binding is sufficient, but sometimes the names
    /// might be programatically generated.
    ///
    /// It is undefined behaviour if you try and use any of the symbols exported
    /// here later in the same module, or if they overlap with existing definitions.
    /// This function should be used rarely.
    fn load_symbols<'v>(
        symbols: SmallMap<&'v str, Value<'v>>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<NoneType> {
        for (k, v) in symbols.into_iter() {
            eval.set_module_variable_at_some_point(k, v)?;
        }
        Ok(NoneType)
    }
}


/*
 * 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(crate) mod dedupe;
pub mod host_info;
pub mod load_symbols;
pub mod read_config;
pub(crate) mod regex;
pub mod sha256;
pub mod soft_error;
pub mod warning;


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

use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::none::NoneOr;
use starlark::values::StringValue;
use starlark::values::StringValueLike;
use starlark::values::Value;

use crate::interpreter::build_context::BuildContext;

#[starlark_module]
pub fn register_read_config(globals: &mut GlobalsBuilder) {
    /// Read a configuration from the nearest enclosing `.buckconfig`
    /// of the `BUCK` file that started evaluation of this code.
    ///
    /// As an example, if you have a `.buckconfig` of:
    ///
    /// ```toml
    /// [package_options]
    /// compile = super_fast
    /// ```
    ///
    /// Then you would get the following results:
    ///
    /// ```python
    /// read_config("package_options", "compile") == "super_fast"
    /// read_config("package_options", "linker") == None
    /// read_config("package_options", "linker", "a_default") == "a_default"
    /// ```
    ///
    /// In general the use of `.buckconfig` is discouraged in favour of `select`,
    /// but it can still be useful.
    #[starlark(speculative_exec_safe)]
    fn read_config<'v>(
        section: StringValue,
        key: StringValue,
        default: Option<Value<'v>>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        let buckconfig = &BuildContext::from_context(eval)?.buckconfig;
        match buckconfig.get(section, key)? {
            Some(v) => Ok(v.to_value()),
            None => Ok(default.unwrap_or_else(Value::new_none)),
        }
    }

    /// Like `read_config` but the project root `.buckconfig` is always consulted,
    /// regardless of the cell of the originating `BUCK` file.
    #[starlark(speculative_exec_safe)]
    fn read_root_config<'v>(
        #[starlark(require = pos)] section: StringValue,
        #[starlark(require = pos)] key: StringValue,
        // Unlike `read_config` we only allow string or `None` as default.
        #[starlark(require = pos, default = NoneOr::None)] default: NoneOr<StringValue<'v>>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<NoneOr<StringValue<'v>>> {
        let buckconfig = &BuildContext::from_context(eval)?.root_buckconfig;
        match buckconfig.get(section, key)? {
            Some(v) => Ok(NoneOr::Other(v.to_string_value())),
            None => Ok(default),
        }
    }
}


/*
 * 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 fancy_regex::Regex;
use starlark::environment::GlobalsBuilder;
use starlark::starlark_module;

#[starlark_module]
pub fn register_regex(builder: &mut GlobalsBuilder) {
    /// Test if a regular expression matches a string. Fails if the regular expression
    /// is malformed.
    ///
    /// As an example:
    ///
    /// ```python
    /// regex_match("^[a-z]*$", "hello") == True
    /// regex_match("^[a-z]*$", "1234") == False
    /// ```
    fn regex_match(
        #[starlark(require = pos)] regex: &str,
        #[starlark(require = pos)] str: &str,
    ) -> anyhow::Result<bool> {
        let re = Regex::new(regex)?;
        Ok(re.is_match(str)?)
    }
}

#[cfg(test)]
mod tests {
    use starlark::assert::Assert;

    use crate::interpreter::functions::regex::register_regex;

    #[test]
    fn test_regex() {
        let mut a = Assert::new();
        a.globals_add(register_regex);
        a.eq("regex_match('abc|def|ghi', 'abc')", "True");
        a.eq("regex_match('abc|def|ghi', 'xyz')", "False");
        a.eq("regex_match('^((?!abc).)*$', 'abc')", "False");
        a.eq("regex_match('^((?!abc).)*$', 'xyz')", "True");
    }
}


/*
 * 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 sha2::Digest;
use sha2::Sha256;
use starlark::environment::GlobalsBuilder;
use starlark::starlark_module;

/// Contains functions that we include in all contexts.
#[starlark_module]
pub fn register_sha256(builder: &mut GlobalsBuilder) {
    /// Computes a sha256 digest for a string. Returns the hex representation of the digest.
    ///
    /// ```python
    /// sha256("Buck2 is the best build system") == "bb99a3f19ecba6c4d2c7cd321b63b669684c713881baae21a6b1d759b3ec6ac9"
    /// ```
    fn sha256(#[starlark(require = pos)] val: &str) -> anyhow::Result<String> {
        let hash = Sha256::digest(val.as_bytes());
        Ok(hex::encode(hash))
    }
}

#[cfg(test)]
mod tests {

    use starlark::assert::Assert;

    use crate::interpreter::functions::sha256::register_sha256;

    #[test]
    fn test_sha256() {
        let mut a = Assert::new();
        a.globals_add(register_sha256);
        a.eq(
            "sha256('123')",
            "'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3'",
        );
    }
}


/*
 * 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_core::soft_error;
use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::none::NoneType;

#[derive(Debug, thiserror::Error)]
enum SoftErrorError {
    #[error("Error produced by Starlark: {category}: {message}\n{call_stack}")]
    StarlarkSoftError {
        category: String,
        message: String,
        call_stack: String,
    },
    #[error(
        "soft_error originated from starlark should have category starting with `starlark_`, got: `{0}`"
    )]
    InvalidCategory(String),
}

#[starlark_module]
pub fn register_soft_error(builder: &mut GlobalsBuilder) {
    /// Produce an error that will become a hard error at some point in the future, but
    /// for now is a warning which is logged to the server.
    /// In the open source version of Buck2 this function always results in an error.
    ///
    /// Called passing a stable key (must be `snake_case` and start with `starlark_`,
    /// used for consistent reporting) and an arbitrary message (used for debugging).
    ///
    /// As an example:
    ///
    /// ```python
    /// soft_error(
    ///     "starlark_rule_is_too_long",
    ///     "Length of property exceeds 100 characters in " + repr(ctx.label),
    /// )
    /// ```
    fn soft_error<'v>(
        #[starlark(require = pos)] category: &str,
        #[starlark(require = pos)] message: String,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<NoneType> {
        if !category.starts_with("starlark_") {
            return Err(SoftErrorError::InvalidCategory(category.to_owned()).into());
        }
        soft_error!(
            category,
            SoftErrorError::StarlarkSoftError {
                category: category.to_owned(),
                message,
                call_stack: eval.call_stack().to_string()
            }
            .into()
        )?;
        Ok(NoneType)
    }
}


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

use starlark::environment::GlobalsBuilder;
use starlark::starlark_module;
use starlark::values::none::NoneType;

#[starlark_module]
pub fn register_warning(builder: &mut GlobalsBuilder) {
    /// Print a warning. The line will be decorated with the timestamp and other details,
    /// including the word `WARN` (colored, if the console supports it).
    ///
    /// If you are not writing a warning, use `print` instead. Be aware that printing
    /// lots of output (warnings or not) can be cause all information to be ignored by the user.
    fn warning(#[starlark(require = pos)] x: &str) -> anyhow::Result<NoneType> {
        tracing::warn!("{}", x);
        Ok(NoneType)
    }
}


/*
 * 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 anyhow::Context;
use buck2_core::target::name::TargetName;
use buck2_node::attrs::attr::CoercedValue;
use buck2_node::attrs::attr_type::string::StringLiteral;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::internal::attr_is_configurable;
use buck2_node::attrs::internal::NAME_ATTRIBUTE_FIELD;
use buck2_node::attrs::internal::VISIBILITY_ATTRIBUTE_FIELD;
use buck2_node::attrs::internal::WITHIN_VIEW_ATTRIBUTE_FIELD;
use buck2_node::attrs::spec::AttributeSpec;
use buck2_node::attrs::values::AttrValues;
use buck2_util::arc_str::ArcStr;
use dupe::Dupe;
use starlark::docs::DocString;
use starlark::eval::ParametersParser;
use starlark::eval::ParametersSpec;
use starlark::typing::Ty;
use starlark::values::Value;

use crate::attrs::AttributeCoerceExt;
use crate::interpreter::module_internals::ModuleInternals;

pub trait AttributeSpecExt {
    fn parse_params<'v>(
        &self,
        param_parser: ParametersParser<'v, '_>,
        arg_count: usize,
        internals: &ModuleInternals,
    ) -> anyhow::Result<(TargetName, AttrValues)>;

    /// Returns a starlark Parameters for the rule callable.
    fn signature(&self, rule_name: String) -> ParametersSpec<Value<'_>>;

    fn starlark_types(&self) -> Vec<Ty>;

    fn docstrings(&self) -> HashMap<String, Option<DocString>>;
}

impl AttributeSpecExt for AttributeSpec {
    /// Parses params extracting the TargetName and the attribute values to store in the TargetNode.
    fn parse_params<'v>(
        &self,
        mut param_parser: ParametersParser<'v, '_>,
        arg_count: usize,
        internals: &ModuleInternals,
    ) -> anyhow::Result<(TargetName, AttrValues)> {
        let mut attr_values = AttrValues::with_capacity(arg_count);

        let mut indices = self.attr_specs();
        let name = match indices.next() {
            Some((name_name, attr_idx, _attr))
                if name_name == NAME_ATTRIBUTE_FIELD && attr_idx.index_in_attribute_spec == 0 =>
            {
                let name: &str = param_parser.next(NAME_ATTRIBUTE_FIELD)?;

                attr_values.push_sorted(
                    attr_idx,
                    CoercedAttr::String(StringLiteral(ArcStr::from(name))),
                );

                TargetName::new(name)?
            }
            _ => panic!("First attribute is `name`, it is known"),
        };

        for (attr_name, attr_idx, attribute) in indices {
            let configurable = attr_is_configurable(attr_name);

            let user_value: Option<Value> = match attribute.default() {
                Some(_) => param_parser.next_opt(attr_name)?,
                None => Some(param_parser.next(attr_name)?),
            };

            let is_visibility = attr_name == VISIBILITY_ATTRIBUTE_FIELD;
            let is_with_view = attr_name == WITHIN_VIEW_ATTRIBUTE_FIELD;
            if let Some(v) = user_value {
                let mut coerced = attribute
                    .coerce(
                        attr_name,
                        configurable,
                        internals.attr_coercion_context(),
                        v,
                    )
                    .with_context(|| {
                        format!(
                            "Error coercing attribute `{}` of `{}:{}`",
                            attr_name,
                            internals.buildfile_path().package(),
                            name,
                        )
                    })?;

                if is_visibility {
                    if coerced == CoercedValue::Default {
                        coerced = CoercedValue::Custom(CoercedAttr::Visibility(
                            internals.super_package.visibility().dupe(),
                        ));
                    }
                } else if is_with_view {
                    if coerced == CoercedValue::Default {
                        coerced = CoercedValue::Custom(CoercedAttr::WithinView(
                            internals.super_package.within_view().dupe(),
                        ));
                    }
                }

                match coerced {
                    CoercedValue::Custom(v) => {
                        attr_values.push_sorted(attr_idx, v);
                    }
                    CoercedValue::Default => {}
                }
            } else if is_visibility {
                attr_values.push_sorted(
                    attr_idx,
                    CoercedAttr::Visibility(internals.super_package.visibility().dupe()),
                );
            } else if is_with_view {
                attr_values.push_sorted(
                    attr_idx,
                    CoercedAttr::WithinView(internals.super_package.within_view().dupe()),
                );
            }
        }

        attr_values.shrink_to_fit();
        Ok((name, attr_values))
    }

    /// Returns a starlark Parameters for the rule callable.
    fn signature(&self, rule_name: String) -> ParametersSpec<Value<'_>> {
        let mut signature = ParametersSpec::with_capacity(rule_name, self.len());
        signature.no_more_positional_args();
        for (name, _idx, attribute) in self.attr_specs() {
            match attribute.default() {
                Some(_) => signature.optional(name),
                None => signature.required(name),
            };
        }
        signature.finish()
    }

    fn starlark_types(&self) -> Vec<Ty> {
        self.attr_specs()
            .map(|(_, _, a)| a.starlark_type())
            .collect()
    }

    fn docstrings(&self) -> HashMap<String, Option<DocString>> {
        self.attr_specs()
            .map(|(name, _idx, attr)| (name.to_owned(), attr.docstring()))
            .collect()
    }
}


/*
 * 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 attr_spec;
pub mod unconfigured;


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

use std::sync::Arc;

use buck2_core::target::label::TargetLabel;
use buck2_core::target::name::TargetNameRef;
use buck2_node::attrs::coerced_deps_collector::CoercedDeps;
use buck2_node::attrs::coerced_deps_collector::CoercedDepsCollector;
use buck2_node::attrs::inspect_options::AttrInspectOptions;
use buck2_node::attrs::internal::NAME_ATTRIBUTE_FIELD;
use buck2_node::attrs::values::AttrValues;
use buck2_node::call_stack::StarlarkCallStack;
use buck2_node::nodes::unconfigured::TargetNode;
use buck2_node::package::Package;
use buck2_node::rule::Rule;
use dupe::Dupe;
use starlark::eval::CallStack;
use starlark::eval::ParametersParser;
use starlark::values::Value;

use crate::interpreter::module_internals::ModuleInternals;
use crate::nodes::attr_spec::AttributeSpecExt;

pub trait TargetNodeExt: Sized {
    fn from_params_ignore_attrs_for_profiling<'v>(
        rule: Arc<Rule>,
        package: Arc<Package>,
        internals: &ModuleInternals,
        param_parser: ParametersParser<'v, '_>,
    ) -> anyhow::Result<Self>;

    fn from_params<'v>(
        rule: Arc<Rule>,
        package: Arc<Package>,
        internals: &ModuleInternals,
        param_parser: ParametersParser<'v, '_>,
        arg_count: usize,
        ignore_attrs_for_profiling: bool,
        call_stack: Option<CallStack>,
    ) -> anyhow::Result<Self>;
}

impl TargetNodeExt for TargetNode {
    /// Extract only the name attribute from rule arguments, ignore the others.
    fn from_params_ignore_attrs_for_profiling<'v>(
        rule: Arc<Rule>,
        package: Arc<Package>,
        internals: &ModuleInternals,
        mut param_parser: ParametersParser<'v, '_>,
    ) -> anyhow::Result<Self> {
        for (attr_name, _attr_idx, _attr) in rule.attributes.attr_specs() {
            let value: Value = param_parser.next(attr_name)?;
            if attr_name == NAME_ATTRIBUTE_FIELD {
                let label = TargetLabel::new(
                    internals.buildfile_path().package().dupe(),
                    TargetNameRef::new(value.unpack_str().unwrap()).unwrap(),
                );
                return Ok(TargetNode::new(
                    rule.dupe(),
                    package,
                    label,
                    AttrValues::with_capacity(0),
                    CoercedDeps::default(),
                    None,
                ));
            }
        }
        unreachable!("`name` attribute not found");
    }

    /// The body of the callable returned by `rule()`. Records the target in this package's `TargetMap`
    #[allow(clippy::box_collection)] // Parameter `call_stack`, because this is the field type.
    fn from_params<'v>(
        rule: Arc<Rule>,
        package: Arc<Package>,
        internals: &ModuleInternals,
        param_parser: ParametersParser<'v, '_>,
        arg_count: usize,
        ignore_attrs_for_profiling: bool,
        call_stack: Option<CallStack>,
    ) -> anyhow::Result<Self> {
        if ignore_attrs_for_profiling {
            return Self::from_params_ignore_attrs_for_profiling(
                rule,
                package,
                internals,
                param_parser,
            );
        }

        let (target_name, attr_values) =
            rule.attributes
                .parse_params(param_parser, arg_count, internals)?;
        let package_name = internals.buildfile_path().package();

        let label = TargetLabel::new(package_name.dupe(), target_name.as_ref());
        let mut deps_cache = CoercedDepsCollector::new();

        for a in rule.attributes.attrs(&attr_values, AttrInspectOptions::All) {
            a.traverse(label.pkg(), &mut deps_cache)?;
        }

        Ok(TargetNode::new(
            rule,
            package,
            label,
            attr_values,
            CoercedDeps::from(deps_cache),
            call_stack.map(StarlarkCallStack::new),
        ))
    }
}


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

use std::sync::Arc;

use allocative::Allocative;
use buck2_node::visibility::VisibilitySpecification;
use buck2_node::visibility::WithinViewSpecification;
use dupe::Dupe;
use starlark::values::OwnedFrozenValue;
use starlark_map::small_map::SmallMap;

#[derive(Default, Debug, Allocative)]
pub(crate) struct SuperPackageData {
    package_values: SmallMap<String, OwnedFrozenValue>,
    visibility: VisibilitySpecification,
    within_view: WithinViewSpecification,
}

/// Contents of a `PACKAGE` file merged with contents of containing `PACKAGE` files.
/// This object exists even for non-existent `PACKAGE` files.
#[derive(Default, Debug, Allocative, Clone, Dupe)]
pub(crate) struct SuperPackage(Arc<SuperPackageData>);

impl SuperPackage {
    pub(crate) fn new(
        package_values: SmallMap<String, OwnedFrozenValue>,
        visibility: VisibilitySpecification,
        within_view: WithinViewSpecification,
    ) -> SuperPackage {
        SuperPackage(Arc::new(SuperPackageData {
            package_values,
            visibility,
            within_view,
        }))
    }

    pub(crate) fn package_values(&self) -> &SmallMap<String, OwnedFrozenValue> {
        &self.0.package_values
    }

    pub(crate) fn visibility(&self) -> &VisibilitySpecification {
        &self.0.visibility
    }

    pub(crate) fn within_view(&self) -> &WithinViewSpecification {
        &self.0.within_view
    }
}

impl PartialEq for SuperPackage {
    fn eq(&self, other: &Self) -> bool {
        let SuperPackageData {
            package_values: this_values,
            visibility: this_visibility,
            within_view: this_within_view,
        } = &*self.0;
        let SuperPackageData {
            package_values: other_values,
            visibility: other_visibility,
            within_view: other_within_view,
        } = &*other.0;
        (this_visibility, this_within_view) == (other_visibility, other_within_view) && {
            // If either package values are not empty, we cannot compare them
            // because we cannot reliably compare arbitrary Starlark values.
            // So if either package values are not empty, we consider super package not equal.
            this_values.is_empty() && other_values.is_empty()
        }
    }
}


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

use starlark::environment::GlobalsBuilder;

use crate::super_package::package::register_package_function;
use crate::super_package::package_value::register_write_package_value;

/// Globals for `PACKAGE` files and `bzl` files included from `PACKAGE` files.
pub fn register_package_natives(globals: &mut GlobalsBuilder) {
    register_package_function(globals);
    register_write_package_value(globals);
}


/*
 * 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::cell::RefCell;

use buck2_node::visibility::VisibilitySpecification;
use buck2_node::visibility::WithinViewSpecification;
use starlark::values::OwnedFrozenValue;
use starlark_map::small_map::SmallMap;

use crate::super_package::data::SuperPackage;

#[derive(Debug, Default)]
pub(crate) struct PackageFileVisibilityFields {
    pub(crate) visibility: VisibilitySpecification,
    pub(crate) within_view: WithinViewSpecification,
    pub(crate) inherit: bool,
}

#[derive(Debug)]
pub(crate) struct PackageFileEvalCtx {
    /// Parent file context.
    /// When evaluating root `PACKAGE` file, parent is still defined.
    pub(crate) parent: SuperPackage,
    pub(crate) visibility: RefCell<Option<PackageFileVisibilityFields>>,
}

impl PackageFileEvalCtx {
    pub(crate) fn build_super_package(
        self,
        package_values: SmallMap<String, OwnedFrozenValue>,
    ) -> SuperPackage {
        let mut merged_package_values = self.parent.package_values().clone();
        merged_package_values.extend(package_values);

        let PackageFileVisibilityFields {
            visibility,
            within_view,
            inherit,
        } = self.visibility.into_inner().unwrap_or_default();

        let (visibility, within_view) = if inherit {
            (
                self.parent.visibility().extend_with(&visibility),
                self.parent.within_view().extend_with(&within_view),
            )
        } else {
            (visibility, within_view)
        };

        SuperPackage::new(merged_package_values, visibility, within_view)
    }
}


/*
 * 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(crate) mod data;
pub mod defs;
pub(crate) mod eval_ctx;
pub(crate) mod package;
pub mod package_value;


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

use buck2_core::cells::name::CellName;
use buck2_core::cells::CellResolver;
use buck2_core::pattern::ParsedPattern;
use buck2_node::visibility::VisibilityPattern;
use buck2_node::visibility::VisibilitySpecification;
use buck2_node::visibility::VisibilityWithinViewBuilder;
use buck2_node::visibility::WithinViewSpecification;
use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::none::NoneType;

use crate::interpreter::build_context::BuildContext;
use crate::interpreter::build_context::PerFileTypeContext;
use crate::super_package::eval_ctx::PackageFileVisibilityFields;

#[derive(Debug, thiserror::Error)]
enum PackageFileError {
    #[error(
        "`package()` can only be called in `PACKAGE` files \
        or in `bzl` files included from `PACKAGE` files"
    )]
    NotPackage,
    #[error("`package()` function can be used at most once per `PACKAGE` file")]
    AtMostOnce,
}

fn parse_visibility(
    patterns: &[String],
    cell_name: CellName,
    cell_resolver: &CellResolver,
) -> anyhow::Result<VisibilitySpecification> {
    let mut builder = VisibilityWithinViewBuilder::with_capacity(patterns.len());
    for pattern in patterns {
        if pattern == VisibilityPattern::PUBLIC {
            builder.add_public();
        } else {
            builder.add(VisibilityPattern(ParsedPattern::parse_precise(
                pattern,
                cell_name,
                cell_resolver,
            )?));
        }
    }
    Ok(builder.build_visibility())
}

fn parse_within_view(
    patterns: &[String],
    cell_name: CellName,
    cell_resolver: &CellResolver,
) -> anyhow::Result<WithinViewSpecification> {
    let mut builder = VisibilityWithinViewBuilder::with_capacity(patterns.len());
    for pattern in patterns {
        if pattern == VisibilityPattern::PUBLIC {
            builder.add_public();
        } else {
            builder.add(VisibilityPattern(ParsedPattern::parse_precise(
                pattern,
                cell_name,
                cell_resolver,
            )?));
        }
    }
    Ok(builder.build_within_view())
}

/// Globals for `PACKAGE` files and `bzl` files included from `PACKAGE` files.
#[starlark_module]
pub(crate) fn register_package_function(globals: &mut GlobalsBuilder) {
    fn package(
        #[starlark(require=named, default=false)] inherit: bool,
        #[starlark(require=named, default=Vec::new())] visibility: Vec<String>,
        #[starlark(require=named, default=Vec::new())] within_view: Vec<String>,
        eval: &mut Evaluator,
    ) -> anyhow::Result<NoneType> {
        let build_context = BuildContext::from_context(eval)?;
        let package_file_eval_ctx = match &build_context.additional {
            PerFileTypeContext::Package(_, package_file_eval_ctx) => package_file_eval_ctx,
            _ => return Err(PackageFileError::NotPackage.into()),
        };
        let visibility = parse_visibility(
            &visibility,
            build_context.cell_info().name().name(),
            build_context.cell_info().cell_resolver(),
        )?;
        let within_view = parse_within_view(
            &within_view,
            build_context.cell_info().name().name(),
            build_context.cell_info().cell_resolver(),
        )?;

        match &mut *package_file_eval_ctx.visibility.borrow_mut() {
            Some(_) => return Err(PackageFileError::AtMostOnce.into()),
            x => {
                *x = Some(PackageFileVisibilityFields {
                    visibility,
                    within_view,
                    inherit,
                })
            }
        };

        Ok(NoneType)
    }
}


/*
 * 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::cell::RefCell;

use allocative::Allocative;
use anyhow::Context;
use starlark::any::ProvidesStaticType;
use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::none::NoneType;
use starlark::values::starlark_value;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::Trace;
use starlark::values::Value;
use starlark::values::ValueLike;
use starlark_map::small_map::SmallMap;

use crate::interpreter::build_context::BuildContext;
use crate::interpreter::module_internals::ModuleInternals;

#[derive(Debug, thiserror::Error)]
enum PackageValueError {
    #[error("key already set in this file: `{0}`")]
    KeyAlreadySetInThisFile(String),
    #[error("key set in parent `PACKAGE` file, and overwrite flag is not set: `{0}`")]
    KeySetInParentFile(String),
    #[error("key must contain exactly one dot: `{0}`")]
    KeyMustContainExactlyOneDot(String),
}

fn validate_key(key: &str) -> anyhow::Result<()> {
    if key.chars().filter(|c| *c == '.').count() != 1 {
        return Err(PackageValueError::KeyMustContainExactlyOneDot(key.to_owned()).into());
    }
    Ok(())
}

#[derive(
    Default,
    Debug,
    derive_more::Display,
    Allocative,
    ProvidesStaticType,
    Trace,
    NoSerialize
)]
#[display(fmt = "{:?}", self)]
pub(crate) struct PackageValues<'v> {
    pub(crate) values: RefCell<SmallMap<String, Value<'v>>>,
}

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

#[starlark_module]
pub(crate) fn register_write_package_value(globals: &mut GlobalsBuilder) {
    /// Set the value to be accessible in the nested `PACKAGE` files.
    fn write_package_value<'v>(
        #[starlark(require = pos)] key: &str,
        #[starlark(require = pos)] value: Value<'v>,
        #[starlark(require = named, default = false)] overwrite: bool,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<NoneType> {
        validate_key(key)?;

        let package_ctx = BuildContext::from_context(eval)?
            .additional
            .require_package_file("write_package_value")?;

        let extra_value = eval
            .module()
            .extra_value()
            .context("Module extra value was not set (internal error)")?;
        let package_values = extra_value
            .downcast_ref::<PackageValues>()
            .context("Module extra value was not a `PackageValues` (internal error)")?;

        let mut package_values = package_values.values.borrow_mut();

        if package_values.contains_key(key) {
            return Err(PackageValueError::KeyAlreadySetInThisFile(key.to_owned()).into());
        }

        if !overwrite {
            if package_ctx.parent.package_values().contains_key(key) {
                return Err(PackageValueError::KeySetInParentFile(key.to_owned()).into());
            }
        }

        package_values.insert(key.to_owned(), value);

        Ok(NoneType)
    }
}

#[starlark_module]
pub fn register_read_package_value(globals: &mut GlobalsBuilder) {
    /// Read value specified in the `PACKAGE` file.
    ///
    /// Returns `None` if value is not set.
    fn read_package_value<'v>(
        #[starlark(require = pos)] key: &str,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        validate_key(key)?;

        let build_ctx = ModuleInternals::from_context(eval, "read_package_value")?;
        match build_ctx.super_package.package_values().get(key) {
            Some(value) => Ok(value.owned_value(eval.frozen_heap())),
            None => Ok(Value::new_none()),
        }
    }
}


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

use buck2_common::package_listing::listing::testing::PackageListingExt;
use buck2_common::package_listing::listing::PackageListing;
use buck2_common::result::SharedResult;
use buck2_core::cells::name::CellName;
use buck2_core::cells::paths::CellRelativePath;
use buck2_core::package::package_relative_path::PackageRelativePathBuf;
use buck2_core::package::PackageLabel;
use buck2_interpreter_for_build::attrs::attrs_global::register_attrs;
use buck2_interpreter_for_build::attrs::coerce::attr_type::AttrTypeExt;
use buck2_interpreter_for_build::attrs::coerce::ctx::BuildAttrCoercionContext;
use buck2_interpreter_for_build::interpreter::testing::cells;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use buck2_node::attrs::attr_type::AttrType;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use buck2_node::attrs::hacks::value_to_string;
use buck2_node::provider_id_set::ProviderIdSet;
use dupe::Dupe;
use indoc::indoc;
use starlark::values::Heap;

fn tester() -> Tester {
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_attrs);
    tester
}

#[test]
fn string_works() -> SharedResult<()> {
    let mut tester = tester();
    tester.run_starlark_bzl_test(indoc!(
        r#"
        frozen = attrs.string(default="something", doc = "foo")
        def test():
            assert_eq('attrs.string(default="something")', repr(attrs.string(default="something", doc = "foo")))
            assert_eq('attrs.string(default="something")', repr(frozen))
        "#
    ))
}

#[test]
fn boolean_works() -> SharedResult<()> {
    let mut tester = tester();
    tester.run_starlark_bzl_test(indoc!(
        r#"
        frozen = attrs.bool(default=False)
        def test():
            assert_eq('attrs.bool(default=True)', repr(attrs.bool(default=True, doc = "foo")))
            assert_eq('attrs.bool(default=False)', repr(frozen))
        "#
    ))
}

#[test]
fn test_attr_module_registered() -> SharedResult<()> {
    let mut tester = tester();
    tester.run_starlark_bzl_test(indoc!(
        r#"
        def test():
            assert_eq(True, attrs.string != None)
        "#
    ))
}

#[test]
fn list_works() -> SharedResult<()> {
    let mut tester = tester();
    tester.run_starlark_bzl_test(indoc!(
        r#"
        frozen = attrs.list(
            attrs.string(default = "something", doc = "foo"),
            default=["1", "2"],
            doc = "foo",
        )
        def test():
            not_frozen = attrs.list(
                attrs.string(default = "something", doc = "foo"),
                default=[],
                doc = "foo",
            )

            assert_eq('attrs.list(attrs.string(), default=[])', repr(not_frozen))
            assert_eq('attrs.list(attrs.string(), default=["1","2"])', repr(frozen))
        "#
    ))
}

#[test]
fn enum_works() -> SharedResult<()> {
    let mut tester = tester();
    tester.run_starlark_bzl_test(indoc!(
        r#"
        frozen = attrs.enum(["red", "green", "blue"])
        def test():
            not_frozen = attrs.enum(["yes", "no"], default="no")
            assert_eq('attrs.enum(["red","green","blue"])', repr(frozen))
            assert_eq('attrs.enum(["yes","no"], default="no")', repr(not_frozen))
        "#
    ))
}

#[test]
fn attr_coercer_coerces() -> anyhow::Result<()> {
    let heap = Heap::new();
    let some_cells = cells(None)?;
    let cell_resolver = some_cells.1;
    let package = PackageLabel::new(
        CellName::testing_new("root"),
        CellRelativePath::unchecked_new("foo"),
    );
    let enclosing_package = (package.dupe(), PackageListing::testing_empty());
    let coercer_ctx =
        BuildAttrCoercionContext::new_with_package(cell_resolver, enclosing_package, false);
    let label_coercer = AttrType::dep(ProviderIdSet::EMPTY);
    let string_coercer = AttrType::string();
    let enum_coercer = AttrType::enumeration(vec![
        "red".to_owned(),
        "green".to_owned(),
        "blue".to_owned(),
    ])?;
    assert!(AttrType::enumeration(vec!["UPPER".to_owned()]).is_err());
    assert!(
        AttrType::enumeration(vec![
            "repeated".to_owned(),
            "and".to_owned(),
            "repeated".to_owned()
        ])
        .is_err()
    );

    let label_value1 = label_coercer.coerce(
        AttrIsConfigurable::Yes,
        &coercer_ctx,
        heap.alloc("root//foo:bar"),
    )?;
    let label_value2 = label_coercer.coerce(
        AttrIsConfigurable::Yes,
        &coercer_ctx,
        heap.alloc("root//foo:bar[baz]"),
    )?;
    let label_value3 =
        label_coercer.coerce(AttrIsConfigurable::Yes, &coercer_ctx, heap.alloc(":bar"))?;
    let label_value4 = label_coercer.coerce(
        AttrIsConfigurable::Yes,
        &coercer_ctx,
        heap.alloc(":bar[baz]"),
    )?;
    let invalid_label_value1 = label_coercer.coerce(
        AttrIsConfigurable::Yes,
        &coercer_ctx,
        heap.alloc("root//foo/..."),
    );
    let invalid_label_value2 = label_coercer.coerce(
        AttrIsConfigurable::Yes,
        &coercer_ctx,
        heap.alloc("root//foo:"),
    );
    let invalid_label_value3 =
        label_coercer.coerce(AttrIsConfigurable::Yes, &coercer_ctx, heap.alloc("1"));

    assert_eq!(
        "root//foo:bar",
        value_to_string(&label_value1, package.dupe())?
    );
    assert_eq!(
        "root//foo:bar[baz]",
        value_to_string(&label_value2, package.dupe())?
    );
    assert_eq!(
        "root//foo:bar",
        value_to_string(&label_value3, package.dupe())?
    );
    assert_eq!(
        "root//foo:bar[baz]",
        value_to_string(&label_value4, package.dupe())?
    );
    assert!(invalid_label_value1.is_err());
    assert!(invalid_label_value2.is_err());
    assert!(invalid_label_value3.is_err());

    let string_value1 =
        string_coercer.coerce(AttrIsConfigurable::Yes, &coercer_ctx, heap.alloc("str"))?;
    assert_eq!("str", value_to_string(&string_value1, package.dupe())?);

    let enum_valid1 =
        enum_coercer.coerce(AttrIsConfigurable::Yes, &coercer_ctx, heap.alloc("red"))?;
    let enum_valid2 =
        enum_coercer.coerce(AttrIsConfigurable::Yes, &coercer_ctx, heap.alloc("green"))?;
    let enum_valid3 =
        enum_coercer.coerce(AttrIsConfigurable::Yes, &coercer_ctx, heap.alloc("RED"))?;
    let enum_invalid1 =
        enum_coercer.coerce(AttrIsConfigurable::Yes, &coercer_ctx, heap.alloc("orange"));
    let enum_invalid2 =
        enum_coercer.coerce(AttrIsConfigurable::Yes, &coercer_ctx, heap.alloc(false));
    assert_eq!("red", value_to_string(&enum_valid1, package.dupe())?);
    assert_eq!("green", value_to_string(&enum_valid2, package.dupe())?);
    assert_eq!("red", value_to_string(&enum_valid3, package.dupe())?);
    assert!(enum_invalid1.is_err());
    assert!(enum_invalid2.is_err());

    Ok(())
}

#[test]
fn dep_works() -> SharedResult<()> {
    let mut t = tester();
    t.run_starlark_bzl_test(indoc!(
        r#"
        frozen1 = attrs.dep(default="root//foo:bar")
        frozen2 = attrs.dep(default="//foo:bar")
        def test():
            assert_eq('attrs.dep(default="root//foo:bar")', repr(attrs.dep(default="//foo:bar")))
            assert_eq('attrs.dep(default="root//foo:bar")', repr(frozen1))
            assert_eq('attrs.dep(default="root//foo:bar")', repr(frozen2))
        "#
    ))?;

    let mut t = tester();
    t.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            attrs.dep(default="notatarget")
        "#
        ),
        "Invalid absolute target",
    );

    // Relative targets are disallowed; there is no build file for them to be relative to
    let mut t = tester();
    t.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            attrs.dep(default=":reltarget")
        "#
        ),
        "Use a fully qualified",
    );
    Ok(())
}

#[test]
fn source_works() -> SharedResult<()> {
    let mut t = tester();
    t.run_starlark_bzl_test(indoc!(
        r#"
        frozen1 = attrs.source(default="root//foo:bar")
        frozen2 = attrs.source(default="//foo:bar")
        def test():
            assert_eq('attrs.source(default="root//foo:bar")', repr(attrs.source(default="root//foo:bar")))
            assert_eq('attrs.source(default="root//foo:bar")', repr(frozen1))
            assert_eq('attrs.source(default="root//foo:bar")', repr(frozen2))
        "#
    ))?;

    // Relative targets are disallowed; there is no build file for them to be relative to
    let mut t = tester();
    t.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            attrs.source(default=":reltarget")
        "#
        ),
        "Use a fully qualified",
    );
    Ok(())
}

#[test]
fn coercing_src_to_path_works() -> anyhow::Result<()> {
    let cell_resolver = cells(None).unwrap().1;
    let package = PackageLabel::new(
        CellName::testing_new("root"),
        CellRelativePath::unchecked_new("foo/bar"),
    );
    let package_ctx = BuildAttrCoercionContext::new_with_package(
        cell_resolver.dupe(),
        (
            package.dupe(),
            PackageListing::testing_files(&["baz/quz.cpp"]),
        ),
        false,
    );
    let no_package_ctx =
        BuildAttrCoercionContext::new_no_package(cell_resolver, CellName::testing_new("root"));

    let err = no_package_ctx
        .coerce_path("baz/quz.cpp", false)
        .unwrap_err();
    assert!(err.to_string().contains("Expected a package"));

    let err = package_ctx
        .coerce_path("/invalid/absolute/path", false)
        .unwrap_err();
    assert!(format!("{:#}", err).contains("absolute path"), "{:?}", err);

    let err = package_ctx
        .coerce_path("../upward/traversal", false)
        .unwrap_err();
    assert!(err.to_string().contains("normalized path"));

    let expected = PackageRelativePathBuf::unchecked_new("baz/quz.cpp".to_owned());
    assert_eq!(
        expected.as_path(),
        &**package_ctx
            .coerce_path("baz/quz.cpp", false)
            .unwrap()
            .path()
    );
    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 buck2_build_api::interpreter::rule_defs::provider::registration::register_builtin_providers;
use buck2_build_api::interpreter::rule_defs::register_rule_defs;
use buck2_common::legacy_configs::cells::BuckConfigBasedCells;
use buck2_common::legacy_configs::testing::TestConfigParserFileOps;
use buck2_common::package_listing::listing::testing::PackageListingExt;
use buck2_common::package_listing::listing::PackageListing;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::bzl::ImportPath;
use buck2_core::cells::name::CellName;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::project::ProjectRoot;
use buck2_core::fs::project_rel_path::ProjectRelativePath;
use buck2_interpreter::file_loader::LoadedModules;
use buck2_interpreter::path::OwnedStarlarkModulePath;
use buck2_interpreter::path::StarlarkPath;
use buck2_interpreter_for_build::attrs::attrs_global::register_attrs;
use buck2_interpreter_for_build::interpreter::natives::register_module_natives;
use buck2_interpreter_for_build::interpreter::testing::run_simple_starlark_test;
use buck2_interpreter_for_build::interpreter::testing::CellsData;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use buck2_interpreter_for_build::rule::register_rule_function;
use buck2_node::attrs::inspect_options::AttrInspectOptions;
use buck2_node::nodes::unconfigured::testing::targets_to_json;
use dupe::Dupe;
use gazebo::prelude::SliceExt;
use indoc::indoc;
use serde_json::json;

#[test]
fn test_eval_import() {
    let loaded = Tester::new()
        .unwrap()
        .add_import(
            &ImportPath::testing_new("root//some/package:defs.bzl"),
            indoc!(
                r#"
            one = 1
            hello = "world"
            "#
            ),
        )
        .unwrap();

    assert_eq!(1, loaded.env().get("one").unwrap().unpack_int().unwrap());

    assert_eq!(
        "world",
        loaded.env().get("hello").unwrap().unpack_str().unwrap()
    );
}

#[test]
fn test_load() {
    let import_path = ImportPath::testing_new("root//imports:one.bzl");
    let mut tester = Tester::new().unwrap();
    tester
        .add_import(
            &import_path,
            indoc!(
                r#"
                    def concat(*args):
                      s = ""
                      for a in args:
                        s += a
                      return s
                    "#
            ),
        )
        .unwrap();

    let parse_result = tester
        .add_import(
            &ImportPath::testing_new("root//some/package:defs.bzl"),
            indoc!(
                r#"
                load("@root//imports:one.bzl", "concat")
                message = concat("hello", " ", "world!")
                "#
            ),
        )
        .unwrap();

    assert_eq!(
        "hello world!",
        parse_result
            .env()
            .get("message")
            .unwrap()
            .unpack_str()
            .unwrap()
    );
}

#[test]
fn test_eval_build_file() {
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_rule_function);
    tester.additional_globals(register_attrs);
    tester.additional_globals(register_builtin_providers);

    tester
        .add_import(
            &ImportPath::testing_new("root//:rules.bzl"),
            indoc!(
                r#"
                def _impl(ctx):
                    return DefaultInfo()

                export_file = rule(
                    impl = _impl,
                    attrs = {
                        "src": attrs.any(),
                    },
                )

                java_library = rule(
                    impl = _impl,
                    attrs = {
                        "srcs": attrs.list(attrs.any()),
                    },
                )
            "#
            ),
        )
        .unwrap();

    tester
        .add_import(
            &ImportPath::testing_new_cross_cell("root", "imports", "one.bzl", "root"),
            indoc!(
                r#"
                    load("@root//:rules.bzl", "export_file")

                    def some_macro(name, **kwargs):
                        export_file(
                            name=name+"-exported",
                            **kwargs
                        )
                    "#
            ),
        )
        .unwrap();

    let build_path = BuildFilePath::testing_new("root//some/package:BUILD");
    let eval_result = tester
        .eval_build_file(
            &build_path,
            indoc!(
                r#"
                load("@root//imports:one.bzl", "some_macro")
                load("@root//:rules.bzl", "java_library")

                some_macro(
                    name = "invoke_some",
                    src = "some.file",
                )
                java_library(
                    name = "java",
                    srcs = glob(["**/*.java"]),
                )
                "#
            ),
            PackageListing::testing_files(&["file1.java", "file2.java"]),
        )
        .unwrap();

    assert_eq!(build_path.package(), eval_result.package());
    let target_names = eval_result
        .targets()
        .keys()
        .map(|t| t.as_str().to_owned())
        .collect::<Vec<_>>();
    assert_eq!(vec!["invoke_some-exported", "java"], target_names);
}

fn cells() -> CellsData {
    let repo_root = if cfg!(windows) { "C:/" } else { "/" };
    let project_fs =
        ProjectRoot::new_unchecked(AbsNormPathBuf::try_from(repo_root.to_owned()).unwrap());
    let BuckConfigBasedCells {
        cell_resolver,
        configs_by_name,
        config_paths: _,
    } = BuckConfigBasedCells::parse_with_file_ops(
        &project_fs,
        &mut TestConfigParserFileOps::new(&[(
            "/.buckconfig",
            indoc!(
                r#"
                    [repositories]
                        root = .
                        cell1 = project/cell1
                        cell2 = project/cell2
                        xalias2 = project/cell2
                    "#
            ),
        )])
        .unwrap(),
        &[],
        ProjectRelativePath::empty(),
    )
    .unwrap();
    (
        cell_resolver
            .get(CellName::testing_new("root"))
            .unwrap()
            .cell_alias_resolver()
            .dupe(),
        cell_resolver,
        configs_by_name,
    )
}

#[test]
fn test_find_imports() {
    let tester = Tester::with_cells(cells()).unwrap();
    let path = BuildFilePath::testing_new("cell1//config:BUCK");
    let parse_result = tester.parse(
        StarlarkPath::BuildFile(&path),
        indoc!(
            r#"
            a = 1
        "#
        ),
    );

    assert!(parse_result.imports().is_empty());

    let parse_result = tester.parse(
        StarlarkPath::BuildFile(&path),
        indoc!(
            r#"
            # some documentation
            """ and a string """

            load("//imports:one.bzl", "some_macro")
            load("@cell1//:one.bzl", "some_macro")
            load("@xalias2//:two.bzl", "some_macro")

            # some other comments
            load(":other.bzl", "some_macro")
        "#
        ),
    );

    assert_eq!(
        &[
            "root//imports/one.bzl@cell1",
            "cell1//one.bzl",
            "cell2//two.bzl@cell1",
            "cell1//config/other.bzl"
        ],
        parse_result.imports().map(|e| e.1.to_string()).as_slice()
    );
}

#[test]
fn test_root_import() {
    let mut tester = Tester::with_cells(
        buck2_interpreter_for_build::interpreter::testing::cells(Some(indoc!(
            r#"
            [buildfile]
                includes = //include.bzl
        "#
        )))
        .unwrap(),
    )
    .unwrap();

    tester.additional_globals(register_builtin_providers);
    tester.additional_globals(register_rule_function);
    tester.additional_globals(register_attrs);

    let import_path = ImportPath::testing_new("root//:include.bzl");
    tester
        .add_import(
            &import_path,
            indoc!(
                r#"
                    some_var = 1
                    def some_func():
                       return "hello"

                    def _impl(ctx):
                        return DefaultInfo()

                    export_file = rule(
                        impl = _impl,
                        attrs = {
                            "level": attrs.int(),
                        },
                    )
        "#
            ),
        )
        .unwrap();

    let build_path = BuildFilePath::testing_new("root//some/package:BUCK");
    let eval_result = tester
        .eval_build_file(
            &build_path,
            indoc!(
                r#"
                export_file(
                    name = some_func(),
                    level = some_var,
                )
                "#
            ),
            PackageListing::testing_files(&["file1.java", "file2.java"]),
        )
        .unwrap();

    assert_eq!(build_path.package(), eval_result.package());
    let target_names = eval_result
        .targets()
        .keys()
        .map(|t| t.as_str().to_owned())
        .collect::<Vec<_>>();
    assert_eq!(vec!["hello"], target_names);
}

#[test]
fn prelude_is_included() -> anyhow::Result<()> {
    let mut tester = Tester::new()?;
    let prelude_path = ImportPath::testing_new("root//prelude:prelude.bzl");
    tester.set_prelude(prelude_path.clone());

    let prelude = tester.eval_import(&prelude_path, "some_var = 1", LoadedModules::default())?;
    let mut loaded_modules = LoadedModules::default();
    loaded_modules
        .map
        .insert(OwnedStarlarkModulePath::LoadFile(prelude_path), prelude);

    // The prelude should be included in build files, and in .bzl files that are not in the
    // prelude's package
    let build_file = BuildFilePath::testing_new("root//prelude:TARGETS.v2");
    assert!(
        tester
            .eval_build_file_with_loaded_modules(
                &build_file,
                "other_var = some_var",
                loaded_modules.clone(),
                PackageListing::testing_empty()
            )
            .is_ok(),
        "build files in the prelude package should have access to the prelude"
    );

    let import = ImportPath::testing_new("root//not_prelude:sibling.bzl");
    assert!(
        tester
            .eval_import(&import, "other_var = some_var", loaded_modules.clone())
            .is_ok(),
        ".bzl files not in the prelude package should have access to the prelude"
    );

    let import = ImportPath::testing_new("root//prelude:defs.bzl");
    assert!(
        tester
            .eval_import(&import, "other_var = some_var", loaded_modules)
            .is_err(),
        "bzl files in the prelude package should NOT have access to the prelude"
    );

    Ok(())
}

#[test]
fn test_package_import() -> anyhow::Result<()> {
    let mut tester = Tester::with_cells(buck2_interpreter_for_build::interpreter::testing::cells(
        Some(indoc!(
            r#"
            [buildfile]
                package_includes = src=>//include.bzl::func_alias=some_func
        "#
        )),
    )?)?;
    tester.additional_globals(register_rule_function);
    tester.additional_globals(register_module_natives);

    let import_path = ImportPath::testing_new("root//:include.bzl");
    tester.add_import(
        &import_path,
        indoc!(
            r#"
            def _impl(ctx):
                pass
            export_file = rule(impl=_impl, attrs = {})

            def some_func(name):
                export_file(name = name)
        "#
        ),
    )?;

    let build_path = BuildFilePath::testing_new("root//src/package:BUCK");
    let eval_result = tester.eval_build_file(
        &build_path,
        indoc!(
            r#"
                implicit_package_symbol("func_alias")(
                    implicit_package_symbol("missing", "DEFAULT")
                )
                "#
        ),
        PackageListing::testing_files(&["file1.java", "file2.java"]),
    )?;
    assert_eq!(build_path.package(), eval_result.package());
    assert_eq!(
        json!({
                "DEFAULT": {
                    "__type__": "root//include.bzl:export_file",
                    "compatible_with": [],
                    "default_target_platform": null,
                    "exec_compatible_with": [],
                    "name": "DEFAULT",
                    "target_compatible_with": [],
                    "tests": [],
                    "visibility": [],
                    "within_view": ["PUBLIC"],
                },
        }),
        targets_to_json(
            eval_result.targets(),
            build_path.package(),
            AttrInspectOptions::All
        )?
    );
    Ok(())
}

#[test]
fn eval() -> anyhow::Result<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(register_module_natives);
    tester.additional_globals(register_rule_function);
    let content = indoc!(
        r#"
            def _impl(ctx):
                pass
            export_file = rule(impl=_impl, attrs = {})

            def test():
                assert_eq("some/package", __internal__.package_name())
                assert_eq("@root", __internal__.repository_name())

                assert_eq(package_name(), __internal__.package_name())
                assert_eq(repository_name(), __internal__.repository_name())

                assert_eq(package_name(), get_base_path())

                export_file(name = "rule_name")
                assert_eq(True, rule_exists("rule_name"))
                assert_eq(False, rule_exists("not_rule_name"))

                print("some message")
                print("multiple", "strings")
            "#
    );
    tester.run_starlark_test(content)?;
    Ok(())
}

#[test]
fn test_internal() -> anyhow::Result<()> {
    // Test that most things end up on __internal__
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_rule_defs);
    run_simple_starlark_test(indoc!(
        r#"
            def test():
                assert_eq(__internal__.json.encode({}), "{}")
            "#
    ))
}

#[test]
fn test_oncall() -> anyhow::Result<()> {
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_module_natives);
    tester.additional_globals(register_rule_defs);
    tester.additional_globals(register_rule_function);
    tester.run_starlark_test(indoc!(
        r#"
            def _impl(ctx):
                pass
            export_file = rule(impl=_impl, attrs = {})

            def test():
                oncall("valid")
                export_file(name = "rule_name")
            "#
    ))?;
    tester.run_starlark_test_expecting_error(
        indoc!(
            r#"
            def test():
                oncall("valid")
                oncall("twice")
            "#
        ),
        "more than once",
    );
    tester.run_starlark_test_expecting_error(
        indoc!(
            r#"
            def _impl(ctx):
                pass
            export_file = rule(impl=_impl, attrs = {})

            def test():
                export_file(name = "rule_name")
                oncall("failure after")
            "#
        ),
        "after one or more targets",
    );
    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.
 */

#![cfg(test)]

#[cfg(test)]
mod attr;
mod functions;
pub mod interpreter;
mod rule;
pub mod select;
mod super_package;
mod tests;
mod uncategorized;
mod uncategorized_2;

#[test]
fn init_late_bindings_for_test() {
    #[ctor::ctor]
    fn init() {
        buck2_interpreter_for_build::init_late_bindings();
        buck2_build_api::init_late_bindings();
    }
}


/*
 * 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_build_api::interpreter::build_defs::register_transitive_set;
use buck2_common::result::SharedResult;
use buck2_core::bzl::ImportPath;
use buck2_interpreter::file_loader::LoadedModules;
use buck2_interpreter_for_build::attrs::attrs_global::register_attrs;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use buck2_interpreter_for_build::nodes::attr_spec::AttributeSpecExt;
use buck2_interpreter_for_build::rule::register_rule_function;
use buck2_node::attrs::inspect_options::AttrInspectOptions;
use buck2_node::attrs::spec::AttributeSpec;
use buck2_node::nodes::unconfigured::testing::targets_to_json;
use indoc::indoc;
use serde_json::json;
use starlark::docs::DocFunction;
use starlark::docs::DocItem;
use starlark::docs::DocParam;
use starlark::docs::DocReturn;
use starlark::docs::DocString;
use starlark::docs::DocStringKind;
use starlark::docs::DocType;
use starlark::typing::Ty;

fn rule_tester() -> Tester {
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_transitive_set);
    tester.additional_globals(register_rule_function);
    tester.additional_globals(register_attrs);
    tester
}

#[test]
fn rule_creates_callable() -> anyhow::Result<()> {
    let mut tester = rule_tester();
    tester.run_starlark_test(indoc!(
        r#"
        def impl(ctx):
            pass

        string_attr = attrs.string(default="something", doc = "foo")
        frozen_rule = rule(
            impl=impl,
            attrs={
                "param1": string_attr,
                "param2": attrs.list(string_attr, default=[], doc = "foo"),
            }
        )

        def test():
            assert_eq(None, frozen_rule(name="target_name"))
            assert_eq("frozen_rule()", repr(frozen_rule))
        "#
    ))?;
    Ok(())
}

#[test]
fn freeze_fails_if_not_assigned() {
    let mut tester = rule_tester();
    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def impl(ctx):
            pass

        def test():
            string_attr = attrs.string(default="something", doc = "foo")
            nonfrozen_rule = rule(
                impl=impl,
                attrs={
                    "param1": string_attr,
                }
            )
            nonfrozen_rule(name="target_name")
        "#
        ),
        "may only be called after the module is loaded",
    );
}

#[test]
fn rule_disallows_reserved_names() {
    let mut tester = rule_tester();
    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def impl(ctx):
            pass

        string_attr = attrs.string(default="something", doc = "foo")
        frozen_rule = rule(
            impl=impl,
            attrs={
                "name": string_attr,
                "param1": string_attr,
            }
        )
        def test():
            frozen_rule(name="target_name")
        "#
        ),
        "not a valid attribute",
    );
}

#[test]
fn rule_unbound() {
    let mut tester = rule_tester();
    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def impl(ctx):
            pass
        rules = []
        rules.append(rule(
            impl=impl,
            attrs={}
        ))
        def test():
            pass
        "#
        ),
        "must be assigned to a variable",
    );
}

#[test]
fn udr_is_recorded() -> SharedResult<()> {
    let content = indoc!(
        r#"
        def impl(ctx):
            pass

        string_attr = attrs.string(default="some_default", doc="")
        mandatory_string_attr = attrs.string(default="", doc="")
        dep_attr = attrs.dep(default="//foo:baz")
        src_attr = attrs.source(default="//foo:baz")
        foo_binary = rule(
            impl=impl,
            attrs={
                "optional": string_attr,
                "other_optional": string_attr,
                "mandatory": mandatory_string_attr,
                "dep": dep_attr,
                "src": src_attr,
            },
        )

        def test():
            foo_binary(name="target1", mandatory="m1", optional="o1", dep=":bar", other_optional=None, src="file1.java")
            foo_binary(name="target2", mandatory="m2", other_optional="o1")
        "#
    );
    let mut tester = rule_tester();
    let result = tester.run_starlark_test(content)?;

    let expected = json!({
        "target1": {
            "name": "target1",
            "__type__": "root//some/package/defs.bzl:foo_binary",
            "compatible_with": [],
            "default_target_platform": null,
            "mandatory": "m1",
            "optional": "o1",
            "other_optional": "some_default",
            "dep": "root//some/package:bar",
            "exec_compatible_with": [],
            "src": "root//some/package/file1.java",
            "target_compatible_with": [],
            "tests": [],
            "visibility": [],
            "within_view": ["PUBLIC"],
        },
        "target2": {
            "name": "target2",
            "__type__": "root//some/package/defs.bzl:foo_binary",
            "compatible_with": [],
            "default_target_platform": null,
            "mandatory": "m2",
            "optional": "some_default",
            "other_optional": "o1",
            "dep": "root//foo:baz",
            "exec_compatible_with": [],
            "src": "root//foo:baz",
            "target_compatible_with": [],
            "tests": [],
            "visibility": [],
            "within_view": ["PUBLIC"],
        },
    });
    let actual = targets_to_json(
        &result,
        Tester::build_file_path().package(),
        AttrInspectOptions::All,
    )?;
    assert_eq!(expected, actual, "`{:#?}` != `{:#?}`", expected, actual);
    Ok(())
}

#[test]
fn udr_rejects_invalid_parameters() {
    let prefix = indoc!(
        r#"
        def impl(ctx):
            pass

        string_attr = attrs.string(default="some_default")
        mandatory_string_attr = attrs.string(doc="")
        foo_binary = rule(
            impl=impl,
            attrs={"optional":string_attr, "mandatory": mandatory_string_attr},
        )

        def test():
        "#
    );

    let missing_name = r#"    foo_binary(mandatory="s")"#;
    let invalid_name = r#"    foo_binary(name="bad name", mandatory="s")"#;
    let missing_mandatory = r#"    foo_binary(name="t1")"#;
    let wrong_type = r#"    foo_binary(name="t1", mandatory=1)"#;
    let unknown_param = r#"    foo_binary(name="t1", mandatory="m1", unknown="")"#;
    let duplicate_targets = format!(
        "    {}\n    {}",
        r#"foo_binary(name="t1", mandatory="m1")"#, r#"foo_binary(name="t1", mandatory="m2")"#,
    );

    let run = |content: &str, msg: &str| {
        let mut tester = rule_tester();
        tester.run_starlark_test_expecting_error(&format!("{}\n{}", prefix, content), msg);
    };

    run(
        missing_name,
        "Missing parameter `name` for call to foo_binary",
    );
    run(invalid_name, "Invalid target name `bad name`.");
    run(missing_mandatory, "Missing parameter `mandatory`");
    run(wrong_type, "coercing attribute `mandatory`");
    run(unknown_param, "Found `unknown` extra named parameter");
    run(
        &duplicate_targets,
        "Attempted to register target root//some/package:t1 twice",
    );
}

#[test]
fn option_allows_none() -> anyhow::Result<()> {
    let mut tester = rule_tester();
    tester.run_starlark_test_expecting_error(
        "def test():\n attrs.option(attrs.string(), default = 'test')",
        "parameter must be `None` or absent",
    );
    let mut tester = rule_tester();
    tester.run_starlark_test("def test():\n attrs.option(attrs.string(), default = None)")?;
    let mut tester = rule_tester();
    tester.run_starlark_test("def test():\n attrs.option(attrs.string())")?;
    let mut tester = rule_tester();
    tester.run_starlark_test_expecting_error(
        "def test():\n attrs.option(attrs.string(), default = select({'DEFAULT': 'test'}))",
        "parameter must be `None` or absent",
    );
    let mut tester = rule_tester();
    tester.run_starlark_test(
        "def test():\n attrs.option(attrs.string(), default = select({'DEFAULT': None}))",
    )?;
    Ok(())
}

#[test]
fn returns_documentation() -> anyhow::Result<()> {
    let bzl = indoc::indoc!(
        r#"def impl(ctx):
            pass

        any_attr = attrs.any(doc="any docs")
        arg_attr = attrs.arg(default="arg", doc="arg docs")
        bool_attr = attrs.bool(default=True, doc="bool docs")
        string_attr = attrs.string(default="string", doc="string docs")
        default_only_attr = attrs.default_only(attrs.string(default="default_only"), doc="default_only docs")
        dep_attr = attrs.dep(default="//:dep", doc="dep docs")
        dict_attr = attrs.dict(attrs.string(), attrs.bool(), default={"dict": True}, doc="dict docs")
        list_attr = attrs.list(attrs.string(), default=["list"], doc="list docs")
        one_of_attr = attrs.one_of(attrs.bool(), attrs.string(), default="", doc="one_of docs")
        option_attr = attrs.option(attrs.string(), default=None, doc="option docs")
        query_attr = attrs.query(doc="query docs")
        source_attr = attrs.source(default="//:src", doc="source docs")
        tuple_attr = attrs.tuple(attrs.bool(), attrs.string(), default=(True, "some string"), doc="tuple docs")

        doc = """Summary for foo_binary

        Details for foo_binary
        """

        foo_binary = rule(
            impl=impl,
            attrs={
                "any": any_attr,
                "arg": arg_attr,
                "bool": bool_attr,
                "default_only": default_only_attr,
                "dep": dep_attr,
                "dict": dict_attr,
                "list": list_attr,
                "one_of": one_of_attr,
                "option": option_attr,
                "query": query_attr,
                "source": source_attr,
                "string": string_attr,
                "tuple": tuple_attr,
            },
            doc = doc,
        )
        "#
    );

    fn arg(name: &str, raw_type: Ty, default: Option<&str>) -> DocParam {
        DocParam::Arg {
            name: name.to_owned(),
            docs: DocString::from_docstring(DocStringKind::Starlark, &format!("{} docs", name)),
            typ: Some(DocType { raw_type }),
            default_value: default.map(String::from),
        }
    }

    // Grab the default parameters that are inserted into every rule.
    let empty_spec = AttributeSpec::from(vec![])?;
    let mut params = empty_spec.signature("foo_binary".to_owned()).documentation(
        empty_spec
            .starlark_types()
            .into_iter()
            .enumerate()
            .map(|(i, raw_type)| (i, DocType { raw_type }))
            .collect(),
        empty_spec.docstrings(),
    );
    params.extend(vec![
        arg("any", Ty::Any, None),
        arg("arg", Ty::string(), Some("_")),
        arg("bool", Ty::bool(), Some("_")),
        arg("default_only", Ty::string(), Some("_")),
        arg("dep", Ty::string(), Some("_")),
        arg("dict", Ty::dict(Ty::string(), Ty::bool()), Some("_")),
        arg("list", Ty::list(Ty::string()), Some("_")),
        arg("one_of", Ty::union2(Ty::bool(), Ty::string()), Some("_")),
        arg("option", Ty::union2(Ty::none(), Ty::string()), Some("_")),
        arg("query", Ty::string(), None),
        arg("source", Ty::string(), Some("_")),
        arg("string", Ty::string(), Some("_")),
        arg("tuple", Ty::tuple2(Ty::bool(), Ty::string()), Some("_")),
    ]);

    let expected_docs = DocItem::Function(DocFunction {
        docs: DocString::from_docstring(
            DocStringKind::Starlark,
            "Summary for foo_binary\n\nDetails for foo_binary",
        ),
        params,
        ret: DocReturn {
            docs: None,
            typ: Some(DocType {
                raw_type: Ty::none(),
            }),
        },
        dot_type: None,
    });

    let tester = rule_tester();
    let res = tester.eval_import(
        &ImportPath::testing_new("root//:defs.bzl"),
        bzl,
        LoadedModules::default(),
    )?;
    let docs = res
        .env()
        .get("foo_binary")
        .expect("foo_binary to exist")
        .value()
        .documentation()
        .unwrap();

    assert_eq!(expected_docs, docs);

    Ok(())
}