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

use std::borrow::Cow;

use buck2_core::build_file_path::BuildFilePath;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_query::query::environment::LabeledNode;
use buck2_query::query::environment::QueryTarget;
use dupe::Dupe;
use serde::Serializer;

use crate::attrs::attr_type::any_matches::AnyMatches;
use crate::attrs::configured_attr::ConfiguredAttr;
use crate::attrs::display::AttrDisplayWithContextExt;
use crate::attrs::fmt_context::AttrFmtContext;
use crate::attrs::inspect_options::AttrInspectOptions;
use crate::attrs::serialize::AttrSerializeWithContext;
use crate::nodes::configured::ConfiguredTargetNode;

impl LabeledNode for ConfiguredTargetNode {
    type NodeRef = ConfiguredTargetLabel;

    fn node_ref(&self) -> &Self::NodeRef {
        ConfiguredTargetNode::label(self)
    }
}

impl QueryTarget for ConfiguredTargetNode {
    type Attr<'a> = ConfiguredAttr;

    fn rule_type(&self) -> Cow<str> {
        Cow::Borrowed(ConfiguredTargetNode::rule_type(self).name())
    }

    fn buildfile_path(&self) -> &BuildFilePath {
        ConfiguredTargetNode::buildfile_path(self)
    }

    // TODO(cjhopman): Use existential traits to remove the Box<> once they are stabilized.
    fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(ConfiguredTargetNode::deps(self).map(|v| v.label()))
    }

    fn exec_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(ConfiguredTargetNode::exec_deps(self).map(|v| v.label()))
    }

    fn target_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(ConfiguredTargetNode::target_deps(self).map(|v| v.label()))
    }

    fn tests<'a>(&'a self) -> Option<Box<dyn Iterator<Item = Self::NodeRef> + Send + 'a>> {
        Some(Box::new(self.tests().map(|t| t.target().dupe())))
    }

    fn special_attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        mut func: F,
    ) -> Result<(), E> {
        for (name, attr) in ConfiguredTargetNode::special_attrs(self) {
            func(name, &attr)?;
        }
        Ok(())
    }

    fn attr_any_matches(
        attr: &Self::Attr<'_>,
        filter: &dyn Fn(&str) -> anyhow::Result<bool>,
    ) -> anyhow::Result<bool> {
        attr.any_matches(filter)
    }

    fn attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        mut func: F,
    ) -> Result<(), E> {
        for a in self.attrs(AttrInspectOptions::All) {
            func(a.name, &a.value)?;
        }
        Ok(())
    }

    fn map_attr<R, F: FnMut(Option<&Self::Attr<'_>>) -> R>(&self, key: &str, mut func: F) -> R {
        func(
            self.get(key, AttrInspectOptions::All)
                .as_ref()
                .map(|v| &v.value),
        )
    }

    fn inputs_for_each<E, F: FnMut(CellPath) -> Result<(), E>>(
        &self,
        mut func: F,
    ) -> Result<(), E> {
        for input in self.inputs() {
            func(input)?;
        }
        Ok(())
    }

    fn call_stack(&self) -> Option<String> {
        self.call_stack()
    }

    fn attr_to_string_alternate(&self, attr: &Self::Attr<'_>) -> String {
        format!(
            "{:#}",
            attr.as_display(&AttrFmtContext {
                package: Some(self.label().pkg().dupe()),
            })
        )
    }

    fn attr_serialize<S: Serializer>(
        &self,
        attr: &Self::Attr<'_>,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        attr.serialize_with_ctx(
            &AttrFmtContext {
                package: Some(self.label().pkg().dupe()),
            },
            serializer,
        )
    }
}


/*
 * 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 configured;
pub mod query_functions;
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_query::query::syntax::simple::functions::QueryFunctionsVisitLiterals;
use buck2_util::late_binding::LateBinding;

pub static CONFIGURED_GRAPH_QUERY_FUNCTIONS: LateBinding<Arc<dyn QueryFunctionsVisitLiterals>> =
    LateBinding::new("QUERY_FUNCTIONS");


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

use std::borrow::Cow;

use buck2_core::build_file_path::BuildFilePath;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::target::label::TargetLabel;
use buck2_query::query::environment::LabeledNode;
use buck2_query::query::environment::QueryTarget;
use dupe::Dupe;
use serde::Serializer;

use crate::attrs::coerced_attr::CoercedAttr;
use crate::attrs::display::AttrDisplayWithContextExt;
use crate::attrs::fmt_context::AttrFmtContext;
use crate::attrs::inspect_options::AttrInspectOptions;
use crate::attrs::serialize::AttrSerializeWithContext;
use crate::nodes::unconfigured::TargetNode;

impl LabeledNode for TargetNode {
    type NodeRef = TargetLabel;

    fn node_ref(&self) -> &Self::NodeRef {
        TargetNode::label(self)
    }
}

impl QueryTarget for TargetNode {
    type Attr<'a> = CoercedAttr;

    fn rule_type(&self) -> Cow<str> {
        Cow::Borrowed(TargetNode::rule_type(self).name())
    }

    fn buildfile_path(&self) -> &BuildFilePath {
        TargetNode::buildfile_path(self)
    }

    // TODO(cjhopman): Use existential traits to remove the Box<> once they are stabilized.
    fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(TargetNode::deps(self))
    }

    fn exec_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(TargetNode::exec_deps(self))
    }

    fn target_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(TargetNode::target_deps(self))
    }

    fn tests<'a>(&'a self) -> Option<Box<dyn Iterator<Item = Self::NodeRef> + Send + 'a>> {
        Some(Box::new(self.tests().map(|t| t.target().dupe())))
    }

    fn attr_any_matches(
        attr: &Self::Attr<'_>,
        filter: &dyn Fn(&str) -> anyhow::Result<bool>,
    ) -> anyhow::Result<bool> {
        attr.any_matches(filter)
    }

    fn special_attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        mut func: F,
    ) -> Result<(), E> {
        for (name, attr) in TargetNode::special_attrs(self) {
            func(name, &attr)?;
        }
        Ok(())
    }

    fn attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        mut func: F,
    ) -> Result<(), E> {
        for a in self.attrs(AttrInspectOptions::All) {
            func(a.name, a.value)?;
        }
        Ok(())
    }

    fn map_attr<R, F: FnMut(Option<&Self::Attr<'_>>) -> R>(&self, key: &str, mut func: F) -> R {
        func(
            self.attr_or_none(key, AttrInspectOptions::All)
                .as_ref()
                .map(|a| a.value),
        )
    }

    fn inputs_for_each<E, F: FnMut(CellPath) -> Result<(), E>>(
        &self,
        mut func: F,
    ) -> Result<(), E> {
        for input in self.inputs() {
            func(input)?;
        }
        Ok(())
    }

    fn call_stack(&self) -> Option<String> {
        self.call_stack()
    }

    fn attr_to_string_alternate(&self, attr: &Self::Attr<'_>) -> String {
        format!(
            "{:#}",
            attr.as_display(&AttrFmtContext {
                package: Some(self.label().pkg().dupe()),
            })
        )
    }

    fn attr_serialize<S: Serializer>(
        &self,
        attr: &Self::Attr<'_>,
        serializer: S,
    ) -> Result<S::Ok, S::Error> {
        attr.serialize_with_ctx(
            &AttrFmtContext {
                package: Some(self.label().pkg().dupe()),
            },
            serializer,
        )
    }
}


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

mod attrs;


/*
 * 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::paths::CellRelativePath;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
use buck2_core::package::PackageLabel;
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_node::attrs::attr_type::AttrType;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use buck2_node::attrs::hacks;
use dupe::Dupe;
use starlark::values::Heap;

#[test]
fn stringifies_correctly() -> anyhow::Result<()> {
    let heap = Heap::new();
    let coercer_ctx =
        BuildAttrCoercionContext::new_no_package(cells(None)?.1, cells(None)?.0.resolve_self());
    let coercer = AttrType::string();
    let coerced = coercer
        .coerce(
            AttrIsConfigurable::Yes,
            &coercer_ctx,
            heap.alloc("Hello, world!"),
        )
        .unwrap();

    let package = PackageLabel::new(
        CellName::testing_new("root"),
        CellRelativePath::new(ForwardRelativePath::new("foo/bar").unwrap()),
    );

    assert_eq!(
        "Hello, world!".to_owned(),
        hacks::value_to_string(&coerced, package.dupe())?
    );

    let list = AttrType::list(coercer).coerce(
        AttrIsConfigurable::Yes,
        &coercer_ctx,
        heap.alloc(vec!["Hello, world!"]),
    )?;
    assert!(hacks::value_to_string(&list, package.dupe()).is_err());
    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.
 */

mod hacks;


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

use std::ffi::OsStr;
use std::fmt;
use std::path::Path;
// Note: Using this because we don't need to propagate async in the offline
// archiver program.
use std::process::Command;

use anyhow::Context;
use buck2_core::fs::fs_util;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::paths::abs_path::AbsPathBuf;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct RelativeSymlink {
    pub link: ProjectRelativePathBuf,
    pub target: ProjectRelativePathBuf,
}

#[derive(Clone, Debug, PartialEq)]
pub struct AbsoluteSymlink {
    pub link: AbsPathBuf,
    pub target: AbsNormPathBuf,
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct ExternalSymlink {
    pub link: ProjectRelativePathBuf,
    pub target: AbsPathBuf,
    pub remaining_path: Option<ForwardRelativePathBuf>,
}

impl ExternalSymlink {
    pub fn full_target(&self) -> AbsPathBuf {
        if let Some(remaining_path) = &self.remaining_path {
            self.target.join(remaining_path.as_path())
        } else {
            self.target.clone()
        }
    }

    /// ExternalSymlink "remaining_path"s can themselves contain symlinks. When
    /// assembling the final archive, we need to preserve these interior links
    /// and materialize their targets in the offline archive.
    pub fn interior_links(&self) -> anyhow::Result<Vec<AbsoluteSymlink>> {
        let mut targets = Vec::new();
        if let Some(remaining_path) = &self.remaining_path {
            for ancestor in remaining_path.as_path().ancestors() {
                let path = self.target.join(ancestor);
                if let Some(meta) = fs_util::symlink_metadata_if_exists(&path)? {
                    if meta.file_type().is_symlink() {
                        let target = fs_util::canonicalize(&path)?;
                        targets.push(AbsoluteSymlink { link: path, target });
                    }
                }
            }
        }

        Ok(targets)
    }
}

/// Structured format for an "offline archive manifest", which contains information
/// necessary to perform a fully offline build of a particular target.
///
/// This manifest is generated by running:
///   `buck2 debug io-trace export-manifest`
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct OfflineArchiveManifest {
    /// The repository revision this archive was generated from.
    pub repository: RepositoryMetadata,
    /// List of project-relative paths that are required to perform a build.
    pub paths: Vec<ProjectRelativePathBuf>,
    /// List of external, absolute paths required to perform a build.
    pub external_paths: Vec<AbsNormPathBuf>,
    /// List of project-relative symlinks with targets inside the project.
    pub relative_symlinks: Vec<RelativeSymlink>,
    /// List of project-relative symlinks with targets outside the project.
    pub external_symlinks: Vec<ExternalSymlink>,
}

/// Repository information for an "offline archive manifest". Contains metadata
/// about the repository this manifest was generated from.
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct RepositoryMetadata {
    /// Repository revision this manifest came from.
    pub revision: String,
    /// Repository name.
    pub name: String,
}

impl RepositoryMetadata {
    pub fn from_cwd() -> anyhow::Result<Self> {
        Self::from_path(std::env::current_dir().context("Error getting current directory")?)
    }

    pub fn from_path<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
        let revision = hg_in(&path, ["whereami"])?;
        let name = hg_in(path, ["config", "remotefilelog.reponame"])?;
        Ok(Self { revision, name })
    }
}

impl fmt::Display for RepositoryMetadata {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}@{}", self.name, self.revision)
    }
}

pub fn hg<I, S>(args: I) -> anyhow::Result<String>
where
    I: IntoIterator<Item = S>,
    S: AsRef<OsStr>,
{
    hg_in(std::env::current_dir()?, args)
}

fn hg_in<I, S, P>(path: P, args: I) -> anyhow::Result<String>
where
    I: IntoIterator<Item = S>,
    S: AsRef<OsStr>,
    P: AsRef<Path>,
{
    let result = Command::new("hg")
        .args(args)
        .current_dir(path.as_ref())
        .env("HGPLAIN", "1")
        .output()
        .context("failed to dispatch hg command")?;
    if result.status.success() {
        let out = String::from_utf8(result.stdout).context("hg stdout to string")?;
        let out = out.trim();
        if out.is_empty() {
            anyhow::bail!("expected to be run in hg repository");
        } else {
            Ok(out.to_owned())
        }
    } else {
        let err = String::from_utf8(result.stderr).context("hg stderr to string")?;
        Err(anyhow::anyhow!(err))
    }
}

/// TODO(skarlage): Symlinks are weird on Windows; this crate won't actually
/// be used for windows for now.
#[cfg(all(test, not(windows)))]
mod tests {
    use buck2_core::fs::paths::abs_path::AbsPath;
    use buck2_core::fs::paths::forward_rel_path::ForwardRelativePath;
    use tempfile::TempDir;

    use super::*;

    struct Symlink {
        pub link: &'static str,
        pub target: &'static str,
    }

    enum Entry {
        File(&'static str),
        Dir(&'static str),
        RelativeSymlink(Symlink),
        /// Separate so we can make 'target' absolute, which needs tree root.
        AbsoluteSymlink(Symlink),
    }

    /// Creates a tree of entries rooted at
    fn create_tree(entries: Vec<Entry>) -> anyhow::Result<TempDir> {
        let working_dir = TempDir::new()?;
        let abs = AbsPath::new(working_dir.path())?;
        for entry in entries {
            match entry {
                Entry::File(path) => {
                    fs_util::create_file(abs.join(path))?;
                }
                Entry::Dir(dir) => {
                    fs_util::create_dir_all(abs.join(dir))?;
                }
                Entry::RelativeSymlink(symlink) => {
                    fs_util::symlink(symlink.target, abs.join(symlink.link))?;
                }
                Entry::AbsoluteSymlink(symlink) => {
                    fs_util::symlink(
                        working_dir.path().join(symlink.target),
                        abs.join(symlink.link),
                    )?;
                }
            }
        }

        Ok(working_dir)
    }

    #[test]
    fn test_full_target_no_remaining() -> anyhow::Result<()> {
        let external_link = ExternalSymlink {
            link: ProjectRelativePathBuf::unchecked_new("foo/bar/baz".to_owned()),
            target: AbsPathBuf::new("/some/absolute/path")?,
            remaining_path: None,
        };
        assert_eq!(
            AbsPathBuf::new("/some/absolute/path")?,
            external_link.full_target()
        );
        Ok(())
    }

    #[test]
    fn test_full_target_with_remaining() -> anyhow::Result<()> {
        let external_link = ExternalSymlink {
            link: ProjectRelativePathBuf::unchecked_new("foo/bar/baz".to_owned()),
            target: AbsPathBuf::new("/some/absolute/path")?,
            remaining_path: Some(ForwardRelativePathBuf::new("and/some/extra".to_owned())?),
        };
        assert_eq!(
            AbsPathBuf::new("/some/absolute/path/and/some/extra")?,
            external_link.full_target()
        );
        Ok(())
    }

    #[test]
    fn test_interior_links_no_remaining() -> anyhow::Result<()> {
        let external_link = ExternalSymlink {
            link: ProjectRelativePathBuf::unchecked_new("foo/bar/baz".to_owned()),
            target: AbsPathBuf::new("/some/absolute/path")?,
            remaining_path: None,
        };
        assert!(external_link.interior_links()?.is_empty());
        Ok(())
    }

    #[test]
    fn test_interior_links_relative() -> anyhow::Result<()> {
        let tree = create_tree(vec![
            Entry::Dir("foo/bar/baz"),
            Entry::File("foo/bar/stuff.txt"),
            Entry::RelativeSymlink(Symlink {
                link: "foo/bar/baz/link",
                target: "..",
            }),
        ])?;

        // Canonicalize this because the canonicalize() call in `interior_links()`
        // resolves to /private/var/... on macOS.
        let working_dir = fs_util::canonicalize(tree.path())?;

        let external_link = ExternalSymlink {
            link: ProjectRelativePathBuf::unchecked_new("unused".to_owned()),
            target: working_dir
                .join(ForwardRelativePath::unchecked_new("foo/bar/baz"))
                .as_abs_path()
                .to_owned(),
            remaining_path: Some(ForwardRelativePathBuf::new("link/stuff.txt".to_owned())?),
        };

        assert_eq!(
            vec![AbsoluteSymlink {
                link: AbsPathBuf::new(
                    working_dir.join(ForwardRelativePath::unchecked_new("foo/bar/baz/link"))
                )?,
                target: working_dir.join(ForwardRelativePath::unchecked_new("foo/bar")),
            }],
            external_link.interior_links()?
        );

        Ok(())
    }

    #[test]
    fn test_interior_links_absolute() -> anyhow::Result<()> {
        let tree = create_tree(vec![
            Entry::Dir("foo/bar/baz"),
            Entry::File("foo/bar/stuff.txt"),
            Entry::AbsoluteSymlink(Symlink {
                link: "foo/bar/baz/link",
                target: "foo/bar",
            }),
        ])?;

        // Canonicalize this because the canonicalize() call in `interior_links()`
        // resolves to /private/var/... on macOS.
        let working_dir = fs_util::canonicalize(tree.path())?;

        let external_link = ExternalSymlink {
            link: ProjectRelativePathBuf::unchecked_new("unused".to_owned()),
            target: working_dir
                .join(ForwardRelativePath::unchecked_new("foo/bar/baz"))
                .as_abs_path()
                .to_owned(),
            remaining_path: Some(ForwardRelativePathBuf::new("link/stuff.txt".to_owned())?),
        };

        assert_eq!(
            vec![AbsoluteSymlink {
                link: AbsPathBuf::new(
                    working_dir.join(ForwardRelativePath::unchecked_new("foo/bar/baz/link"))
                )?,
                target: working_dir.join(ForwardRelativePath::unchecked_new("foo/bar")),
            }],
            external_link.interior_links()?
        );

        Ok(())
    }
}


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

use std::sync::Arc;

use anyhow::Context;
use buck2_cli_proto::profile_request::ProfileOpts;
use buck2_cli_proto::profile_request::Profiler;
use buck2_core::fs::fs_util;
use buck2_core::fs::paths::abs_path::AbsPath;
use buck2_interpreter::dice::starlark_profiler::StarlarkProfilerConfiguration;
use buck2_interpreter::starlark_profiler::StarlarkProfileDataAndStats;
use starlark::eval::ProfileMode;

pub fn starlark_profiler_configuration_from_request(
    req: &buck2_cli_proto::ProfileRequest,
) -> anyhow::Result<StarlarkProfilerConfiguration> {
    let profiler_proto = buck2_cli_proto::profile_request::Profiler::from_i32(req.profiler)
        .context("Invalid profiler")?;

    let profile_mode = match profiler_proto {
        Profiler::HeapFlameAllocated => ProfileMode::HeapFlameAllocated,
        Profiler::HeapFlameRetained => ProfileMode::HeapFlameRetained,
        Profiler::HeapSummaryAllocated => ProfileMode::HeapSummaryAllocated,
        Profiler::HeapSummaryRetained => ProfileMode::HeapSummaryRetained,
        Profiler::TimeFlame => ProfileMode::TimeFlame,
        Profiler::Statement => ProfileMode::Statement,
        Profiler::Bytecode => ProfileMode::Bytecode,
        Profiler::BytecodePairs => ProfileMode::BytecodePairs,
        Profiler::Typecheck => ProfileMode::Typecheck,
    };

    match req.profile_opts.as_ref().expect("Missing profile opts") {
        ProfileOpts::TargetProfile(opts) => {
            let action = buck2_cli_proto::target_profile::Action::from_i32(opts.action)
                .context("Invalid action")?;
            Ok(match (action, opts.recursive) {
                (buck2_cli_proto::target_profile::Action::Loading, false) => {
                    StarlarkProfilerConfiguration::ProfileLastLoading(profile_mode)
                }
                (buck2_cli_proto::target_profile::Action::Loading, true) => {
                    return Err(anyhow::anyhow!(
                        "Recursive profiling is not supported for loading profiling"
                    ));
                }
                (buck2_cli_proto::target_profile::Action::Analysis, false) => {
                    StarlarkProfilerConfiguration::ProfileLastAnalysis(profile_mode)
                }
                (buck2_cli_proto::target_profile::Action::Analysis, true) => {
                    StarlarkProfilerConfiguration::ProfileAnalysisRecursively(profile_mode)
                }
            })
        }
        ProfileOpts::BxlProfile(_) => Ok(StarlarkProfilerConfiguration::ProfileBxl(profile_mode)),
    }
}

pub fn get_profile_response(
    profile_data: Arc<StarlarkProfileDataAndStats>,
    req: &buck2_cli_proto::ProfileRequest,
    output: &AbsPath,
) -> anyhow::Result<buck2_cli_proto::ProfileResponse> {
    let command_profile_mode = buck2_cli_proto::profile_request::Profiler::from_i32(req.profiler)
        .context("Invalid profiler")?;

    match command_profile_mode {
        Profiler::HeapFlameAllocated | Profiler::HeapFlameRetained | Profiler::TimeFlame => {
            let mut profile = profile_data.profile_data.gen()?;
            if profile.is_empty() {
                // inferno does not like empty flamegraphs.
                profile = "empty 1\n".to_owned();
            }
            let mut svg = Vec::new();
            inferno::flamegraph::from_reader(
                &mut inferno::flamegraph::Options::default(),
                profile.as_bytes(),
                &mut svg,
            )
            .context("writing SVG from profile data")?;

            fs_util::create_dir_if_not_exists(output)?;

            fs_util::write(output.join("flame.src"), &profile)
                .context("Failed to write profile")?;
            fs_util::write(output.join("flame.svg"), &svg).context("Failed to write profile")?;
        }
        _ => {
            let profile = profile_data.profile_data.gen()?;
            fs_util::write(output, profile).context("Failed to write profile")?;
        }
    };

    Ok(buck2_cli_proto::ProfileResponse {
        elapsed: Some(profile_data.elapsed().try_into()?),
        total_retained_bytes: profile_data.total_retained_bytes() as u64,
    })
}


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

use std::env;
use std::ffi::OsString;
use std::io;
use std::path::Path;
#[cfg(not(buck2_build))]
use std::path::PathBuf;

fn get_env(key: &str) -> Option<OsString> {
    println!("cargo:rerun-if-env-changed={}", key);
    env::var_os(key)
}

#[cfg(not(buck2_build))]
fn set_var(var: &str, override_var: &str, path: Result<PathBuf, protoc_bin_vendored::Error>) {
    let path = if let Some(override_var_value) = env::var_os(override_var) {
        eprintln!("INFO: Variable ${} is overridden by ${}", var, override_var);
        PathBuf::from(override_var_value)
    } else {
        match path {
            Err(e) => {
                panic!("{var} not available for platform {e:?}, set ${override_var} to override")
            }
            Ok(path) => {
                assert!(path.exists(), "Path does not exist: `{}`", path.display());
                path
            }
        }
    };

    let path = dunce::canonicalize(path).expect("Failed to canonicalize path");
    eprintln!("INFO: Variable ${} set to {:?}", var, path);
    env::set_var(var, path);
}

/// Set up $PROTOC to point to the in repo binary if available.
///
/// Note: repo root is expected to be a relative or absolute path to the root of the repository.
fn maybe_set_protoc() {
    #[cfg(not(buck2_build))]
    {
        // `cargo build` of `buck2` does not require external `protoc` dependency
        // because it uses prebuilt bundled `protoc` binary from `protoc-bin-vendored` crate.
        // However, prebuilt `protoc` binaries do not work in NixOS builds, see
        // https://github.com/facebook/buck2/issues/65
        // So for NixOS builds path to `protoc` binary can be overridden with
        // `BUCK2_BUILD_PROTOC` environment variable.
        set_var(
            "PROTOC",
            "BUCK2_BUILD_PROTOC",
            protoc_bin_vendored::protoc_bin_path(),
        );
    }
}

/// Set $PROTOC_INCLUDE.
fn maybe_set_protoc_include() {
    #[cfg(not(buck2_build))]
    {
        set_var(
            "PROTOC_INCLUDE",
            "BUCK2_BUILD_PROTOC_INCLUDE",
            protoc_bin_vendored::include_path(),
        );
    }
}

pub struct Builder {
    tonic: tonic_build::Builder,
}

pub fn configure() -> Builder {
    let tonic = tonic_build::configure();
    // We want to use optional everywhere
    let tonic = tonic.protoc_arg("--experimental_allow_proto3_optional");

    Builder { tonic }
}

impl Builder {
    pub fn type_attribute<P: AsRef<str>, A: AsRef<str>>(self, path: P, attribute: A) -> Self {
        Self {
            tonic: self.tonic.type_attribute(path, attribute),
        }
    }

    pub fn field_attribute<P: AsRef<str>, A: AsRef<str>>(self, path: P, attribute: A) -> Self {
        Self {
            tonic: self.tonic.field_attribute(path, attribute),
        }
    }

    pub fn extern_path(self, proto_path: impl AsRef<str>, rust_path: impl AsRef<str>) -> Self {
        Self {
            tonic: self.tonic.extern_path(proto_path, rust_path),
        }
    }

    pub fn boxed<P: AsRef<str>>(self, path: P) -> Self {
        Self {
            tonic: self.tonic.boxed(path),
        }
    }

    pub fn setup_protoc(self) -> Self {
        // It would be great if there were on the config rather than an env variables...
        maybe_set_protoc();
        maybe_set_protoc_include();
        self
    }

    pub fn compile(
        self,
        protos: &[impl AsRef<Path>],
        includes: &[impl AsRef<Path>],
    ) -> io::Result<()> {
        let Self { mut tonic } = self;

        // Buck likes to set $OUT in a genrule, while Cargo likes to set $OUT_DIR.
        // If we have $OUT set only, move it into the config
        if get_env("OUT_DIR").is_none() {
            if let Some(out) = get_env("OUT") {
                tonic = tonic.out_dir(out);
            }
        }

        // Tell Cargo that if the given file changes, to rerun this build script.
        for proto_file in protos {
            println!("cargo:rerun-if-changed={}", proto_file.as_ref().display());
        }

        tonic.compile(protos, includes)
    }
}


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

#![feature(async_closure)]
#![feature(box_patterns)]
#![feature(try_blocks)]
// Plugins
#![cfg_attr(feature = "gazebo_lint", feature(plugin))]
#![cfg_attr(feature = "gazebo_lint", allow(deprecated))] // :(
#![cfg_attr(feature = "gazebo_lint", plugin(gazebo_lint))]

pub mod query;

pub use buck2_query_derive::query_module;

// Required for use of #[query_module] within this crate (it allows query_module generated code to reference this crate as
// ::buck2_query like it would when used in other crates).
extern crate self as buck2_query;

/// __derive_refs allows us to reference other crates in buck_query_proc_macro without users needing to be
///  aware of those dependencies. We make them public here and then can reference them like
///  `buck_query::__derive_refs::foo`.
#[doc(hidden)]
pub mod __derive_refs {
    pub use buck2_query_parser;
    pub use indexmap;
    pub use ref_cast;
}


/*
 * 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::target::label::ConfiguredTargetLabel;
use buck2_core::target::label::TargetLabel;

use crate::query::environment::ConfiguredOrUnconfiguredTargetLabel;

impl ConfiguredOrUnconfiguredTargetLabel for TargetLabel {
    fn unconfigured_label(&self) -> &TargetLabel {
        self
    }
}

impl ConfiguredOrUnconfiguredTargetLabel for ConfiguredTargetLabel {
    fn unconfigured_label(&self) -> &TargetLabel {
        self.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::future::Future;
use std::pin::Pin;

use futures::stream::FuturesOrdered;
use futures::stream::FuturesUnordered;
use futures::Stream;
use futures::StreamExt;

/// `FuturesOrdered` or `FuturesUnordered`.
// This can be done with GAT, but GAT is unstable, and requires too much work to make it work,
// and this switch has only a little runtime overhead.
pub(crate) enum FuturesQueue<Fut: Future> {
    Ordered(FuturesOrdered<Fut>),
    Unordered(FuturesUnordered<Fut>),
}

impl<Fut: Future> FuturesQueue<Fut> {
    pub(crate) fn new_ordered() -> Self {
        FuturesQueue::Ordered(FuturesOrdered::new())
    }

    pub(crate) fn new_unordered() -> Self {
        FuturesQueue::Unordered(FuturesUnordered::new())
    }

    pub(crate) fn push(&mut self, fut: Fut) {
        match self {
            FuturesQueue::Ordered(futures_ordered) => futures_ordered.push_back(fut),
            FuturesQueue::Unordered(futures_unordered) => futures_unordered.push(fut),
        }
    }
}

impl<Fut: Future> Stream for FuturesQueue<Fut> {
    type Item = Fut::Output;

    fn poll_next(
        self: Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Option<Self::Item>> {
        match self.get_mut() {
            FuturesQueue::Ordered(futures_ordered) => futures_ordered.poll_next_unpin(cx),
            FuturesQueue::Unordered(futures_unordered) => futures_unordered.poll_next_unpin(cx),
        }
    }
}


/*
 * 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 buck_types;
pub mod environment;
pub(crate) mod futures_queue_generic;
pub mod syntax;
pub mod traversal;


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

use std::collections::HashMap;
use std::collections::HashSet;

use async_trait::async_trait;
use futures::StreamExt;

use crate::query::environment::LabeledNode;
use crate::query::futures_queue_generic::FuturesQueue;
use crate::query::syntax::simple::eval::label_indexed::LabelIndexedSet;

pub trait ChildVisitor<T: LabeledNode>: Send {
    fn visit(&mut self, node: T::NodeRef) -> anyhow::Result<()>;
}

impl<F, T: LabeledNode> ChildVisitor<T> for F
where
    F: FnMut(T::NodeRef) -> anyhow::Result<()>,
    F: Send,
{
    fn visit(&mut self, node: T::NodeRef) -> anyhow::Result<()> {
        self(node)
    }
}

/// The TraversalDelegate determines how to traverse the graph (via
/// for_each_child) and then handles doing the actual processing (via
/// visit). Different traversals may call `visit()` at different times (ex. dfs_postorder
/// calls it after all children have been visited)
#[async_trait]
pub trait AsyncTraversalDelegate<T: LabeledNode>: Send + Sync {
    /// visit is called once for each node. When it is called is traversal-dependent.
    fn visit(&mut self, target: T) -> anyhow::Result<()>;

    /// for_each_child should apply the provided function to each child of the node. This may be called multiple times in some traversals.
    async fn for_each_child(
        &mut self,
        target: &T,
        func: &mut dyn ChildVisitor<T>,
    ) -> anyhow::Result<()>;
}

pub trait NodeLookup<T: LabeledNode> {
    // TODO(cjhopman): Maybe this should be `&mut self` since we only need the one reference to it.
    fn get(&self, label: &T::NodeRef) -> anyhow::Result<T>;
}

#[async_trait]
pub trait AsyncNodeLookup<T: LabeledNode>: Send + Sync {
    async fn get(&self, label: &T::NodeRef) -> anyhow::Result<T>;
}

/// Implements a completely unordered graph traversal that visits nodes in a random order. When
/// traversing the graph (potentially) requires work to produce each node (like processing build files)
/// this unordered traversal will parallelize the work efficiently.
pub async fn async_unordered_traversal<
    'a,
    T: LabeledNode,
    RootIter: IntoIterator<Item = &'a T::NodeRef>,
>(
    nodes: &dyn AsyncNodeLookup<T>,
    root: RootIter,
    delegate: &mut dyn AsyncTraversalDelegate<T>,
) -> anyhow::Result<()> {
    async_traversal_common(nodes, root, delegate, None, false).await
}

/// Implements a depth-first postorder traversal. A node will be visited only after all of its
/// dependencies have been visited. Unlike `async_depth_first_postorder_traversal`, this will
/// only perform a single traversal, however that means that it will wait on each node lookup.
/// This should be used when the node lookups are cheap, if node lookups may be expensive, the
/// non-fast version should be used.
// TODO(cjhopman): Figure out how to implement this traversal in a way that has good performance
// in both cases.
pub async fn async_fast_depth_first_postorder_traversal<
    'a,
    T: LabeledNode,
    RootIter: IntoIterator<Item = &'a T::NodeRef>,
>(
    nodes: &(dyn NodeLookup<T> + Send + Sync),
    root: RootIter,
    delegate: &mut dyn AsyncTraversalDelegate<T>,
) -> anyhow::Result<()> {
    // This implementation simply performs a dfs. We maintain a work stack here.
    // When visiting a node, we first add an item to the work stack to call
    // post_visit for that node, and then add items to visit all the
    // children. While a work item for a child will not be added if it has
    // already been visited, if there's an item in the stack for that child
    // it will still be added. When popping the visit, if the node had been
    // visited, it's ignored. This ensures that a node's children are all
    // visited before we do PostVisit for that node.
    #[derive(Hash, Eq, PartialEq)]
    enum WorkItem<T: LabeledNode> {
        PostVisit(T),
        Visit(T::NodeRef),
    }

    #[derive(Default)]
    struct State<T: LabeledNode> {
        visited: HashSet<T::NodeRef>,
        work: Vec<WorkItem<T>>,
    }

    // TODO(cjhopman): There's a couple of things that could be improved about this.
    // 1. it would be better to hold a stack of iterators through children, but I
    // couldn't figure out quite a good way to do that in rust. I think it would
    // mean changing the delegate's for_each_children to return an iterator,
    // but idk.
    impl<T: LabeledNode> State<T> {
        fn new() -> Self {
            Self {
                visited: HashSet::new(),
                work: Vec::new(),
            }
        }

        fn push(&mut self, target: T::NodeRef) {
            if self.visited.contains(&target) {
                return;
            }

            self.work.push(WorkItem::Visit(target));
        }

        fn pop(&mut self) -> Option<WorkItem<T>> {
            self.work.pop()
        }
    }

    let mut state = State::new();

    for target in root {
        state.push(target.clone());
    }

    while let Some(curr) = state.pop() {
        match curr {
            WorkItem::Visit(target) => {
                if state.visited.contains(&target) {
                    continue;
                }

                let node = nodes.get(&target)?;
                state.visited.insert(target);
                state.work.push(WorkItem::PostVisit(node.dupe()));

                delegate
                    .for_each_child(&node, &mut |child| {
                        state.push(child);
                        Ok(())
                    })
                    .await?;
            }
            WorkItem::PostVisit(target) => {
                delegate.visit(target)?;
            }
        }
    }

    Ok(())
}

async fn async_traversal_common<
    'a,
    T: LabeledNode,
    RootIter: IntoIterator<Item = &'a T::NodeRef>,
>(
    nodes: &dyn AsyncNodeLookup<T>,
    root: RootIter,
    delegate: &mut dyn AsyncTraversalDelegate<T>,
    // `None` means no max depth.
    max_depth: Option<u32>,
    ordered: bool,
) -> anyhow::Result<()> {
    let mut visited = HashMap::new();
    let mut push = |queue: &mut FuturesQueue<_>,
                    target: T::NodeRef,
                    parent: Option<T::NodeRef>,
                    depth: u32| {
        if visited.contains_key(&target) {
            return;
        }
        visited.insert(target.clone(), parent);
        queue.push(async move {
            let result = nodes.get(&target).await;
            (target, depth, result)
        })
    };

    let mut queue = if ordered {
        FuturesQueue::new_ordered()
    } else {
        FuturesQueue::new_unordered()
    };

    for target in root {
        push(&mut queue, target.clone(), None, 0);
    }

    // TODO(cjhopman): FuturesOrdered/Unordered interacts poorly with tokio cooperative scheduling
    // (see https://github.com/rust-lang/futures-rs/issues/2053). Clean this up once a good
    // solution there exists.
    while let Some((target, depth, node)) = tokio::task::unconstrained(queue.next()).await {
        let result: anyhow::Result<_> = try {
            let node = node?;
            if Some(depth) != max_depth {
                let depth = depth + 1;
                delegate
                    .for_each_child(&node, &mut |child| {
                        push(&mut queue, child, Some(target.clone()), depth);
                        Ok(())
                    })
                    .await?;
            }

            delegate.visit(node)?;
        };

        if let Err(mut e) = result {
            let mut target = target;
            while let Some(Some(parent)) = visited.get(&target) {
                e = e.context(format!("Error traversing children of {}", parent));
                target = parent.clone();
            }
            return Err(e);
        }
    }

    Ok(())
}

pub async fn async_depth_limited_traversal<
    'a,
    T: LabeledNode,
    RootIter: IntoIterator<Item = &'a T::NodeRef>,
>(
    nodes: &dyn AsyncNodeLookup<T>,
    root: RootIter,
    delegate: &mut dyn AsyncTraversalDelegate<T>,
    max_depth: u32,
) -> anyhow::Result<()> {
    async_traversal_common(nodes, root, delegate, Some(max_depth), true).await
}

/// Implements a depth-first postorder traversal. A node will be visited only after all of its
/// dependencies have been visited.
// TODO(cjhopman): Accept a generic iterator for the roots. We need to iterate over it twice and it's only used with this specific iterator so it was easier to not be generic.
pub async fn async_depth_first_postorder_traversal<
    'a,
    T: LabeledNode,
    Iter: IntoIterator<Item = &'a T::NodeRef> + Clone,
>(
    nodes: &dyn AsyncNodeLookup<T>,
    root: Iter,
    delegate: &mut dyn AsyncTraversalDelegate<T>,
) -> anyhow::Result<()> {
    // We first do an unordered graph traversal to collect all nodes. The unordered traversal efficiently
    // uses resources when we need to process build files.
    // We don't cache the results of the for_each_child iterators, so that is called multiple times. Potentially it would be more performant to avoid that if an expensive filter/operation is involved.
    struct UnorderedDelegate<'a, T: LabeledNode> {
        delegate: &'a mut dyn AsyncTraversalDelegate<T>,
        nodes: LabelIndexedSet<T>,
    }

    #[async_trait]
    impl<T: LabeledNode> AsyncTraversalDelegate<T> for UnorderedDelegate<'_, T> {
        /// visit is called once for each node. When it is called is traversal-dependent.
        fn visit(&mut self, target: T) -> anyhow::Result<()> {
            self.nodes.insert(target);
            Ok(())
        }

        /// for_each_child should apply the provided function to each child of the node. This may be called multiple times in some traversals.
        async fn for_each_child(
            &mut self,
            target: &T,
            func: &mut dyn ChildVisitor<T>,
        ) -> anyhow::Result<()> {
            self.delegate.for_each_child(target, func).await
        }
    }

    let mut unordered_delegate = UnorderedDelegate {
        delegate,
        nodes: LabelIndexedSet::new(),
    };

    async_unordered_traversal(nodes, root.clone(), &mut unordered_delegate).await?;

    struct Nodes<T: LabeledNode> {
        nodes: LabelIndexedSet<T>,
    }

    impl<T: LabeledNode> NodeLookup<T> for Nodes<T> {
        fn get(&self, label: &T::NodeRef) -> anyhow::Result<T> {
            Ok(self
                .nodes
                .get(label)
                .unwrap_or_else(|| {
                    panic!(
                    "Should've failed in first traversal if there's a missing node (missing `{}`).",
                    label
                )
                })
                .dupe())
        }
    }

    let nodes = Nodes {
        nodes: unordered_delegate.nodes,
    };

    async_fast_depth_first_postorder_traversal(&nodes, root, delegate).await?;

    Ok(())
}

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

    use buck2_core::build_file_path::BuildFilePath;
    use buck2_core::cells::cell_path::CellPath;
    use derive_more::Display;
    use dupe::Dupe;
    use gazebo::prelude::*;
    use serde::Serialize;
    use serde::Serializer;

    use super::*;
    use crate::query::environment::QueryTarget;
    use crate::query::syntax::simple::eval::set::TargetSet;

    #[derive(Debug, Clone)]
    struct Node(Ref, Vec<Ref>);

    // For tests, we don't care that this Dupe impl is slow.
    impl Dupe for Node {}

    #[derive(Debug, Clone, Dupe, Hash, Display, PartialEq, Eq, PartialOrd, Ord)]
    struct Ref(i64);

    #[derive(Debug, Display, Serialize)]
    struct Attr(String);

    impl LabeledNode for Node {
        type NodeRef = Ref;

        fn node_ref(&self) -> &Self::NodeRef {
            &self.0
        }
    }

    impl QueryTarget for Node {
        type Attr<'a> = Attr;

        fn rule_type(&self) -> Cow<str> {
            unimplemented!()
        }
        fn buildfile_path(&self) -> &BuildFilePath {
            unimplemented!()
        }

        fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
            Box::new(self.1.iter())
        }

        fn special_attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
            &self,
            _func: F,
        ) -> Result<(), E> {
            unimplemented!()
        }

        fn attr_any_matches(
            _attr: &Self::Attr<'_>,
            _filter: &dyn Fn(&str) -> anyhow::Result<bool>,
        ) -> anyhow::Result<bool> {
            unimplemented!()
        }

        fn attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
            &self,
            _func: F,
        ) -> Result<(), E> {
            unimplemented!()
        }

        fn map_attr<R, F: FnMut(Option<&Self::Attr<'_>>) -> R>(&self, _key: &str, _func: F) -> R {
            unimplemented!()
        }

        fn inputs_for_each<E, F: FnMut(CellPath) -> Result<(), E>>(
            &self,
            _func: F,
        ) -> Result<(), E> {
            unimplemented!()
        }

        fn exec_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
            unimplemented!()
        }

        fn target_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
            unimplemented!()
        }

        fn call_stack(&self) -> Option<String> {
            None
        }

        fn attr_to_string_alternate(&self, _attr: &Self::Attr<'_>) -> String {
            unimplemented!("not needed for tests")
        }

        fn attr_serialize<S: Serializer>(
            &self,
            _attr: &Self::Attr<'_>,
            _serializer: S,
        ) -> Result<S::Ok, S::Error> {
            unimplemented!("not needed for tests")
        }
    }

    struct Graph(HashMap<Ref, Node>);

    impl Graph {
        fn collecting_delegate<'a>(
            &self,
            results: &'a mut Vec<Ref>,
        ) -> impl AsyncTraversalDelegate<Node> + 'a {
            struct Delegate<'a> {
                results: &'a mut Vec<Ref>,
            }

            #[async_trait]
            impl<'a> AsyncTraversalDelegate<Node> for Delegate<'a> {
                fn visit(&mut self, target: Node) -> anyhow::Result<()> {
                    self.results.push(target.0.dupe());
                    Ok(())
                }

                async fn for_each_child(
                    &mut self,
                    target: &Node,
                    func: &mut dyn ChildVisitor<Node>,
                ) -> anyhow::Result<()> {
                    for child in &target.1 {
                        func.visit(child.dupe())?;
                    }
                    Ok(())
                }
            }

            Delegate { results }
        }
    }

    #[async_trait]
    impl AsyncNodeLookup<Node> for Graph {
        async fn get(&self, label: &Ref) -> anyhow::Result<Node> {
            self.0
                .get(label)
                .ok_or_else(|| anyhow::anyhow!("missing node"))
                .map(|v| v.dupe())
        }
    }

    fn make_graph(nodes: &[(i64, &[i64])]) -> anyhow::Result<Graph> {
        let mut map = HashMap::new();
        for (n, deps) in nodes {
            map.insert(Ref(*n), Node(Ref(*n), deps.map(|v| Ref(*v))));
        }
        Ok(Graph(map))
    }

    #[tokio::test]
    async fn test_postorder() -> anyhow::Result<()> {
        let graph = make_graph(&[
            (0, &[1, 2]),
            (1, &[2, 3, 4]),
            (2, &[3, 4]),
            (3, &[4]),
            (4, &[]),
        ])?;
        let mut targets = TargetSet::new();
        targets.insert(graph.get(&Ref(0)).await?);

        let mut results = Vec::new();
        {
            let mut delegate = graph.collecting_delegate(&mut results);
            async_depth_first_postorder_traversal(&graph, targets.iter_names(), &mut delegate)
                .await?;
        }

        assert_eq!(results, vec![Ref(4), Ref(3), Ref(2), Ref(1), Ref(0)]);

        Ok(())
    }

    #[tokio::test]
    async fn test_async_depth_limited_traversal() -> anyhow::Result<()> {
        let graph = make_graph(&[
            (0, &[1, 2]),
            (1, &[2, 3, 4]),
            (2, &[3, 4]),
            (3, &[4]),
            (4, &[]),
        ])?;
        let mut targets = TargetSet::new();
        targets.insert(graph.get(&Ref(0)).await?);

        let mut results0 = Vec::new();
        {
            let mut delegate = graph.collecting_delegate(&mut results0);
            async_depth_limited_traversal(&graph, targets.iter_names(), &mut delegate, 0).await?;
        }
        assert_eq!(results0, vec![Ref(0)]);

        let mut results1 = Vec::new();
        {
            let mut delegate = graph.collecting_delegate(&mut results1);
            async_depth_limited_traversal(&graph, targets.iter_names(), &mut delegate, 1).await?;
        }
        assert_eq!(results1, vec![Ref(0), Ref(1), Ref(2)]);

        Ok(())
    }
}


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

use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::Debug;
use std::fmt::Display;
use std::hash::Hash;

use anyhow::Context;
use async_trait::async_trait;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::cells::cell_path::CellPath;
use buck2_core::configuration::compatibility::MaybeCompatible;
use buck2_core::package::PackageLabel;
use buck2_core::target::label::TargetLabel;
use buck2_util::collections::ordered_set::OrderedSet;
use dupe::Dupe;
use futures::stream::FuturesUnordered;
use futures::stream::TryStreamExt;
use thiserror::Error;

use crate::query::syntax::simple::eval::error::QueryError;
use crate::query::syntax::simple::eval::file_set::FileSet;
use crate::query::syntax::simple::eval::set::TargetSet;
use crate::query::syntax::simple::eval::set::TargetSetExt;
use crate::query::traversal::AsyncTraversalDelegate;
use crate::query::traversal::ChildVisitor;
mod tests;

#[derive(Error, Debug)]
pub enum QueryEnvironmentError {
    #[error("Missing target `{}`. Targets in the package: <{}>", .0, .1.join(", "))]
    MissingTargetError(String, Vec<String>),
    #[error("Expected package `{0}` to be available in traversal.")]
    TraversalMissingPackage(PackageLabel),
}

impl QueryEnvironmentError {
    pub fn missing_target<T: NodeLabel, S: AsRef<str>, Iter: IntoIterator<Item = S>>(
        target: &T,
        package_targets: Iter,
    ) -> Self {
        let existing = package_targets
            .into_iter()
            .map(|e| format!("`{}`", e.as_ref()))
            .collect();
        Self::MissingTargetError(target.to_string(), existing)
    }
}

pub trait NodeLabel: Clone + Hash + PartialEq + Eq + Debug + Display + Send + Sync {}
impl<T: Clone + Hash + PartialEq + Eq + Debug + Display + Send + Sync> NodeLabel for T {}

pub trait ConfiguredOrUnconfiguredTargetLabel: NodeLabel {
    fn unconfigured_label(&self) -> &TargetLabel;
}

pub trait LabeledNode: Dupe + Send + Sync + 'static {
    type NodeRef: NodeLabel;

    fn node_ref(&self) -> &Self::NodeRef;
}

pub struct QueryTargets {}

impl QueryTargets {
    /// Used to process all the attrs of a node (both the normal rule attrs and the "special" attrs). Applies
    /// a function to the attrs instead of returning an iterator as some of them are owned and some are refs
    /// into the node.
    pub fn for_all_attrs<E, T: QueryTarget, F: FnMut(&str, &T::Attr<'_>) -> Result<(), E>>(
        target: &T,
        mut func: F,
    ) -> Result<(), E> {
        target.special_attrs_for_each(&mut func)?;
        target.attrs_for_each(&mut func)?;
        Ok(())
    }
}

pub trait QueryTarget: LabeledNode + Dupe + Send + Sync + 'static {
    type Attr<'a>: ?Sized + Debug + 'a;

    /// Returns the input files for this node.
    fn inputs_for_each<E, F: FnMut(CellPath) -> Result<(), E>>(&self, func: F) -> Result<(), E>;

    fn rule_type(&self) -> Cow<str>;

    /// Return the path to the buildfile that defines this target, e.g. `fbcode//foo/bar/TARGETS`
    fn buildfile_path(&self) -> &BuildFilePath;

    // TODO(cjhopman): Use existential traits to remove the Box<> once they are stabilized.
    fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a>;

    // TODO(cjhopman): Use existential traits to remove the Box<> once they are stabilized.
    fn exec_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a>;

    // TODO(cjhopman): Use existential traits to remove the Box<> once they are stabilized.
    fn target_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a>;

    fn tests<'a>(&'a self) -> Option<Box<dyn Iterator<Item = Self::NodeRef> + Send + 'a>> {
        None
    }

    fn attr_to_string_alternate(&self, attr: &Self::Attr<'_>) -> String;

    fn attr_serialize<S: serde::Serializer>(
        &self,
        attr: &Self::Attr<'_>,
        serializer: S,
    ) -> Result<S::Ok, S::Error>;

    fn attr_any_matches(
        attr: &Self::Attr<'_>,
        filter: &dyn Fn(&str) -> anyhow::Result<bool>,
    ) -> anyhow::Result<bool>;

    fn special_attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        func: F,
    ) -> Result<(), E>;

    fn attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        func: F,
    ) -> Result<(), E>;

    fn map_attr<R, F: FnMut(Option<&Self::Attr<'_>>) -> R>(&self, key: &str, func: F) -> R;

    fn call_stack(&self) -> Option<String>;
}

#[async_trait]
pub trait TraversalFilter<T: QueryTarget>: Send + Sync {
    /// Returns a the children that pass this filter.
    async fn get_children(&self, target: &T) -> anyhow::Result<TargetSet<T>>;
}

/// The environment of a Buck query that can evaluate queries to produce a
/// result.
#[async_trait]
pub trait QueryEnvironment: Send + Sync {
    type Target: QueryTarget;

    async fn get_node(
        &self,
        node_ref: &<Self::Target as LabeledNode>::NodeRef,
    ) -> anyhow::Result<Self::Target>;

    async fn get_node_for_default_configured_target(
        &self,
        node_ref: &<Self::Target as LabeledNode>::NodeRef,
    ) -> anyhow::Result<MaybeCompatible<Self::Target>>;

    /// Evaluates a literal target pattern. See buck2_common::pattern
    async fn eval_literals(&self, literal: &[&str]) -> anyhow::Result<TargetSet<Self::Target>>;

    /// Evaluates a file literal
    async fn eval_file_literal(&self, literal: &str) -> anyhow::Result<FileSet>;

    /// Performs a depth first traversal, with a post-order callback. The
    /// delegate defines the traversal and receives the callback.
    async fn dfs_postorder(
        &self,
        root: &TargetSet<Self::Target>,
        delegate: &mut dyn AsyncTraversalDelegate<Self::Target>,
    ) -> anyhow::Result<()>;

    async fn depth_limited_traversal(
        &self,
        root: &TargetSet<Self::Target>,
        delegate: &mut dyn AsyncTraversalDelegate<Self::Target>,
        depth: u32,
    ) -> anyhow::Result<()>;

    async fn allpaths(
        &self,
        from: &TargetSet<Self::Target>,
        to: &TargetSet<Self::Target>,
    ) -> anyhow::Result<TargetSet<Self::Target>> {
        self.rdeps(from, to, None).await
    }

    async fn somepath(
        &self,
        from: &TargetSet<Self::Target>,
        to: &TargetSet<Self::Target>,
    ) -> anyhow::Result<TargetSet<Self::Target>> {
        struct Delegate<'a, Q: QueryTarget> {
            to: &'a TargetSet<Q>,
            /// Contains targets that were reached starting from `from` that have a path to `to`.
            path: TargetSet<Q>,
        }

        #[async_trait]
        impl<'a, Q: QueryTarget> AsyncTraversalDelegate<Q> for Delegate<'a, Q> {
            fn visit(&mut self, target: Q) -> anyhow::Result<()> {
                // NOTE: It would be better to just only post-order visit our parents, but that is
                // not possible because we push *all* children when visiting a node, so we will not
                // just post-visit all parents when we interrupt the search.
                // NOTE: We assert! around the insertions below because we know each node should
                // only be post-visited once but since we rely on `last()`, it matters so we check
                // it.

                if let Some(head) = self.path.last() {
                    if target.deps().any(|t| t == head.node_ref()) {
                        assert!(self.path.insert(target));
                    }
                    return Ok(());
                }

                if self.to.contains(target.node_ref()) {
                    assert!(self.path.insert(target));
                }

                Ok(())
            }

            async fn for_each_child(
                &mut self,
                target: &Q,
                func: &mut dyn ChildVisitor<Q>,
            ) -> anyhow::Result<()> {
                // Stop adding more children if we are putting a path back together.
                if !self.path.is_empty() || self.to.contains(target.node_ref()) {
                    return Ok(());
                }
                let res: anyhow::Result<_> = try {
                    for dep in target.deps() {
                        func.visit(dep.clone())?;
                    }
                };
                res.with_context(|| format!("Error traversing children of `{}`", target.node_ref()))
            }
        }

        let mut delegate = Delegate {
            path: TargetSet::new(),
            to,
        };
        self.dfs_postorder(from, &mut delegate).await?;
        Ok(delegate.path)
    }

    async fn allbuildfiles(&self, _universe: &TargetSet<Self::Target>) -> anyhow::Result<FileSet> {
        Err(anyhow::anyhow!(QueryError::FunctionUnimplemented(
            "allbuildfiles() is implemented only for uquery and cquery.",
        )))
    }

    async fn rbuildfiles(&self, _universe: &FileSet, _argset: &FileSet) -> anyhow::Result<FileSet> {
        Err(anyhow::anyhow!(QueryError::FunctionUnimplemented(
            "rbuildfiles() is implemented only for uquery and cquery."
        )))
    }

    async fn rdeps(
        &self,
        universe: &TargetSet<Self::Target>,
        from: &TargetSet<Self::Target>,
        depth: Option<i32>,
    ) -> anyhow::Result<TargetSet<Self::Target>> {
        // First, we map all deps to their rdeps (parents).
        // This effectively allows traversing the graph later, in reverse (following dependency back-edges).
        struct ParentsCollectorDelegate<Q: QueryTarget> {
            parents: HashMap<Q::NodeRef, OrderedSet<Q::NodeRef>>,
            // Keep track of nodes in-universe so that, if any rdeps are collected out-of-universe,
            // we don't return them.
            nodes_in_universe: TargetSet<Q>,
        }

        #[async_trait]
        impl<Q: QueryTarget> AsyncTraversalDelegate<Q> for ParentsCollectorDelegate<Q> {
            fn visit(&mut self, target: Q) -> anyhow::Result<()> {
                self.nodes_in_universe.insert(target);
                Ok(())
            }

            async fn for_each_child(
                &mut self,
                target: &Q,
                func: &mut dyn ChildVisitor<Q>,
            ) -> anyhow::Result<()> {
                for dep in target.deps() {
                    func.visit(dep.clone()).with_context(|| {
                        format!("Error traversing children of `{}`", target.node_ref())
                    })?;
                    self.parents
                        .entry(dep.clone())
                        .or_default()
                        .insert(target.node_ref().clone());
                }
                Ok(())
            }
        }

        let mut parents_collector_delegate = ParentsCollectorDelegate {
            parents: HashMap::new(),
            nodes_in_universe: TargetSet::new(),
        };

        self.dfs_postorder(universe, &mut parents_collector_delegate)
            .await?;

        // Now that we have a mapping of back-edges, traverse deps graph in reverse.
        struct ReverseDelegate<Q: QueryTarget> {
            rdeps: TargetSet<Q>,
            parents: HashMap<Q::NodeRef, OrderedSet<Q::NodeRef>>,
        }

        #[async_trait]
        impl<Q: QueryTarget> AsyncTraversalDelegate<Q> for ReverseDelegate<Q> {
            fn visit(&mut self, target: Q) -> anyhow::Result<()> {
                self.rdeps.insert(target);
                Ok(())
            }

            async fn for_each_child(
                &mut self,
                target: &Q,
                func: &mut dyn ChildVisitor<Q>,
            ) -> anyhow::Result<()> {
                if let Some(parents) = self.parents.get(target.node_ref()) {
                    for parent in parents {
                        func.visit(parent.clone()).with_context(|| {
                            format!("Error traversing parents of `{}`", target.node_ref())
                        })?;
                    }
                }
                Ok(())
            }
        }

        let mut delegate = ReverseDelegate {
            rdeps: TargetSet::new(),
            parents: parents_collector_delegate.parents,
        };

        let roots_in_universe = from.intersect(&parents_collector_delegate.nodes_in_universe)?;

        match depth {
            // For unbounded traversals, buck1 recommends specifying a large value. We'll accept either a negative (like -1) or
            // a large value as unbounded. We can't just call it optional because args are positional only in the query syntax
            // and so to specify a filter you need to specify a depth.
            Some(v) if (0..1_000_000_000).contains(&v) => {
                self.depth_limited_traversal(&roots_in_universe, &mut delegate, v as u32)
                    .await?;
            }
            _ => {
                self.dfs_postorder(&roots_in_universe, &mut delegate)
                    .await?;
            }
        }

        Ok(delegate.rdeps)
    }

    async fn testsof(
        &self,
        targets: &TargetSet<Self::Target>,
    ) -> anyhow::Result<TargetSet<Self::Target>> {
        let target_tests = targets
            .iter()
            .map(|target| {
                let tests = target
                    .tests()
                    .ok_or(QueryError::FunctionUnimplemented("testsof"))?;

                anyhow::Ok((target, tests))
            })
            .collect::<Result<Vec<_>, _>>()?;

        let mut futs = target_tests
            .into_iter()
            .flat_map(|(target, tests)| {
                tests.into_iter().map(move |test| async move {
                    let test = self.get_node(&test).await.with_context(|| {
                        format!(
                            "Error getting test of target {}",
                            LabeledNode::node_ref(target),
                        )
                    })?;
                    anyhow::Ok(test)
                })
            })
            .collect::<FuturesUnordered<_>>();

        let mut ret = TargetSet::new();
        while let Some(test) = futs.try_next().await? {
            ret.insert(test);
        }

        Ok(ret)
    }

    async fn testsof_with_default_target_platform(
        &self,
        targets: &TargetSet<Self::Target>,
    ) -> anyhow::Result<Vec<MaybeCompatible<Self::Target>>> {
        let target_tests = targets
            .iter()
            .map(|target| {
                let tests = target
                    .tests()
                    .ok_or(QueryError::FunctionUnimplemented("testsof"))?;

                anyhow::Ok((target, tests))
            })
            .collect::<Result<Vec<_>, _>>()?;

        let mut futs = target_tests
            .into_iter()
            .flat_map(|(target, tests)| {
                tests.into_iter().map(move |test| async move {
                    let test = self
                        .get_node_for_default_configured_target(&test)
                        .await
                        .with_context(|| {
                            format!(
                                "Error getting test of target {}",
                                LabeledNode::node_ref(target),
                            )
                        })?;
                    anyhow::Ok(test)
                })
            })
            .collect::<FuturesUnordered<_>>();

        let mut ret = Vec::new();
        while let Some(test) = futs.try_next().await? {
            ret.push(test);
        }

        Ok(ret)
    }

    async fn deps(
        &self,
        targets: &TargetSet<Self::Target>,
        depth: Option<i32>,
        filter: Option<&dyn TraversalFilter<Self::Target>>,
    ) -> anyhow::Result<TargetSet<Self::Target>> {
        deps(self, targets, depth, filter).await
    }

    async fn owner(&self, _paths: &FileSet) -> anyhow::Result<TargetSet<Self::Target>>;
}

pub async fn deps<Env: QueryEnvironment + ?Sized>(
    env: &Env,
    targets: &TargetSet<Env::Target>,
    depth: Option<i32>,
    filter: Option<&dyn TraversalFilter<Env::Target>>,
) -> anyhow::Result<TargetSet<Env::Target>> {
    let mut deps = TargetSet::new();

    struct Delegate<'a, Q: QueryTarget> {
        deps: &'a mut TargetSet<Q>,
        filter: Option<&'a dyn TraversalFilter<Q>>,
    }

    #[async_trait]
    impl<'a, Q: QueryTarget> AsyncTraversalDelegate<Q> for Delegate<'a, Q> {
        fn visit(&mut self, target: Q) -> anyhow::Result<()> {
            self.deps.insert(target);
            Ok(())
        }

        async fn for_each_child(
            &mut self,
            target: &Q,
            func: &mut dyn ChildVisitor<Q>,
        ) -> anyhow::Result<()> {
            let res: anyhow::Result<_> = try {
                match self.filter {
                    Some(filter) => {
                        for dep in filter.get_children(target).await?.iter() {
                            func.visit(dep.node_ref().clone())?;
                        }
                    }
                    None => {
                        for dep in target.deps() {
                            func.visit(dep.clone())?;
                        }
                    }
                }
            };
            res.with_context(|| format!("Error traversing children of `{}`", target.node_ref()))
        }
    }

    match depth {
        // For unbounded traversals, buck1 recommends specifying a large value. We'll accept either a negative (like -1) or
        // a large value as unbounded. We can't just call it optional because args are positional only in the query syntax
        // and so to specify a filter you need to specify a depth.
        Some(v) if (0..1_000_000_000).contains(&v) => {
            env.depth_limited_traversal(
                targets,
                &mut Delegate {
                    deps: &mut deps,
                    filter,
                },
                v as u32,
            )
            .await?;
        }
        _ => {
            env.dfs_postorder(
                targets,
                &mut Delegate {
                    deps: &mut deps,
                    filter,
                },
            )
            .await?;
        }
    }

    Ok(deps)
}


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

#![cfg(test)]

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

use buck2_query::query::traversal::async_depth_first_postorder_traversal;
use buck2_query::query::traversal::async_depth_limited_traversal;
use buck2_query::query::traversal::NodeLookup;
use derive_more::Display;
use derive_more::From;
use dupe::OptionDupedExt;
use indexmap::IndexSet;
use serde::Serializer;

use super::*;
use crate::query::traversal::AsyncNodeLookup;

#[derive(Debug, Copy, Clone, Dupe, Eq, PartialEq, Hash, Display, From)]
struct TestTargetId(u64);

#[derive(Debug, Copy, Clone, Dupe, Eq, PartialEq, Hash, Display)]
struct TestTargetAttr;

#[derive(Clone, Dupe, Eq, PartialEq)]
struct TestTarget {
    id: TestTargetId,
    deps: Arc<IndexSet<TestTargetId>>,
}

/// Custom debug to make the test output more readable
impl fmt::Debug for TestTarget {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.id.0)
    }
}

impl LabeledNode for TestTarget {
    type NodeRef = TestTargetId;

    fn node_ref(&self) -> &Self::NodeRef {
        &self.id
    }
}

impl QueryTarget for TestTarget {
    type Attr<'a> = TestTargetAttr;

    fn inputs_for_each<E, F: FnMut(CellPath) -> Result<(), E>>(&self, _func: F) -> Result<(), E> {
        unimplemented!()
    }

    fn rule_type(&self) -> Cow<str> {
        unimplemented!()
    }

    fn buildfile_path(&self) -> &BuildFilePath {
        unimplemented!()
    }

    fn deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(self.deps.iter())
    }

    fn exec_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(std::iter::empty())
    }

    fn target_deps<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::NodeRef> + Send + 'a> {
        Box::new(std::iter::empty())
    }

    fn attr_to_string_alternate(&self, _attr: &Self::Attr<'_>) -> String {
        unimplemented!("not needed for tests")
    }

    fn attr_serialize<S: Serializer>(
        &self,
        _attr: &Self::Attr<'_>,
        _serializer: S,
    ) -> Result<S::Ok, S::Error> {
        unimplemented!("not needed for tests")
    }

    fn attr_any_matches(
        _attr: &Self::Attr<'_>,
        _filter: &dyn Fn(&str) -> anyhow::Result<bool>,
    ) -> anyhow::Result<bool> {
        unimplemented!()
    }

    fn special_attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        _func: F,
    ) -> Result<(), E> {
        unimplemented!()
    }

    fn attrs_for_each<E, F: FnMut(&str, &Self::Attr<'_>) -> Result<(), E>>(
        &self,
        _func: F,
    ) -> Result<(), E> {
        unimplemented!()
    }

    fn map_attr<R, F: FnMut(Option<&Self::Attr<'_>>) -> R>(&self, _key: &str, _func: F) -> R {
        unimplemented!()
    }

    fn call_stack(&self) -> Option<String> {
        None
    }
}

struct TestEnv {
    graph: HashMap<TestTargetId, TestTarget>,
}

impl NodeLookup<TestTarget> for TestEnv {
    fn get(&self, label: &<TestTarget as LabeledNode>::NodeRef) -> anyhow::Result<TestTarget> {
        self.graph
            .get(label)
            .duped()
            .with_context(|| format!("Invalid node: {:?}", label))
    }
}

#[async_trait]
impl AsyncNodeLookup<TestTarget> for TestEnv {
    async fn get(
        &self,
        label: &<TestTarget as LabeledNode>::NodeRef,
    ) -> anyhow::Result<TestTarget> {
        self.graph
            .get(label)
            .duped()
            .with_context(|| format!("Invalid node: {:?}", label))
    }
}

#[async_trait]
impl QueryEnvironment for TestEnv {
    type Target = TestTarget;

    async fn get_node(
        &self,
        node_ref: &<Self::Target as LabeledNode>::NodeRef,
    ) -> anyhow::Result<Self::Target> {
        <Self as NodeLookup<TestTarget>>::get(self, node_ref)
    }

    async fn get_node_for_default_configured_target(
        &self,
        _node_ref: &<Self::Target as LabeledNode>::NodeRef,
    ) -> anyhow::Result<MaybeCompatible<Self::Target>> {
        unimplemented!()
    }

    async fn eval_literals(&self, _literal: &[&str]) -> anyhow::Result<TargetSet<Self::Target>> {
        unimplemented!()
    }

    async fn eval_file_literal(&self, _literal: &str) -> anyhow::Result<FileSet> {
        unimplemented!()
    }

    async fn dfs_postorder(
        &self,
        root: &TargetSet<Self::Target>,
        delegate: &mut dyn AsyncTraversalDelegate<Self::Target>,
    ) -> anyhow::Result<()> {
        // TODO: Should this be part of QueryEnvironment's default impl?
        async_depth_first_postorder_traversal(self, root.iter_names(), delegate).await
    }

    async fn depth_limited_traversal(
        &self,
        root: &TargetSet<Self::Target>,
        delegate: &mut dyn AsyncTraversalDelegate<Self::Target>,
        depth: u32,
    ) -> anyhow::Result<()> {
        async_depth_limited_traversal(self, root.iter_names(), delegate, depth).await
    }

    async fn owner(&self, _paths: &FileSet) -> anyhow::Result<TargetSet<Self::Target>> {
        unimplemented!()
    }
}

impl TestEnv {
    /// A helper to get e.g. stuff like "1,2,3" into a TargetSet.
    fn set(&self, entries: &str) -> anyhow::Result<TargetSet<TestTarget>> {
        let mut set = TargetSet::new();
        for c in entries.split(',') {
            let id = TestTargetId(c.parse().context("Invalid ID")?);
            set.insert(<Self as NodeLookup<TestTarget>>::get(self, &id)?);
        }
        Ok(set)
    }
}

#[derive(Default)]
pub struct TestEnvBuilder {
    graph: HashMap<u64, IndexSet<u64>>,
}

impl TestEnvBuilder {
    fn edge(&mut self, from: u64, to: u64) {
        self.graph.entry(from).or_default().insert(to);
        self.graph.entry(to).or_default();
    }

    fn build(&self) -> TestEnv {
        TestEnv {
            graph: self
                .graph
                .iter()
                .map(|(id, vs)| {
                    let id = TestTargetId(*id);
                    let deps = Arc::new(vs.iter().map(|v| TestTargetId(*v)).collect());
                    (id, TestTarget { id, deps })
                })
                .collect(),
        }
    }
}

#[tokio::test]
async fn test_one_path() -> anyhow::Result<()> {
    let mut env = TestEnvBuilder::default();
    // The actual path
    env.edge(1, 2);
    env.edge(2, 3);
    // Some unused edges
    env.edge(3, 4);
    env.edge(1, 10);
    env.edge(1, 12);
    let env = env.build();

    let path = env.allpaths(&env.set("1")?, &env.set("3")?).await?;
    let expected = env.set("1,2,3")?;
    assert_eq!(path, expected);

    let path = env.somepath(&env.set("1")?, &env.set("3")?).await?;
    let expected = env.set("3,2,1")?;
    assert_eq!(path, expected);

    Ok(())
}

#[tokio::test]
async fn test_many_paths() -> anyhow::Result<()> {
    let mut env = TestEnvBuilder::default();
    env.edge(1, 2);
    env.edge(2, 3);
    env.edge(1, 10);
    env.edge(10, 11);
    env.edge(11, 3);
    // More unused edges
    env.edge(3, 4);
    env.edge(10, 20);
    let env = env.build();

    let path = env.allpaths(&env.set("1")?, &env.set("3")?).await?;
    let expected = env.set("1,10,11,2,3")?;
    assert_eq!(path, expected);

    // We iterate with a stack so this is why we find this path
    let path = env.somepath(&env.set("1")?, &env.set("3")?).await?;
    let expected = env.set("3,11,10,1")?;
    assert_eq!(path, expected);

    Ok(())
}

#[tokio::test]
async fn test_distinct_paths() -> anyhow::Result<()> {
    let mut env = TestEnvBuilder::default();
    env.edge(1, 10);
    env.edge(10, 100);
    env.edge(2, 20);
    env.edge(20, 200);
    let env = env.build();

    let path = env.allpaths(&env.set("1,2")?, &env.set("100,200")?).await?;
    let expected = env.set("2,20,200,1,10,100")?;
    assert_eq!(path, expected);

    // Same as above
    let path = env.somepath(&env.set("1,2")?, &env.set("100,200")?).await?;
    let expected = env.set("200,20,2")?;
    assert_eq!(path, expected);

    Ok(())
}

#[tokio::test]
async fn test_no_path() -> anyhow::Result<()> {
    let mut env = TestEnvBuilder::default();
    env.edge(1, 10);
    env.edge(2, 20);
    let env = env.build();

    let path = env.allpaths(&env.set("1")?, &env.set("20")?).await?;
    let expected = TargetSet::new();
    assert_eq!(path, expected);

    let path = env.somepath(&env.set("1")?, &env.set("20")?).await?;
    let expected = TargetSet::new();
    assert_eq!(path, expected);

    Ok(())
}

#[tokio::test]
async fn test_nested_paths() -> anyhow::Result<()> {
    let mut env = TestEnvBuilder::default();
    env.edge(1, 2);
    env.edge(2, 3);
    env.edge(3, 4);
    let env = env.build();

    let path = env.allpaths(&env.set("1")?, &env.set("2,4")?).await?;
    assert_eq!(path, env.set("1,2,3,4")?);

    let path = env.somepath(&env.set("1")?, &env.set("2,4")?).await?;
    assert_eq!(path, env.set("2,1")?);

    Ok(())
}

#[tokio::test]
async fn test_paths_with_cycles_present() -> anyhow::Result<()> {
    let mut env = TestEnvBuilder::default();
    env.edge(1, 2);
    env.edge(2, 3);
    env.edge(3, 4);
    env.edge(4, 5);
    // Introduce cycles.
    env.edge(4, 1);
    env.edge(4, 3);
    let env = env.build();

    let path = env.allpaths(&env.set("3")?, &env.set("4")?).await?;
    assert_eq!(path, env.set("1,2,3,4")?);

    let path = env.allpaths(&env.set("1")?, &env.set("1")?).await?;
    assert_eq!(path, env.set("2,3,4,1")?);

    let path = env.allpaths(&env.set("1")?, &env.set("5")?).await?;
    assert_eq!(path, env.set("1,2,3,4,5")?);

    let path = env.rdeps(&env.set("1")?, &env.set("3")?, Some(2)).await?;
    assert_eq!(path, env.set("3,2,4,1")?);

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

pub mod simple;


/*
 * 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 eval;
pub mod functions;


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

//! Implementation of the cli and query_* attr query language.

use buck2_core::fs::project::ProjectRoot;
use buck2_query_parser::spanned::Spanned;
use thiserror::Error;

/// While this is a std Error type, we generally don't use it directly. It's instead wrapped in a Spanned and can be converted to a normal error
/// with QueryError::convert_error (which will resolve the spans to context messages).
#[derive(Debug, Error)]
pub enum QueryError {
    #[error("unknown function `{0}`")]
    UnknownFunction(String),
    #[error("binary op `{0}` unsupported in this context")]
    UnsupportedBinaryOp(String),
    #[error("expected a literal, got value of type `{actual}`")]
    ExpectedLiteral { actual: &'static str },
    #[error("expected value of type `{expected}`, got `{actual}`")]
    InvalidType {
        expected: &'static str,
        actual: &'static str,
    },
    #[error("too many args. function `{function}` accepts maximum {max} args, got {actual}")]
    TooManyArgs {
        function: String,
        max: usize,
        actual: usize,
    },
    #[error("too few args. function `{function}` requires at least {min} args, got {actual}")]
    TooFewArgs {
        function: String,
        min: usize,
        actual: usize,
    },
    #[error("function `{0}` is not implemented yet")]
    FunctionUnimplemented(&'static str),
    #[error("Argument `{1}` to `{0}` is not yet supported in buck2")]
    ArgNotYetSupported(String, String),
    #[error("Invalid traversal depth `{0}`")]
    InvalidDepth(i32),
    #[error("File literal `{1}` not within the project root `{}`", .0)]
    FileLiteralNotInProject(ProjectRoot, String),
    #[error("query function {0} not available in this context")]
    NotAvailableInContext(&'static str),
    #[error(
        "Operation + requires either two set types, or one set and one string, got `{0}` and `{1}`"
    )]
    UnionIncompatibleTypes(&'static str, &'static str),
    /// Used to propagate up an inner error. The inner span will mark where the inner error was (which itself may be the
    /// propagation of another error). This error will end up in a Spanned that indicates where this error (the propagation) occurs.
    /// Since QueryError has an impl for `From<Spanned<QueryError>>`, just propagating inner eval errors via `?` will hit this case (and
    /// so will actually end up not just propagating the inner error but wrapping it in this and adding an additional Spanned to it).
    #[error("{0}")]
    Inner(Box<Spanned<QueryError>>),
    /// Used to propagate an inner error. Generally this would happen when we are calling something outside the evaluation
    /// (ex. target parsing). That inner error won't have span information and so the innermost span information will be the one
    /// attached to this error.
    #[error("{0}")]
    Anyhow(anyhow::Error),
}

impl From<anyhow::Error> for QueryError {
    fn from(err: anyhow::Error) -> Self {
        QueryError::Anyhow(err)
    }
}

impl From<Spanned<QueryError>> for QueryError {
    fn from(value: Spanned<QueryError>) -> Self {
        QueryError::Inner(Box::new(value))
    }
}

impl QueryError {
    pub fn drop_spans(err: Spanned<Self>) -> anyhow::Error {
        match err.value {
            Self::Anyhow(inner) => inner,
            Self::Inner(inner) => Self::drop_spans(*inner),
            e => {
                // TODO(cjhopman): This is going to drop the backtrace attached to the error, we should figure
                // out how to keep that.
                anyhow::anyhow!(e)
            }
        }
    }
    pub fn convert_error(err: Spanned<Self>, input: &str) -> anyhow::Error {
        let context = err.get_err_context(input);

        match err.value {
            Self::Anyhow(inner) => {
                inner.context(format!("Error evaluating expression:{}", context))
            }
            Self::Inner(inner) => Self::convert_error(*inner, input)
                .context(format!("Error evaluating expression:{}", context)),
            e => {
                // TODO(cjhopman): This is going to drop the backtrace attached to the error, we should figure
                // out how to keep that.
                anyhow::anyhow!("{}:{}", e, context)
            }
        }
    }
}


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

//! Implementation of the cli and query_* attr query language.

use buck2_query_parser::parse_expr;
use buck2_query_parser::spanned::Spanned;
use buck2_query_parser::Expr;
use futures::FutureExt;
use gazebo::prelude::*;
use gazebo::variants::VariantName;

use crate::__derive_refs::indexmap::IndexSet;
use crate::query::environment::QueryEnvironment;
use crate::query::syntax::simple::eval::error::QueryError;
use crate::query::syntax::simple::eval::file_set::FileNode;
use crate::query::syntax::simple::eval::file_set::FileSet;
use crate::query::syntax::simple::eval::set::TargetSet;
use crate::query::syntax::simple::eval::values::QueryEvaluationValue;
use crate::query::syntax::simple::eval::values::QueryResult;
use crate::query::syntax::simple::eval::values::QueryValue;
use crate::query::syntax::simple::functions::QueryFunctions;
pub struct QueryEvaluator<'e, Env: QueryEnvironment> {
    env: &'e Env,
    functions: &'e dyn QueryFunctions<Env = Env>,
}

impl<'e, Env: QueryEnvironment> QueryEvaluator<'e, Env> {
    pub fn new(env: &'e Env, functions: &'e dyn QueryFunctions<Env = Env>) -> Self {
        Self { env, functions }
    }

    pub fn env(&self) -> &Env {
        self.env
    }

    pub fn functions(&self) -> &dyn QueryFunctions<Env = Env> {
        self.functions
    }

    async fn resolve_literal(&self, literal: &str) -> anyhow::Result<TargetSet<Env::Target>> {
        self.env.eval_literals(&[literal]).await
    }

    async fn eval_internal(&self, expr: &Expr<'_>) -> Result<QueryValue<Env::Target>, QueryError> {
        // TODO(cjhopman): We should extract these functions to a map of name->functionobj and attach
        // more information to them like documentation and signature. Potentially we could generalize
        // the function impls there to work across bxl and here, but not sure if that's worth the
        // complexity or not.
        match expr {
            Expr::String(word) => Ok(QueryValue::String((*word).to_owned())),
            Expr::Integer(value) => Ok(QueryValue::Integer(*value)),
            Expr::Function {
                function_name,
                args,
            } => match self.functions.get(function_name) {
                Some(func) => func.invoke(self, args).await,
                None => Err(QueryError::UnknownFunction(
                    (*function_name.fragment()).to_owned(),
                )),
            },
            Expr::BinaryOpSequence(left, exprs) => {
                let (left, rights) = futures::future::try_join(
                    self.eval(left),
                    futures::future::try_join_all(exprs.iter().map(|(op, expr)| async move {
                        let value = self.eval(expr).await?;
                        Ok((op, value))
                    })),
                )
                .await?;
                let mut value = left.value;
                for (op, right) in rights {
                    value = right
                        .async_into_map_res(|right| async move {
                            match self.functions.get_op(*op) {
                                Some(func) => func.invoke(self.env(), value, right).await,
                                None => Err(QueryError::UnsupportedBinaryOp(op.to_string())),
                            }
                        })
                        .await?
                        .value;
                }
                Ok(value)
            }
            Expr::Set(args) => {
                let patterns: Vec<_> = args.map(|v| v.fragment());
                // TODO(cjhopman): evaluating the literals in this way does not preserver the ordering from
                // the user, instead the result will be package-ordered. We may need to change this to
                // preserve order.
                Ok(self.env.eval_literals(&patterns).await?.into())
            }
            Expr::FileSet(args) => {
                let patterns: Vec<_> = args.map(|v| v.fragment());
                let mut files = FileSet::new(IndexSet::<FileNode>::new());

                for pattern in patterns {
                    files.insert_all(&self.env.eval_file_literal(pattern).await?)
                }

                Ok(files.into())
            }
        }
    }

    pub fn eval<'a>(
        &'a self,
        expr: &'a Spanned<Expr<'a>>,
    ) -> std::pin::Pin<
        Box<dyn std::future::Future<Output = QueryResult<QueryValue<Env::Target>>> + Send + 'a>,
    > {
        async move { expr.span(self.eval_internal(&expr.value).await) }.boxed()
    }

    pub async fn eval_query<'a>(
        &self,
        query: &str,
    ) -> anyhow::Result<QueryEvaluationValue<Env::Target>> {
        let parsed_query = parse_expr(query)?;
        match self.eval_parsed_query(&parsed_query).await {
            Ok(v) => Ok(v.value),
            Err(e) => Err(QueryError::convert_error(e, query)),
        }
    }

    pub async fn eval_parsed_query<'a>(
        &self,
        expr: &Spanned<Expr<'a>>,
    ) -> QueryResult<QueryEvaluationValue<Env::Target>> {
        self.eval(expr)
            .await?
            .async_into_map_res(async move |value| {
                match value {
                    // A top-level string we treat as a target pattern and resolve it. This allows something like
                    // `buck2 query //lib/...` to resolve to the corresponding targets.
                    QueryValue::String(word) => Ok(QueryEvaluationValue::TargetSet(
                        self.resolve_literal(&word).await?,
                    )),
                    QueryValue::TargetSet(targets) => Ok(QueryEvaluationValue::TargetSet(targets)),
                    QueryValue::FileSet(files) => Ok(QueryEvaluationValue::FileSet(files)),
                    _ => Err(QueryError::InvalidType {
                        expected: "targets",
                        actual: value.variant_name(),
                    }),
                }
            })
            .await
    }
}


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

use std::fmt;

use allocative::Allocative;
use buck2_core::cells::cell_path::CellPath;
use derive_more::Display;
use display_container::fmt_container;
use fancy_regex::Regex;
use indexmap::IndexSet;

use crate::query::environment::QueryEnvironment;
use crate::query::environment::QueryTarget;
use crate::query::syntax::simple::eval::error::QueryError;
use crate::query::syntax::simple::eval::set::TargetSet;

/// An entry in a FileSet.
#[derive(Hash, Eq, PartialEq, Debug, Clone, Display, Allocative)]
pub struct FileNode(pub CellPath);

#[derive(Debug, Eq, PartialEq, Clone, Allocative)]
pub struct FileSet {
    files: IndexSet<FileNode>,
}

impl fmt::Display for FileSet {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt_container(f, "[", "]", self.files.iter())
    }
}

impl FileSet {
    pub fn new(files: IndexSet<FileNode>) -> Self {
        Self { files }
    }

    pub(crate) fn filter_name(&self, regex: &str) -> anyhow::Result<Self> {
        let re = Regex::new(regex)?;
        self.filter(|node| Ok(re.is_match(&node.0.to_string())?))
    }

    fn filter<F: Fn(&FileNode) -> anyhow::Result<bool>>(&self, filter: F) -> anyhow::Result<Self> {
        let mut files = IndexSet::new();
        for file in self.files.iter() {
            if filter(file)? {
                files.insert(file.clone());
            }
        }
        Ok(Self { files })
    }

    pub fn insert(&mut self, file: FileNode) {
        self.files.insert(file);
    }

    pub fn insert_all(&mut self, other: &FileSet) {
        for v in other.files.iter() {
            self.files.insert(v.clone());
        }
    }

    pub fn union(&self, right: &FileSet) -> FileSet {
        let mut files = self.files.clone();
        for file in right.files.iter() {
            files.insert(file.clone());
        }
        Self { files }
    }

    pub fn owner<T: QueryTarget>(
        &self,
        _env: &dyn QueryEnvironment<Target = T>,
    ) -> anyhow::Result<TargetSet<T>> {
        Err(anyhow::anyhow!(QueryError::FunctionUnimplemented(
            "owner()"
        )))
    }

    pub fn iter(&self) -> impl Iterator<Item = &CellPath> + '_ {
        self.files.iter().map(|v| &v.0)
    }

    pub fn get_index(&self, index: usize) -> Option<&CellPath> {
        match self.files.get_index(index) {
            Some(x) => Some(&x.0),
            None => None,
        }
    }

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

impl FromIterator<FileNode> for FileSet {
    fn from_iter<T: IntoIterator<Item = FileNode>>(iter: T) -> FileSet {
        FileSet {
            files: IndexSet::from_iter(iter),
        }
    }
}