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

use buck2_build_api::interpreter::rule_defs::cmd_args::value_as::ValueAsCommandLineLike;
use buck2_build_api::interpreter::rule_defs::cmd_args::SimpleCommandLineArtifactVisitor;
use buck2_build_api::interpreter::rule_defs::cmd_args::StarlarkCommandLineInputs;
use buck2_build_api::interpreter::rule_defs::register_rule_defs;
use buck2_common::result::SharedResult;
use buck2_core::bzl::ImportPath;
use buck2_interpreter_for_build::interpreter::testing::expect_error;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use buck2_interpreter_for_build::label::testing::label_creator;
use indoc::indoc;
use starlark::environment::GlobalsBuilder;
use starlark::starlark_module;
use starlark::values::Value;

use crate::interpreter::rule_defs::artifact::testing::artifactory;
use crate::interpreter::rule_defs::cmd_args::testing;

#[starlark_module]
pub(crate) fn inputs_helper(builder: &mut GlobalsBuilder) {
    fn make_inputs<'v>(values: Vec<Value<'v>>) -> anyhow::Result<StarlarkCommandLineInputs> {
        let mut visitor = SimpleCommandLineArtifactVisitor::new();
        for v in values {
            let cli = v.as_command_line_err()?;
            cli.visit_artifacts(&mut visitor)?;
        }

        Ok(StarlarkCommandLineInputs {
            inputs: visitor.inputs,
        })
    }
}

fn tester() -> anyhow::Result<Tester> {
    let mut tester = Tester::new()?;
    tester.additional_globals(testing::command_line_stringifier);
    tester.additional_globals(inputs_helper);
    tester.additional_globals(artifactory);
    tester.additional_globals(label_creator);
    tester.additional_globals(register_rule_defs);
    Ok(tester)
}

#[test]
fn stringifies_correctly() -> SharedResult<()> {
    let mut tester = tester()?;
    tester.run_starlark_bzl_test(indoc!(
        r#"
        artifact1 = source_artifact("foo", "bar/baz.h")
        artifact2 = bound_artifact("//:dep1", "dir/baz.h")
        arg2 = "string1"
        arg3 = artifact1
        arg4 = artifact2
        arg5 = str(label("//foo:bar[baz]"))

        def test():
            artifact3 = source_artifact("foo", "bar/quz.h")
            artifact4 = bound_artifact("//:dep2", "dir/quz.h")
            arg7 = "string2"
            arg8 = artifact3
            arg9 = artifact4
            arg10 = str(label("//foo:bar[quz]"))

            assert_eq("string1", stringify_cli_arg(arg2))
            assert_eq("foo/bar/baz.h", stringify_cli_arg(arg3))
            assert_eq_ignore_hash("buck-out/v2/gen/root/<HASH>/__dep1__/dir/baz.h", stringify_cli_arg(arg4))
            assert_eq_ignore_hash("root//foo:bar[baz] (<testing>#<HASH>)", stringify_cli_arg(arg5))
            assert_eq("string2", stringify_cli_arg(arg7))
            assert_eq("foo/bar/quz.h", stringify_cli_arg(arg8))
            assert_eq_ignore_hash("buck-out/v2/gen/root/<HASH>/__dep2__/dir/quz.h", stringify_cli_arg(arg9))
            assert_eq_ignore_hash("root//foo:bar[quz] (<testing>#<HASH>)", stringify_cli_arg(arg10))
        "#
    ))?;

    let contents = indoc!(
        r#"
        def test():
            arg = stringify_cli_arg(["list of strings aren't valid"])
        "#
    );

    expect_error(
        tester.run_starlark_bzl_test(contents),
        contents,
        "expected command line item to be a string",
    );

    Ok(())
}

#[test]
fn displays_correctly() -> SharedResult<()> {
    let mut tester = tester()?;
    tester.run_starlark_bzl_test(indoc!(
        r#"
        def test():
            cli = cmd_args()
            cli.add("foo")
            cli.hidden("bar")
            assert_eq('cmd_args("foo", hidden=["bar"])', str(cli))
            assert_eq('cmd_args(\n  "foo",\n  hidden=[ "bar" ]\n)', pprint_str(cli))
        "#
    ))?;

    Ok(())
}

#[test]
fn command_line_builder() -> SharedResult<()> {
    let mut tester = tester()?;
    let content = indoc!(
        r#"
        b1 = cmd_args()
        b2 = cmd_args()
        b3 = cmd_args()
        b4 = cmd_args()
        b5 = cmd_args()

        b2.add("b2 s1").add(["b2 s2"])
        b3.add("b3 s1").add(["b3 s2"])

        a1 = source_artifact("foo", "bar1/baz.h")
        a2 = bound_artifact("//:dep1", "dir/baz.h")
        a3 = source_artifact("foo", "bar2/baz.h")
        a4 = bound_artifact("//:dep2", "dir/baz.h")
        l1 = str(label("//foo:bar[baz]"))

        b1.add("b1 s1", "b1 s2").add(b2).add(a1).add(a2)
        b1.add([b3, l1]).add([a3, a4])

        # Add something so we can verify it doesn't modify b1
        b2 = b2.copy()
        b2.add("not in b1")

        # Ensure that both lists, and *args formats work
        b4.add(cmd_args(["--foo=", a1, ",bar=", b3, ",baz"], delimiter = ""))
        b4.add(cmd_args("--foo=", a1, ",bar=", b3, ",baz", delimiter = ""))

        # Make sure delimiters work properly
        b5.add(cmd_args("foo", delimiter = ""))
        b5.add(cmd_args("foo", "bar", delimiter=","))
        b5.add(cmd_args("foo", "bar", "baz", delimiter=","))

        mutable_b1_args = get_args(b1)
        mutable_b2_args = get_args(b2)
        mutable_b3_args = get_args(b3)
        mutable_b4_args = get_args(b4)
        mutable_b5_args = get_args(b5)

        def test():
            b1_args_expected = [
                "b1 s1",
                "b1 s2",
                "b2 s1",
                "b2 s2",
                "foo/bar1/baz.h",
                "buck-out/v2/gen/root/<HASH>/__dep1__/dir/baz.h",
                "b3 s1",
                "b3 s2",
                "root//foo:bar[baz] (<testing>#<HASH>)",
                "foo/bar2/baz.h",
                "buck-out/v2/gen/root/<HASH>/__dep2__/dir/baz.h",
            ]
            b2_args_expected = [
                "b2 s1",
                "b2 s2",
                "not in b1",
            ]
            b3_args_expected = [
                "b3 s1",
                "b3 s2",
            ]
            b4_args_expected = [
                "--foo=foo/bar1/baz.h,bar=b3 s1b3 s2,baz",
                "--foo=foo/bar1/baz.h,bar=b3 s1b3 s2,baz",
            ]
            b5_args_expected = [
                "foo",
                "foo,bar",
                "foo,bar,baz",
            ]

            # Check values while builders were mutable
            assert_eq_ignore_hash(b1_args_expected, mutable_b1_args)

            assert_eq_ignore_hash(b2_args_expected, mutable_b2_args)

            assert_eq_ignore_hash(b3_args_expected, mutable_b3_args)

            assert_eq_ignore_hash(b4_args_expected, mutable_b4_args)

            assert_eq_ignore_hash(b5_args_expected, mutable_b5_args)

            # Check that frozen values still work
            frozen_b1_args = get_args(b1)
            frozen_b2_args = get_args(b2)
            frozen_b3_args = get_args(b3)
            frozen_b4_args = get_args(b4)
            frozen_b5_args = get_args(b5)

            assert_eq_ignore_hash(b1_args_expected, frozen_b1_args)

            assert_eq_ignore_hash(b2_args_expected, frozen_b2_args)

            assert_eq_ignore_hash(b3_args_expected, frozen_b3_args)

            assert_eq_ignore_hash(b4_args_expected, frozen_b4_args)

            assert_eq_ignore_hash(b5_args_expected, frozen_b5_args)

            # Make sure we can add frozen CLIs to unfrozen ones
            b6 = cmd_args()
            b6.add("b6").add(b2).add([b3])

            b6_args_expected = ["b6"] + b2_args_expected + b3_args_expected
            assert_eq(b6_args_expected, get_args(b6))
        "#
    );

    tester.run_starlark_bzl_test(content)?;

    let content_invalid_type_1 = r#"cmd_args().add({"not": "an arg"})"#;
    // TODO(ndmitchel): We claim you can add labels to a command line, but you can't
    let content_invalid_type_2 = r#"cmd_args().add(label("//:foo"))"#;
    let content_invalid_type_3 = r#"cmd_args().add([{"not": "an arg"}])"#;

    expect_error(
        tester.run_starlark_bzl_test(content_invalid_type_1),
        content_invalid_type_1,
        "expected command line item",
    );
    expect_error(
        tester.run_starlark_bzl_test(content_invalid_type_2),
        content_invalid_type_2,
        "expected command line item",
    );
    expect_error(
        tester.run_starlark_bzl_test(content_invalid_type_3),
        content_invalid_type_3,
        "expected command line item",
    );

    Ok(())
}

#[test]
fn test_relative_absolute() -> anyhow::Result<()> {
    let mut tester = tester()?;
    let contents = indoc!(
        r#"
        def test():
            args = cmd_args()
            args.add(source_artifact("foo","bar/baz/qux.h"))
            args.relative_to(source_artifact("foo", "bar/foo"))
            args.absolute_prefix("$ABSOLUTE/")
            assert_eq(get_args(args), ["$ABSOLUTE/../baz/qux.h"])

            args.absolute_suffix("!")
            assert_eq(get_args(args), ["$ABSOLUTE/../baz/qux.h!"])

            args = cmd_args()
            args.add(source_artifact("foo","bar/baz/qux.h"))
            args.relative_to(source_artifact("foo", "bar/baz"), parent=1)
            assert_eq(get_args(args), ["baz/qux.h"])
            "#
    );
    tester.run_starlark_bzl_test(contents)?;
    Ok(())
}

#[test]
fn test_parent() -> anyhow::Result<()> {
    let mut tester = tester()?;
    let contents = indoc!(
        r#"
        def test():
            args = cmd_args()
            args.add(source_artifact("foo","bar/baz/qux.h"))
            args.parent().absolute_suffix("!")
            assert_eq(get_args(args), ["foo/bar/baz!"])
        "#
    );
    tester.run_starlark_bzl_test(contents)?;

    let too_many_parent_calls = indoc!(
        r#"
        def test():
            args = cmd_args()
            args.add(source_artifact("foo","qux.h"))
            args.parent().parent().parent()
            get_args(args)
        "#
    );
    expect_error(
        tester.run_starlark_bzl_test(too_many_parent_calls),
        too_many_parent_calls,
        "too many .parent() calls",
    );

    Ok(())
}

#[test]
fn test_parent_n() -> anyhow::Result<()> {
    let mut tester = tester()?;
    let contents = indoc!(
        r#"
        def test():
            args = cmd_args()
            args.add(source_artifact("foo","bar/baz/qux.h"))
            args.parent(2).absolute_suffix("!")
            assert_eq(get_args(args), ["foo/bar!"])
        "#
    );
    tester.run_starlark_bzl_test(contents)?;

    let too_many_parent_calls = indoc!(
        r#"
        def test():
            args = cmd_args()
            args.add(source_artifact("foo","qux.h"))
            args.parent(3)
            get_args(args)
        "#
    );
    expect_error(
        tester.run_starlark_bzl_test(too_many_parent_calls),
        too_many_parent_calls,
        "too many .parent() calls",
    );

    let bad_count = indoc!(
        r#"
        def test():
            args = cmd_args()
            args.add(source_artifact("foo","qux.h"))
            args.parent(-12)
            get_args(args)
        "#
    );
    expect_error(
        tester.run_starlark_bzl_test(bad_count),
        bad_count,
        "Type of parameter `count` doesn't match",
    );

    Ok(())
}

#[test]
fn test_format() -> anyhow::Result<()> {
    let mut tester = tester()?;
    let contents = indoc!(
        r#"
        def test():
            args1 = cmd_args(delimiter="", format="format-{}-{}-string")
            args2 = cmd_args(format="format-{}-{}-string")
            args3 = cmd_args()

            args1.add("foo")
            args1.add(",bar")
            args2.add("foo")
            args2.add("bar")
            args3.add(cmd_args(["foo", "bar"], format="format-{}-{}-string", delimiter = ""))
            args3.add(cmd_args(["foo", "bar"], delimiter=",", format="format-{}-{}-string"))
            args3.add(cmd_args("foo", format="format-{}-{}-string1"))
            args3.add(cmd_args("bar", format="format-{}-{}-string2"))

            assert_eq(["format-foo-foo-stringformat-,bar-,bar-string"], get_args(args1))
            assert_eq(
                [
                    "format-foo-foo-string",
                    "format-bar-bar-string",
                ],
                get_args(args2),
            )
            assert_eq(
                [
                    "format-foo-foo-stringformat-bar-bar-string",
                    "format-foo-foo-string,format-bar-bar-string",
                    "format-foo-foo-string1",
                    "format-bar-bar-string2",
                ],
                get_args(args3),
            )
        "#
    );
    tester.run_starlark_bzl_test(contents)?;
    Ok(())
}

#[test]
fn test_joined_with_empty_args() -> anyhow::Result<()> {
    let mut tester = tester()?;
    let contents = indoc!(
        r#"
        def test():
            args = cmd_args()
            args.add(cmd_args(["", "foo"], delimiter=",", format="format-{}-string"))
            args.add(cmd_args(["", "", "foo"], delimiter=","))
            assert_eq(
                [
                    "format--string,format-foo-string",
                    ",,foo",
                ],
                get_args(args),
            )
        "#
    );
    tester.run_starlark_bzl_test(contents)?;
    Ok(())
}

#[test]
fn test_inputs_outputs() -> anyhow::Result<()> {
    let mut tester = tester()?;
    tester.run_starlark_bzl_test(indoc!(
        r#"
        artifact1 = source_artifact("foo", "bar/baz.h")
        artifact2 = bound_artifact("//:dep1", "dir/baz.h")

        def norm(xs):
            # Try and make everything consistent for the equality test
            return sorted([repr(x) for x in xs])

        def test():
            artifact3 = source_artifact("foo", "bar/quz.h")
            artifact4 = bound_artifact("//:dep2", "dir/quz.h")
            artifact5 = declared_artifact("declared")

            cli = cmd_args()
            cli.add(artifact3)
            cli.add("just a string")
            cli.add(artifact4)
            cli.hidden(artifact1)
            cli.add(artifact5.as_output())

            assert_eq(make_inputs([artifact3, artifact4, artifact1]), cli.inputs)
            assert_eq(3, len(cli.inputs))
            assert_eq(norm([artifact5.as_output()]), norm(cli.outputs))
        "#
    ))?;
    Ok(())
}

#[test]
fn test_ignore_artifacts() -> anyhow::Result<()> {
    let mut tester = tester()?;
    tester.run_starlark_bzl_test(indoc!(
        r#"
        def norm(xs):
            # Try and make everything consistent for the equality test
            return sorted([repr(x) for x in xs])

        def test():
            artifact = bound_artifact("//:dep2", "dir/quz.h")

            cli = cmd_args()
            cli.add(artifact)
            cli.ignore_artifacts()

            assert_eq(make_inputs([]), cli.inputs)
            assert_eq([], cli.outputs)

            assert_eq_ignore_hash(["buck-out/v2/gen/root/<HASH>/__dep2__/dir/quz.h"], get_args(cli))
        "#
    ))?;
    Ok(())
}

#[test]
fn test_frozen_inputs_outputs() -> anyhow::Result<()> {
    let mut tester = tester()?;

    tester.add_import(
        &ImportPath::testing_new("root//test:def1.bzl"),
        indoc!(
            r#"
            def norm(xs):
                # Try and make everything consistent for the equality test
                return sorted([repr(x) for x in xs])

            input = source_artifact("foo", "bar/quz.h")
            output = declared_bound_artifact("//foo:bar", "declared")

            cli = cmd_args()
            cli.add("string")
            cli.add(input)
            cli.add(output.as_output())

            expected_outputs = norm([output.as_output()])
            expected_inputs = make_inputs([input])
            "#
        ),
    )?;

    tester.run_starlark_bzl_test(indoc!(
        r#"
        load("//test:def1.bzl", "cli", "expected_inputs", "expected_outputs", "norm")

        def test():
            assert_eq(expected_inputs, cli.inputs)
            assert_eq(expected_outputs, norm(cli.outputs))
        "#
    ))?;
    Ok(())
}

#[test]
fn test_quote_style_shell() -> anyhow::Result<()> {
    let mut tester = tester()?;
    let contents = indoc!(
        r#"
        def test():
            args = cmd_args()
            args.add(cmd_args("$HELLO", quote = "shell"))
            assert_eq(get_args(args), ["\"\\$HELLO\""])

            args = cmd_args()
            args.add(cmd_args(source_artifact("foo", "bar$qux.h"), quote = "shell"))
            assert_eq(get_args(args), ["\"foo/bar\\$qux.h\""])
            "#
    );
    tester.run_starlark_bzl_test(contents)?;
    Ok(())
}

#[test]
fn test_prepend() -> anyhow::Result<()> {
    let mut tester = tester()?;
    let contents = indoc!(
        r#"
        def test():
            args = cmd_args()
            args.add([["foo", "bar"], "baz"])
            assert_eq(["foo", "bar", "baz"], get_args(args))
            "#
    );
    tester.run_starlark_bzl_test(contents)?;
    Ok(())
}

#[test]
fn test_list_list() -> anyhow::Result<()> {
    let mut tester = tester()?;
    let contents = indoc!(
        r#"
        def test():
            args = cmd_args()
            args.add(cmd_args(["foo", "bar"], prepend = "-X"))
            assert_eq(["-X", "foo", "-X", "bar"], get_args(args))
            "#
    );
    tester.run_starlark_bzl_test(contents)?;
    Ok(())
}

#[test]
fn test_concat() -> anyhow::Result<()> {
    let mut tester = tester()?;
    let contents = indoc!(
        r#"
        def test():
            args = cmd_args("foo", "bar", delimiter = "-")
            assert_eq(["foo-bar"], get_args(args))
            args = cmd_args("foo", "bar", delimiter = "-", format = "({})")
            assert_eq(["(foo)-(bar)"], get_args(args))
            args = cmd_args("foo", "bar", delimiter = "-", prepend = "@", format = "({})")
            assert_eq(["@(foo)-@(bar)"], get_args(args))
            args = cmd_args("foo", "bar", delimiter = "-", prepend = "@", format = "({})", quote="shell")
            assert_eq(['@"(foo)"-@"(bar)"'], get_args(args))

            # Nail down the order of application
            # Note that prepend and delimiter together are not separable
            args = cmd_args(cmd_args(cmd_args("foo", "bar", format="({})"), quote="shell"), prepend="@", delimiter="-")
            assert_eq(['@"(foo)"-@"(bar)"'], get_args(args))
            args = cmd_args(cmd_args(cmd_args(cmd_args("foo", "bar", format="({})"), quote="shell"), prepend="@"), delimiter="-")
            assert_eq(['@-"(foo)"-@-"(bar)"'], get_args(args))
            args = cmd_args(cmd_args(cmd_args(cmd_args("foo", "bar", format="({})"), quote="shell"), delimiter="-"), prepend="@")
            assert_eq(['@', '"(foo)"-"(bar)"'], get_args(args))
     "#
    );
    tester.run_starlark_bzl_test(contents)?;
    Ok(())
}

#[test]
fn test_replace_regex() -> anyhow::Result<()> {
    let mut tester = tester()?;
    let contents = indoc!(
        r#"
        def test():
            args = cmd_args("$OUT", "$OUTPUT", "$SRCS", format="$OUT: {}")
            args.replace_regex("\\$OUT\\b", "%OUT%")
            args.replace_regex("\\$SRCS\\b", "%SRCS%")
            assert_eq(["$OUT: %OUT%", "$OUT: $OUTPUT", "$OUT: %SRCS%"], get_args(args))

            args = cmd_args("\\n\n")
            args.replace_regex("\\\\n", "\\\n").replace_regex("\\n", "\\n")
            assert_eq(["\\\\n\\n"], get_args(args))
        "#
    );
    tester.run_starlark_bzl_test(contents)?;
    Ok(())
}


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

use buck2_build_api::interpreter::build_defs::register_provider;
use buck2_build_api::interpreter::rule_defs::provider::collection::tester::collection_creator;
use buck2_build_api::interpreter::rule_defs::register_rule_defs;
use buck2_common::result::SharedResult;
use buck2_core::bzl::ImportPath;
use buck2_interpreter_for_build::interpreter::testing::expect_error;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

use crate::interpreter::rule_defs::artifact::testing::artifactory;

fn provider_collection_tester() -> SharedResult<Tester> {
    let mut tester = Tester::new()?;
    tester.additional_globals(collection_creator);
    tester.additional_globals(artifactory);
    tester.additional_globals(register_rule_defs);
    tester.additional_globals(register_provider);
    tester.add_import(
        &ImportPath::testing_new("root//provider:defs1.bzl"),
        indoc!(
            r#"
                    FooInfo = provider(fields=["foo"])
                    BarInfo = provider(fields=["bar"])
                    BazInfo = provider(fields=["baz"])
                    "#
        ),
    )?;
    tester.add_import(
        &ImportPath::testing_new("root//provider:defs2.bzl"),
        indoc!(
            r#"
                    load("//provider:defs1.bzl", "FooInfo", "BarInfo", "BazInfo")
                    foo1 = FooInfo(foo="foo1")
                    foo2 = FooInfo(foo="foo2")
                    bar1 = BarInfo(bar="bar1")
                    baz1 = BazInfo(baz="baz1")
                    "#
        ),
    )?;

    Ok(tester)
}

#[test]
fn provider_collection_constructs_properly() -> SharedResult<()> {
    let mut tester = provider_collection_tester()?;
    tester.run_starlark_bzl_test(indoc!(
        r#"
            load("//provider:defs1.bzl", "FooInfo", "BarInfo", "BazInfo")
            load("//provider:defs2.bzl", "foo1", "bar1")
            def test():
                col = create_collection([foo1, bar1, DefaultInfo()])
                assert_eq(None, col.get(BazInfo))
                assert_eq("foo1", col[FooInfo].foo)
                assert_eq("bar1", col[BarInfo].bar)
                assert_eq([], col[DefaultInfo].default_outputs)
            "#
    ))?;
    Ok(())
}

#[test]
fn provider_collection_fails_to_construct_on_bad_data() -> SharedResult<()> {
    let mut tester = provider_collection_tester()?;
    let not_a_list = indoc!(
        r#"
            load("//provider:defs2.bzl", "foo1")
            def test():
                create_collection(foo1)
            "#
    );
    expect_error(
        tester.run_starlark_bzl_test(not_a_list),
        not_a_list,
        "expected a list of Provider objects",
    );

    let mut tester = provider_collection_tester()?;
    let not_a_provider = indoc!(
        r#"
            load("//provider:defs2.bzl", "foo1", "bar1")
            def test():
                create_collection([foo1, bar1, "not a provider", DefaultInfo()])
            "#
    );
    expect_error(
        tester.run_starlark_bzl_test(not_a_provider),
        not_a_provider,
        "expected a Provider object",
    );

    let mut tester = provider_collection_tester()?;
    let duplicate_provider_types = indoc!(
        r#"
            load("//provider:defs2.bzl", "foo1", "foo2", "bar1")
            def test():
                create_collection([foo1, bar1, foo2, DefaultInfo()])
            "#
    );
    expect_error(
        tester.run_starlark_bzl_test(duplicate_provider_types),
        duplicate_provider_types,
        "specified twice",
    );

    let mut tester = provider_collection_tester()?;
    let missing_default_info = indoc!(
        r#"
            load("//provider:defs2.bzl", "foo1", "bar1")
            def test():
                create_collection([foo1, bar1])
            "#
    );
    expect_error(
        tester.run_starlark_bzl_test(missing_default_info),
        missing_default_info,
        "did not receive a DefaultInfo",
    );
    Ok(())
}

#[test]
fn returns_default_info() -> SharedResult<()> {
    let mut tester = provider_collection_tester()?;
    tester.run_starlark_bzl_test(indoc!(
        r#"
            artifact = source_artifact("foo", "bar.cpp")
            frozen_collection = create_collection([
                DefaultInfo(
                    sub_targets={"foo": []},
                    default_outputs=[artifact]
                )
            ])
            def test():
                di_sub_targets = get_default_info_sub_targets(frozen_collection)
                di_default_outputs = get_default_info_default_outputs(frozen_collection)
                assert_eq([], di_sub_targets["foo"][DefaultInfo].default_outputs)
                assert_eq([artifact], di_default_outputs)
            "#
    ))
}

#[test]
fn provider_collection_contains_methods_and_in_operator() -> SharedResult<()> {
    let mut tester = provider_collection_tester()?;
    tester.add_import(
        &ImportPath::testing_new("root//providers:defs.bzl"),
        indoc!(
            r#"
                FooInfo = provider(fields=["foo"])
                BarInfo = provider(fields=["bar"])
                "#
        ),
    )?;
    tester.run_starlark_bzl_test(indoc!(
        r#"
            load("//providers:defs.bzl", "FooInfo", "BarInfo")
            c1 = create_collection([DefaultInfo(), FooInfo(foo="f1")])
            c2 = create_collection([DefaultInfo(), FooInfo(foo="f2"), BarInfo(bar="b2")])
            def test():
                assert_eq(True, contains_provider(c1, FooInfo))
                assert_eq(True, FooInfo in c1)
                assert_eq(False, contains_provider(c1, BarInfo))
                assert_eq(False, BarInfo in c1)
                assert_eq(["DefaultInfo", "FooInfo", "BarInfo"], providers_list(c2))
            "#
    ))
}

#[test]
fn provider_collection_get() -> SharedResult<()> {
    let mut tester = provider_collection_tester()?;
    tester.add_import(
        &ImportPath::testing_new("root//providers:defs.bzl"),
        indoc!(
            r#"
                FooInfo = provider(fields=["foo"])
                BarInfo = provider(fields=["bar"])

                # Frozen collection
                cf = create_collection([DefaultInfo(), FooInfo(foo="f1")])
                "#
        ),
    )?;
    tester.run_starlark_bzl_test(indoc!(
        r#"
            load("//providers:defs.bzl", "FooInfo", "BarInfo", "cf")

            # Unfrozen collection
            cu = create_collection([DefaultInfo(), FooInfo(foo="f1")])

            def test():
                assert_eq("f1", cu.get(FooInfo).foo)
                assert_eq(None, cu.get(BarInfo))
                assert_eq("f1", cf.get(FooInfo).foo)
                assert_eq(None, cf.get(BarInfo))
            "#
    ))
}


/*
 * 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 builtin;
mod collection;
pub(crate) mod testing;
mod tests;


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

use buck2_build_api::interpreter::build_defs::register_provider;
use buck2_build_api::interpreter::rule_defs::provider::collection::FrozenProviderCollectionValue;
use buck2_build_api::interpreter::rule_defs::provider::registration::register_builtin_providers;
use buck2_interpreter_for_build::attrs::coerce;
use starlark::environment::GlobalsBuilder;
use starlark::environment::Module;

pub trait FrozenProviderCollectionValueExt {
    /// Creates a `FrozenProviderCollectionValue` for testing. The given string should be
    /// Starlark code that returns a list of providers. The built in providers are available.
    fn testing_new(providers: &str) -> Self;
}

impl FrozenProviderCollectionValueExt for FrozenProviderCollectionValue {
    fn testing_new(providers: &str) -> Self {
        let env = Module::new();
        let globals = GlobalsBuilder::extended()
            .with(register_builtin_providers)
            .with(register_provider)
            .build();
        let value = coerce::testing::to_value(&env, &globals, providers);
        let res_typed =
            buck2_build_api::interpreter::rule_defs::provider::collection::ProviderCollection::try_from_value(value)
                .map_err(|e| anyhow::anyhow!("{:?}", e))
                .unwrap();

        let res = env.heap().alloc(res_typed);
        env.set("", res);

        let frozen_env = env.freeze().expect("should freeze successfully");
        let res = frozen_env.get("").unwrap();

        FrozenProviderCollectionValue::try_from_value(res)
            .expect("just created this, this shouldn't happen")
    }
}


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

use buck2_build_api::interpreter::build_defs::register_provider;
use buck2_build_api::interpreter::rule_defs::register_rule_defs;
use buck2_core::bzl::ImportPath;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

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

#[test]
fn creates_providers() -> anyhow::Result<()> {
    // TODO(nmj): Starlark doesn't let you call 'new_invoker()' on is_mutable types.
    //                 Once that's fixed, make sure we can call 'FooInfo' before the module is
    //                 frozen.
    let mut tester = provider_tester();

    tester.run_starlark_test(indoc!(
        r#"
    FooInfo = provider(fields=["bar", "baz"])
    FooInfo2 = FooInfo
    #frozen_foo_1 = FooInfo(bar="bar_f1", baz="baz_f1")
    #frozen_foo_2 = FooInfo(bar="bar_f2")

    assert_eq("unnamed provider", repr(provider(fields=["f1"])))
    assert_eq("FooInfo(bar, baz)", repr(FooInfo))
    assert_eq("FooInfo(bar, baz)", repr(FooInfo2))

    def test():
        assert_eq(FooInfo.type, "FooInfo")
        assert_eq("FooInfo(bar, baz)", repr(FooInfo))
        assert_eq("FooInfo(bar, baz)", repr(FooInfo2))

        #assert_eq("FooInfo(bar=\"bar_f1\", baz=\"baz_f1\")", repr(frozen_foo1))
        #assert_eq("bar_f1", frozen_foo1.bar)
        #assert_eq("baz_f1", frozen_foo1.baz)
        #assert_eq("FooInfo(bar=\"bar_f2\", baz=None)", repr(frozen_foo2))
        #assert_eq("bar_f2", frozen_foo2.bar)
        #assert_eq(None, frozen_foo2.baz)

        foo_1 = FooInfo(bar="bar_1", baz="baz_1")
        foo_2 = FooInfo(bar="bar_2")

        assert_eq("FooInfo(bar, baz)", repr(FooInfo))
        assert_eq("FooInfo(bar=\"bar_1\", baz=\"baz_1\")", repr(foo_1))
        assert_eq("bar_1", foo_1.bar)
        assert_eq("baz_1", foo_1.baz)
        assert_eq("FooInfo(bar=\"bar_2\", baz=None)", repr(foo_2))
        assert_eq("bar_2", foo_2.bar)
        assert_eq(None, foo_2.baz)

        assert_eq("{\"bar\":\"bar_1\",\"baz\":\"baz_1\"}", foo_1.to_json())
    "#
    ))?;

    tester.run_starlark_test_expecting_error(
        indoc!(
            r#"
    FooInfo = provider(fields=["bar", "baz"])

    def test():
        foo_1 = FooInfo(bar="bar1")
        foo_1.quz
    "#
        ),
        "Object of type `provider` has no attribute `quz`",
    );

    tester.run_starlark_test_expecting_error(
        indoc!(
            r#"
    list = []
    list.append(provider(fields=["bar", "baz"]))
    "#
        ),
        "must be assigned to a variable",
    );

    // Make sure that frozen UserProvider instances work
    let mut tester = provider_tester();
    tester.add_import(
        &ImportPath::testing_new("root//provider:def1.bzl"),
        indoc!(
            r#"
            FooInfo = provider(fields=["foo"])
            "#
        ),
    )?;
    tester.add_import(
        &ImportPath::testing_new("root//provider:def2.bzl"),
        indoc!(
            r#"
            load("//provider:def1.bzl", "FooInfo")
            foo = FooInfo(foo="foo1")
            "#
        ),
    )?;
    tester.run_starlark_test(indoc!(
        r#"
        load("//provider:def2.bzl", "foo")
        def test():
            assert_eq('FooInfo(foo="foo1")', repr(foo))
        "#
    ))?;

    Ok(())
}


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

use buck2_build_api::interpreter::rule_defs::register_rule_defs;
use buck2_common::result::SharedResult;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

#[test]
fn configuration_info_validates_buckconfigs() -> SharedResult<()> {
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_rule_defs);
    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ConfigurationInfo(
                constraints = {},
                values = {
                    "applekey": "value",
                }
            )
        "#
        ),
        "Could not find section separator (`.`) in pair `applekey`",
    );

    tester.run_starlark_bzl_test(indoc!(
        r#"
        def test():
            ConfigurationInfo(
                constraints = {},
                values = {
                    "apple.key": "value",
                }
            )
        "#
    ))
}


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

use buck2_build_api::interpreter::build_defs::register_provider;
use buck2_build_api::interpreter::rule_defs::provider::registration::register_builtin_providers;
use buck2_build_api::interpreter::rule_defs::register_rule_defs;
use buck2_common::result::SharedResult;
use buck2_core::bzl::ImportPath;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

use crate::interpreter::rule_defs::artifact::testing::artifactory;

#[test]
fn default_info_is_available() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(artifactory);
    tester.additional_globals(register_rule_defs);
    tester.additional_globals(register_provider);
    tester.add_import(
        &ImportPath::testing_new("root//foo:defs.bzl"),
        r#"BarInfo = provider(fields=["bar"])"#,
    )?;
    tester.run_starlark_bzl_test(indoc!(
            r#"
            load("//foo:defs.bzl", "BarInfo")

            def test():
                b1 = BarInfo(bar="bar1")
                artifact = source_artifact("foo", "bar.cpp")
                default_defaults = DefaultInfo()
                default1 = DefaultInfo(sub_targets={"foo": [b1]}, default_outputs=[artifact])
                default2 = DefaultInfo(sub_targets={"foo": [default1]}, default_outputs=[])

                assert_eq("DefaultInfo(sub_targets={}, default_outputs=[], other_outputs=[])", repr(default_defaults))
                assert_eq([], default_defaults.default_outputs);
                assert_eq({}, default_defaults.sub_targets);

                r = repr(artifact)
                expected_repr = (
                    'DefaultInfo(sub_targets={"foo": Providers([BarInfo(bar="bar1"), ' +
                     repr(default_defaults) + '])}, default_outputs=[' + r + '], other_outputs=[])'
                )
                assert_eq(expected_repr, repr(default1))
                assert_eq([artifact], default1.default_outputs);
                assert_eq([], default1.sub_targets["foo"][DefaultInfo].default_outputs);
                assert_eq("bar1", default1.sub_targets["foo"][BarInfo].bar);

                expected_repr = (
                    'DefaultInfo(sub_targets={"foo": Providers([' + repr(default1) + '])}, ' +
                    'default_outputs=[], other_outputs=[])'
                )
                assert_eq(expected_repr, repr(default2))
                assert_eq([], default2.default_outputs);
                assert_eq([artifact], default2.sub_targets["foo"][DefaultInfo].default_outputs);
                assert_eq("bar1", default2.sub_targets["foo"][DefaultInfo].sub_targets["foo"][BarInfo].bar);
            "#
        ))
}

#[test]
fn default_info_validates_types() -> SharedResult<()> {
    // TODO(nmj): More complex types
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_rule_defs);
    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
            def test():
                default_defaults = DefaultInfo()
                DefaultInfo(sub_targets={"foo": []}, default_outputs=1)
            "#
        ),
        "Type of parameter",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
            def test():
                default_defaults = DefaultInfo()
                DefaultInfo(sub_targets=[], default_outputs=["foo"])
            "#
        ),
        "Type of parameter",
    );

    tester.run_starlark_bzl_test(indoc!(
        r#"
            def test():
                assert_eq(DefaultInfo.type, "DefaultInfo")
            "#
    ))
}

#[test]
fn test_to_json() {
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_builtin_providers);
    tester
        .run_starlark_bzl_test(indoc!(
            r#"
            def test():
                default = DefaultInfo(
                    sub_targets={"foo": [DefaultInfo()]},
                )
                assert_eq(
                    '{"sub_targets":{"foo":{"DefaultInfo":{"sub_targets":{},"default_outputs":[],"other_outputs":[]}}},"default_outputs":[],"other_outputs":[]}',
                    default.to_json(),
                )
            "#
        ))
        .unwrap();
}


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

use buck2_build_api::interpreter::rule_defs::provider::collection::ProviderCollection;
use buck2_build_api::interpreter::rule_defs::provider::dependency::Dependency;
use buck2_common::result::SharedResult;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::pattern::pattern_type::ProvidersPatternExtra;
use buck2_core::pattern::ParsedPattern;
use buck2_interpreter_for_build::interpreter::build_context::BuildContext;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;
use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::Value;

#[starlark_module]
fn dependency_creator(builder: &mut GlobalsBuilder) {
    fn create_collection<'v>(
        s: &str,
        providers: Value<'v>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Dependency<'v>> {
        let c = BuildContext::from_context(eval)?;
        let label = match ParsedPattern::<ProvidersPatternExtra>::parse_precise(
            s,
            c.build_file_cell().name(),
            c.cell_resolver(),
        ) {
            Ok(ParsedPattern::Target(package, target_name, providers)) => providers
                .into_providers_label(package, target_name.as_ref())
                .configure(ConfigurationData::testing_new()),
            _ => {
                eprintln!("Expected a target, not {}", s);
                panic!();
            }
        };
        let collection = eval
            .heap()
            .alloc(ProviderCollection::try_from_value(providers)?);

        Ok(Dependency::new(eval.heap(), label, collection, None))
    }
}

#[test]
fn dependency_works() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(buck2_build_api::interpreter::rule_defs::register_rule_defs);
    tester.additional_globals(dependency_creator);
    tester.run_starlark_bzl_test(indoc!(
        r#"
        frozen = create_collection("root//foo:bar[baz]", [DefaultInfo()])
        def test():
            notfrozen = create_collection("root//foo:bar[baz]", [DefaultInfo()])
            expect = "<dependency root//foo:bar[baz] (<testing>#<HASH>)>"

            assert_eq_ignore_hash(expect, repr(notfrozen))
            assert_eq({}, notfrozen[DefaultInfo].sub_targets)
            assert_eq(["baz"], notfrozen.label.sub_target)

            assert_eq_ignore_hash(expect, repr(frozen))
            assert_eq({}, frozen[DefaultInfo].sub_targets)
            assert_eq(["baz"], frozen.label.sub_target)
        "#
    ))?;
    Ok(())
}


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

use buck2_build_api::interpreter::rule_defs::register_rule_defs;
use buck2_core::bzl::ImportPath;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

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

#[test]
fn test_construction() -> anyhow::Result<()> {
    let test = indoc!(
        r#"
        def test():
            ExternalRunnerTestInfo(type = "foo")
            ExternalRunnerTestInfo(type = "foo", command = ["cmd"])
            ExternalRunnerTestInfo(type = "foo", command = ["cmd", cmd_args()])
            ExternalRunnerTestInfo(type = "foo", command = ("cmd",))
            ExternalRunnerTestInfo(type = "foo", env = {"foo": "bar" })
            ExternalRunnerTestInfo(type = "foo", env = {"foo": cmd_args() })
            ExternalRunnerTestInfo(type = "foo", labels = ["foo"])
            ExternalRunnerTestInfo(type = "foo", contacts = ["foo"])
            ExternalRunnerTestInfo(type = "foo", labels = ("foo",))
            ExternalRunnerTestInfo(type = "foo", use_project_relative_paths = True)
            ExternalRunnerTestInfo(type = "foo", run_from_project_root = True)
        "#
    );
    let mut tester = tester();
    tester.run_starlark_bzl_test(test)?;
    Ok(())
}

#[test]
fn test_validation() -> anyhow::Result<()> {
    let mut tester = tester();
    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo()
        "#
        ),
        "`type`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = 123)
        "#
        ),
        "`type`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", command = "foo")
        "#
        ),
        "`command`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", command = [123])
        "#
        ),
        "`command`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", env = "foo")
        "#
        ),
        "`env`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", env = {"foo": 123})
        "#
        ),
        "`env`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", labels = "foo")
        "#
        ),
        "`labels`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", labels = [123])
        "#
        ),
        "`labels`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", contacts = "foo")
        "#
        ),
        "`contacts`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", contacts = [123])
        "#
        ),
        "`contacts`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", use_project_relative_paths = "foo")
        "#
        ),
        "`use_project_relative_paths`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", run_from_project_root = "foo")
        "#
        ),
        "`run_from_project_root`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", default_executor = "foo")
        "#
        ),
        "`default_executor`",
    );

    tester.run_starlark_bzl_test_expecting_error(
        indoc!(
            r#"
        def test():
            ExternalRunnerTestInfo(type = "foo", executor_overrides = {"foo": "bar" })
        "#
        ),
        "`executor_overrides`",
    );

    Ok(())
}

#[test]
fn test_validation_at_freeze() -> anyhow::Result<()> {
    let mut tester = tester();

    let res = tester.add_import(
        &ImportPath::testing_new("root//test:def1.bzl"),
        indoc!(
            r#"
        def make_info():
            contacts = []
            info = ExternalRunnerTestInfo(type = "foo", contacts = contacts)
            contacts.append(123)
            return info

        exported_info = make_info()
        "#
        ),
    );

    assert!(res.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.
 */

use buck2_build_api::interpreter::rule_defs::provider::collection::tester::collection_creator;
use buck2_build_api::interpreter::rule_defs::register_rule_defs;
use buck2_common::result::SharedResult;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use buck2_interpreter_for_build::label::testing::label_creator;
use indoc::indoc;

use crate::interpreter::rule_defs::artifact::testing::artifactory;

fn tester() -> Tester {
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(collection_creator);
    tester.additional_globals(artifactory);
    tester.additional_globals(label_creator);
    tester.additional_globals(register_rule_defs);
    tester
}

#[test]
fn install_info_works_as_provider_key() -> SharedResult<()> {
    let content = indoc!(
        r#"
             installer_app = label("//foo:bar[quz]")
             c = create_collection([InstallInfo(installer=installer_app, files={}), DefaultInfo(), RunInfo()])
             def test():
                 assert_eq(True, contains_provider(c, InstallInfo))
             "#
    );
    let mut tester = tester();
    tester.run_starlark_bzl_test(content)
}

#[test]
fn info_validator_succeeds_for_artifacts_without_additional_artifacts() -> SharedResult<()> {
    let content = indoc!(
        r#"
             a1 = source_artifact("foo/bar", "baz.h")
             a2 = bound_artifact("//:dep1", "dir/baz.h")
             installer_app = label("//foo:bar[quz]")
             c = create_collection([InstallInfo(installer=installer_app, files={"a1": a1, "a2": a2}), DefaultInfo(), RunInfo()])
             def test():
                 assert_eq(True, contains_provider(c, InstallInfo))
             "#
    );
    let mut tester = tester();
    tester.run_starlark_bzl_test(content)
}


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

use buck2_build_api::interpreter::rule_defs::register_rule_defs;
use buck2_core::bzl::ImportPath;
use buck2_interpreter_for_build::interpreter::testing::expect_error;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use buck2_interpreter_for_build::label::testing::label_creator;
use indoc::indoc;

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

#[test]
fn test_construction() -> anyhow::Result<()> {
    let mut tester = new_tester();
    let test = indoc!(
        r#"
        def test():
            target = label("//:foobar")
            LocalResourceInfo(source_target=target, setup=["/foo", "--resource"], resource_env_vars={"RESOURCE_ENV_VAR": "json_key"})
            LocalResourceInfo(source_target=target, setup=cmd_args(["/foo", "--resource"]), resource_env_vars={"RESOURCE_ENV_VAR": "json_key"})
        "#
    );
    tester.run_starlark_bzl_test(test)?;
    Ok(())
}

#[test]
fn test_missing_fields_validation() -> anyhow::Result<()> {
    let mut tester = new_tester();
    {
        let test = indoc!(
            r#"
            def test():
                LocalResourceInfo(setup=cmd_args(), resource_env_vars={})
            "#
        );
        expect_error(tester.run_starlark_bzl_test(test), test, "`source_target`");
    }
    {
        let test = indoc!(
            r#"
            def test():
                target = label("//:foobar")
                LocalResourceInfo(source_target=target, resource_env_vars={})
            "#
        );
        expect_error(tester.run_starlark_bzl_test(test), test, "`setup`");
    }
    {
        let test = indoc!(
            r#"
            def test():
                target = label("//:foobar")
                LocalResourceInfo(source_target=target, setup=cmd_args())
            "#
        );
        expect_error(
            tester.run_starlark_bzl_test(test),
            test,
            "`resource_env_vars`",
        );
    }
    Ok(())
}

#[test]
fn test_validation() -> anyhow::Result<()> {
    let mut tester = new_tester();

    {
        let test = indoc!(
            r#"
            def test():
                wrong_target = 42
                LocalResourceInfo(source_target=wrong_target, setup=["/foo", "--resource"], resource_env_vars={"RESOURCE_ENV_VAR": "json_key"})
            "#
        );
        expect_error(
            tester.run_starlark_bzl_test(test),
            test,
            "Value for `source_target` field is not a label",
        );
    }
    {
        let test = indoc!(
            r#"
            def test():
                target = label("//:foobar")
                wrong_setup = {5:6}
                LocalResourceInfo(source_target=target, setup=wrong_setup, resource_env_vars={"RESOURCE_ENV_VAR": "json_key"})
            "#
        );
        expect_error(
            tester.run_starlark_bzl_test(test),
            test,
            "Value for `setup` field is not a command line",
        );
    }
    {
        let test = indoc!(
            r#"
            def test():
                target = label("//:foobar")
                wrong_setup = []
                LocalResourceInfo(source_target=target, setup=wrong_setup, resource_env_vars={"RESOURCE_ENV_VAR": "json_key"})
            "#
        );
        expect_error(
            tester.run_starlark_bzl_test(test),
            test,
            "Value for `setup` field is an empty command line",
        );
    }
    {
        let test = indoc!(
            r#"
            def test():
                target = label("//:foobar")
                wrong_env_vars = "baz"
                LocalResourceInfo(source_target=target, setup=["/foo", "--resource"], resource_env_vars=wrong_env_vars)
            "#
        );
        expect_error(
            tester.run_starlark_bzl_test(test),
            test,
            "Value for `resource_env_vars` field is not a dictionary",
        );
    }
    {
        let test = indoc!(
            r#"
            def test():
                target = label("//:foobar")
                wrong_env_vars = {}
                LocalResourceInfo(source_target=target, setup=["/foo", "--resource"], resource_env_vars=wrong_env_vars)
            "#
        );
        expect_error(
            tester.run_starlark_bzl_test(test),
            test,
            "Value for `resource_env_vars` field is an empty dictionary",
        );
    }
    {
        let test = indoc!(
            r#"
            def test():
                target = label("//:foobar")
                wrong_env_vars = {1:"one"}
                LocalResourceInfo(source_target=target, setup=["/foo", "--resource"], resource_env_vars=wrong_env_vars)
            "#
        );
        expect_error(
            tester.run_starlark_bzl_test(test),
            test,
            "Invalid key in `resource_env_vars`: Expected a str, got",
        );
    }
    {
        let test = indoc!(
            r#"
            def test():
                target = label("//:foobar")
                wrong_env_vars = {"one":1}
                LocalResourceInfo(source_target=target, setup=["/foo", "--resource"], resource_env_vars=wrong_env_vars)
            "#
        );
        expect_error(
            tester.run_starlark_bzl_test(test),
            test,
            "Invalid value in `resource_env_vars`: Expected a str, got",
        );
    }
    Ok(())
}

#[test]
fn test_validation_at_freeze() -> anyhow::Result<()> {
    let mut tester = new_tester();
    let test = indoc!(
        r#"
        def make_info():
            resource_env_vars = {"RESOURCE_ENV_VAR": "json_key"}
            info = LocalResourceInfo(source_target=target, setup=["/foo", "--resource"], resource_env_vars=resource_env_vars)
            resource_env_vars["ONE"] = 1
            return info

        exported_info = make_info()
        "#
    );
    let res = tester.add_import(&ImportPath::testing_new("root//:defs2.bzl"), test);
    assert!(res.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 configuration_info;
mod default_info;
mod dependency;
mod external_runner_test_info;
mod install_info;
mod local_resource_info;
mod run_info;
mod tests;
mod worker_info;


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

use buck2_build_api::interpreter::rule_defs::provider::collection::tester::collection_creator;
use buck2_build_api::interpreter::rule_defs::register_rule_defs;
use buck2_common::result::SharedResult;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

use crate::interpreter::rule_defs::artifact::testing::artifactory;
use crate::interpreter::rule_defs::cmd_args::testing::command_line_stringifier;

fn run_info_tester() -> Tester {
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(command_line_stringifier);
    tester.additional_globals(artifactory);
    tester.additional_globals(register_rule_defs);
    tester.additional_globals(collection_creator);
    tester
}

#[test]
fn run_info_stringifies() -> SharedResult<()> {
    let mut tester = run_info_tester();
    let content = indoc!(
        r#"
            a = source_artifact("foo/bar", "baz.h")
            b = source_artifact("foo/bar", "baz.cpp")
            c = source_artifact("foo/bar", "baz-inl.h")
            cli = cmd_args().add(a).add(b)
            frozen_ri1 = RunInfo()
            frozen_ri2 = RunInfo(args=["1", "2", cli, a])
            frozen_ri3 = RunInfo(args="1")
            frozen_ri4 = RunInfo(args=cli)
            frozen_ri5 = RunInfo(args=[frozen_ri2, "some_string"])

            cli = cli.copy()
            cli.add(c)

            def test():
                ri1 = RunInfo()
                ri2 = RunInfo(args=["1", "2", cli, a])
                ri3 = RunInfo(args="1")
                ri4 = RunInfo(args=cli)
                ri5 = RunInfo(args=[ri2, "some_string"])
                ri6 = RunInfo(args=[frozen_ri2, "some_string"])

                baz_h = "foo/bar/baz.h"
                baz_cpp = "foo/bar/baz.cpp"
                baz_inl = "foo/bar/baz-inl.h"

                assert_eq([], get_args(frozen_ri1))

                assert_eq(
                    ["1","2", baz_h, baz_cpp, baz_h],
                    get_args(frozen_ri2)
                )

                assert_eq(["1"], get_args(frozen_ri3))

                assert_eq([baz_h, baz_cpp], get_args(frozen_ri4))

                assert_eq(
                    ["1","2", baz_h, baz_cpp, baz_h, "some_string"],
                    get_args(frozen_ri5)
                )

                assert_eq([], get_args(ri1))

                assert_eq(
                    ["1","2", baz_h, baz_cpp, baz_inl, baz_h],
                    get_args(ri2)
                )
                assert_eq(["1"], get_args(ri3))
                assert_eq([baz_h, baz_cpp, baz_inl], get_args(ri4))
                assert_eq(
                    ["1","2", baz_h, baz_cpp, baz_inl, baz_h, "some_string"],
                    get_args(ri5)
                )
                assert_eq(
                    ["1","2", baz_h, baz_cpp, baz_h, "some_string"],
                    get_args(ri6)
                )
            "#
    );
    tester.run_starlark_bzl_test(content)
}

#[test]
fn run_info_validates_types() {
    let content_bad_args1 = indoc!(
        r#"
            def test():
                RunInfo(args=False)
            "#
    );
    let mut tester = run_info_tester();
    tester.run_starlark_bzl_test_expecting_error(content_bad_args1, "expected command line item");

    let content_bad_args2 = indoc!(
        r#"
            def test():
                RunInfo(args={})
            "#
    );
    let mut tester = run_info_tester();
    tester.run_starlark_bzl_test_expecting_error(content_bad_args2, "expected command line item");
}

#[test]
fn run_info_works_as_provider_key() -> SharedResult<()> {
    let mut tester = run_info_tester();

    let content = indoc!(
        r#"
            c = create_collection([RunInfo(args=["1","2","3"]), DefaultInfo()])
            def test():
                assert_eq(True, contains_provider(c, RunInfo))
            "#
    );

    tester.run_starlark_bzl_test(content)
}


/*
 * 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 buck2_build_api::interpreter::rule_defs::provider::registration::register_builtin_providers;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use buck2_interpreter_for_build::label::testing::label_creator;
use indoc::indoc;

/// Test `equals` in generated code for providers.
#[test]
fn test_equals() -> anyhow::Result<()> {
    let mut tester = Tester::new()?;

    tester.additional_globals(register_builtin_providers);
    tester.additional_globals(label_creator);

    tester.run_starlark_bzl_test(indoc!(
        r#"
            def test():
                x = target_label("root//bar:baz")
                y = target_label("root//quux:corge")
                a0 = ConstraintSettingInfo(label = x)
                a1 = ConstraintSettingInfo(label = x)
                b = ConstraintSettingInfo(label = y)
                assert_eq(a0, a1)
                assert_ne(a0, b)
        "#
    ))?;

    Ok(())
}


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

use buck2_build_api::interpreter::rule_defs::provider::registration::register_builtin_providers;
use buck2_interpreter_for_build::interpreter::testing::Tester;

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

/// Test `id` is not there.
#[test]
fn run_display() {
    let mut tester = run_info_tester();
    tester
        .run_starlark_bzl_test(
            r#"
def test():
    assert_eq('WorkerInfo(exe=cmd_args("x"))', str(WorkerInfo(exe="x")))
"#,
        )
        .unwrap();
}


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

pub(crate) mod testing;
mod tests;


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

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

use anyhow::Context as _;
use buck2_artifact::deferred::data::DeferredData;
use buck2_artifact::deferred::id::DeferredId;
use buck2_artifact::deferred::key::DeferredKey;
use buck2_build_api::interpreter::build_defs::register_transitive_set;
use buck2_build_api::interpreter::rule_defs::transitive_set::FrozenTransitiveSet;
use buck2_build_api::interpreter::rule_defs::transitive_set::TransitiveSet;
use buck2_build_api::interpreter::rule_defs::transitive_set::TransitiveSetOrdering;
use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::target::label::ConfiguredTargetLabel;
use indoc::indoc;
use starlark::environment::GlobalsBuilder;
use starlark::environment::Module;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::OwnedFrozenValueTyped;
use starlark::values::Value;

use crate::interpreter::rule_defs::artifact::testing::artifactory;

#[starlark_module]
pub(crate) fn tset_factory(builder: &mut GlobalsBuilder) {
    fn make_tset<'v>(
        definition: Value<'v>,
        value: Option<Value<'v>>,
        children: Option<Value<'v>>, // An iterable.
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<TransitiveSet<'v>> {
        static LAST_ID: AtomicU32 = AtomicU32::new(0);

        let target = ConfiguredTargetLabel::testing_parse(
            "cell//path:target",
            ConfigurationData::testing_new(),
        );
        let deferred_id = DeferredId::testing_new(LAST_ID.fetch_add(1, Ordering::Relaxed));
        let deferred_key = DeferredKey::Base(BaseDeferredKey::TargetLabel(target), deferred_id);

        let set = TransitiveSet::new_from_values(
            DeferredData::unchecked_new(deferred_key),
            definition,
            value,
            children,
            eval,
        )?;

        Ok(set)
    }
}

pub(crate) fn new_transitive_set(
    code: &str,
) -> anyhow::Result<OwnedFrozenValueTyped<FrozenTransitiveSet>> {
    let env = Module::new();

    let globals = GlobalsBuilder::extended()
        .with(register_transitive_set)
        .with(tset_factory)
        .with(artifactory)
        .build();

    let val = buck2_interpreter_for_build::attrs::coerce::testing::to_value(&env, &globals, code);

    env.set("", val);
    let frozen = env.freeze().context("Freeze failed")?;

    let value = frozen.get("").context("Frozen tset was not found!")?;
    value
        .downcast()
        .map_err(|v| anyhow::anyhow!("Value was not a TransitiveSet: {:?}", v))
}

#[test]
fn test_new_transitive_set() -> anyhow::Result<()> {
    let set = new_transitive_set(indoc!(
        r#"
        FooSet = transitive_set()
        s1 = make_tset(FooSet, value = "foo")
        make_tset(FooSet, value = "bar", children = [s1])
        "#
    ))?;

    assert_eq!(
        set.as_ref().iter(TransitiveSetOrdering::Preorder).count(),
        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::sync::Arc;

use buck2_common::executor_config::CommandExecutorConfig;
use buck2_configured::configuration::calculation::ExecutionPlatformsKey;
use buck2_core::build_file_path::BuildFilePath;
use buck2_core::bzl::ImportPath;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::fs::paths::file_name::FileNameBuf;
use buck2_core::package::PackageLabel;
use buck2_core::provider::label::ProvidersLabel;
use buck2_core::provider::label::ProvidersName;
use buck2_core::target::label::TargetLabel;
use buck2_core::target::name::TargetName;
use buck2_execute::execute::dice_data::set_fallback_executor_config;
use buck2_interpreter_for_build::interpreter::calculation::InterpreterResultsKey;
use buck2_node::attrs::attr::Attribute;
use buck2_node::attrs::attr_type::any::AnyAttrType;
use buck2_node::attrs::attr_type::bool::BoolLiteral;
use buck2_node::attrs::attr_type::dep::DepAttr;
use buck2_node::attrs::attr_type::dep::DepAttrTransition;
use buck2_node::attrs::attr_type::dep::DepAttrType;
use buck2_node::attrs::attr_type::list::ListLiteral;
use buck2_node::attrs::attr_type::string::StringLiteral;
use buck2_node::attrs::attr_type::AttrType;
use buck2_node::attrs::coerced_attr::CoercedAttr;
use buck2_node::attrs::configured_attr::ConfiguredAttr;
use buck2_node::attrs::inspect_options::AttrInspectOptions;
use buck2_node::attrs::internal::internal_attrs;
use buck2_node::nodes::configured_frontend::ConfiguredTargetNodeCalculation;
use buck2_node::nodes::eval_result::EvaluationResult;
use buck2_node::nodes::frontend::TargetGraphCalculation;
use buck2_node::nodes::targets_map::TargetsMap;
use buck2_node::nodes::unconfigured::testing::TargetNodeExt;
use buck2_node::nodes::unconfigured::TargetNode;
use buck2_node::provider_id_set::ProviderIdSet;
use buck2_node::rule_type::RuleType;
use buck2_node::rule_type::StarlarkRuleType;
use buck2_util::arc_str::ArcSlice;
use dice::testing::DiceBuilder;
use dice::UserComputationData;
use dupe::Dupe;
use starlark::collections::SmallMap;
use starlark_map::smallmap;

#[tokio::test]
async fn test_get_node() -> anyhow::Result<()> {
    let cfg = ConfigurationData::testing_new();
    let pkg = PackageLabel::testing_parse("cell//foo/bar");

    let name1 = TargetName::unchecked_new("t1");
    let label1 = TargetLabel::new(pkg.dupe(), name1.as_ref());

    let name2 = TargetName::unchecked_new("t2");
    let label2 = TargetLabel::new(pkg.dupe(), name2.as_ref());

    let rule_type = RuleType::Starlark(Arc::new(StarlarkRuleType {
        import_path: ImportPath::testing_new("cell//foo/bar:def.bzl"),
        name: "some_rule".to_owned(),
    }));
    let attrs1 = vec![
        (
            "bool_field",
            Attribute::new(None, "", AttrType::bool()),
            CoercedAttr::Bool(BoolLiteral(false)),
        ),
        (
            "another_field",
            Attribute::new(None, "", AttrType::string()),
            CoercedAttr::String(StringLiteral("some_string".into())),
        ),
        (
            "some_deps",
            Attribute::new(
                None,
                "",
                AttrType::list(AttrType::dep(ProviderIdSet::EMPTY)),
            ),
            CoercedAttr::List(ListLiteral(ArcSlice::new([CoercedAttr::Dep(
                ProvidersLabel::new(label2.dupe(), ProvidersName::Default),
            )]))),
        ),
    ];

    let node1 = TargetNode::testing_new(label1.dupe(), rule_type.dupe(), attrs1);

    let attrs2 = vec![
        (
            "bool_field",
            Attribute::new(None, "", AttrType::bool()),
            CoercedAttr::Bool(BoolLiteral(true)),
        ),
        (
            "another_field",
            Attribute::new(None, "", AttrType::string()),
            CoercedAttr::String(StringLiteral("another_string".into())),
        ),
        (
            "some_deps",
            Attribute::new(
                None,
                "",
                AttrType::list(AttrType::dep(ProviderIdSet::EMPTY)),
            ),
            AnyAttrType::empty_list(),
        ),
    ];

    let node2 = TargetNode::testing_new(label2.dupe(), rule_type.dupe(), attrs2);

    let eval_result = EvaluationResult::new(
        Arc::new(BuildFilePath::new(
            pkg.dupe(),
            FileNameBuf::unchecked_new("BUCK"),
        )),
        Vec::new(),
        TargetsMap::from_iter([node1.dupe(), node2.dupe()]),
    );

    let mut data = UserComputationData::new();
    set_fallback_executor_config(&mut data.data, CommandExecutorConfig::testing_local());
    let computations = DiceBuilder::new()
        .mock_and_return(InterpreterResultsKey(pkg), Ok(Arc::new(eval_result)))
        .mock_and_return(ExecutionPlatformsKey, Ok(None))
        .build(data)?;
    let computations = computations.commit().await;

    let node = computations.get_target_node(&label1).await?;
    assert_eq!(node.0, node1.0);

    let node = computations.get_target_node(&label2).await?;
    assert_eq!(node.0, node2.0);

    let conf_attrs1 = smallmap![
        "bool_field" => ConfiguredAttr::Bool(BoolLiteral(false)),
        "another_field" =>
         ConfiguredAttr::String(StringLiteral("some_string".into())),
        "some_deps" =>
         ConfiguredAttr::List(ListLiteral(ArcSlice::new([
            ConfiguredAttr::Dep(Box::new(DepAttr {
                attr_type: DepAttrType::new(ProviderIdSet::EMPTY, DepAttrTransition::Identity),
                label: ProvidersLabel::new(label2.dupe(), ProvidersName::Default)
                    .configure(cfg.dupe()),
            })),
        ]))),
    ];

    let conf_attrs2 = smallmap![
        "bool_field" => ConfiguredAttr::Bool(BoolLiteral(true)),
        "another_field" =>
         ConfiguredAttr::String(StringLiteral("another_string".into())),
        "some_deps" => ConfiguredAttr::List(
            ListLiteral(ArcSlice::new([]))
        ),
    ];

    let node = computations.get_target_node(&label1).await?;
    assert_eq!(node.0, node1.0);

    let node = computations.get_target_node(&label2).await?;
    assert_eq!(node.0, node2.0);

    let node = computations
        .get_configured_target_node(&label1.configure(cfg.dupe()))
        .await?;
    let node = node.require_compatible()?;
    let node_attrs: SmallMap<_, _> = node
        .attrs(AttrInspectOptions::All)
        .filter_map(|a| {
            if internal_attrs().contains_key(a.name) {
                None
            } else {
                Some((a.name, a.value))
            }
        })
        .collect();
    assert_eq!(node_attrs, conf_attrs1);

    let node = computations
        .get_configured_target_node(&label2.configure(cfg.dupe()))
        .await?;
    let node = node.require_compatible()?;
    let node_attrs: SmallMap<_, _> = node
        .attrs(AttrInspectOptions::All)
        .filter_map(|a| {
            if internal_attrs().contains_key(a.name) {
                None
            } else {
                Some((a.name, a.value))
            }
        })
        .collect();
    assert_eq!(node_attrs, conf_attrs2);

    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 calculation;


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

/// Get the source control revision for this binary, if available. We provide this externally when
/// building Buck2 for release.
pub fn revision() -> Option<&'static str> {
    if let Some(rev) = std::option_env!("BUCK2_SET_EXPLICIT_VERSION") {
        if !rev.is_empty() {
            return Some(rev);
        }
    }

    None
}

/// Get the generated version for the windows binary. We use this for defining bucks internal version
pub fn win_internal_version() -> Option<&'static str> {
    if let Some(win) = std::option_env!("BUCK2_WIN_INTERNAL_VERSION") {
        if !win.is_empty() {
            return Some(win);
        }
    }
    None
}

/// Get the time at which this binary was built, if available.
pub fn time_iso8601() -> Option<&'static str> {
    #[cfg(any(fbcode_build, cargo_internal_build))]
    {
        Some(build_info::BuildInfo::get_time_iso8601())
    }

    #[cfg(not(any(fbcode_build, cargo_internal_build)))]
    {
        None
    }
}

/// A timestamp for this release. Notionally this is similar to time_iso8601, except a) the format
/// differs so it's more easily machine readable and b) we *only* set this in release binaries.
///
/// We use this in Ingress when dealing with panic and soft error reports to omit logging for older
/// versions.
pub fn release_timestamp() -> Option<&'static str> {
    if let Some(ts) = std::option_env!("BUCK2_RELEASE_TIMESTAMP") {
        if !ts.is_empty() {
            return Some(ts);
        }
    }
    None
}


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

use std::str::FromStr;
use std::time::Duration;

use allocative::Allocative;
use anyhow::Context as _;
use async_trait::async_trait;
use buck2_core::soft_error;
use buck2_events::dispatch::EventDispatcher;
use dice::UserComputationData;
use dupe::Dupe;
use futures::future::Future;

#[derive(Copy, Clone, Dupe)]
pub struct NodeDuration {
    /// The amount of time for this node that corresponds to something the user might be able to
    /// improve. We should better break this down.
    pub user: Duration,
    /// The total duration for this node.
    pub total: Duration,
}

impl NodeDuration {
    /// Returns the duration we are using in our critical path calculation. This doesn't really
    /// *need* to be a function but right now we use user and want to switch to total so it's
    /// easier to do that if this is in a single function.
    pub fn critical_path_duration(&self) -> Duration {
        self.total
    }

    pub fn zero() -> Self {
        Self {
            user: Duration::from_secs(0),
            total: Duration::from_secs(0),
        }
    }
}

#[derive(Copy, Clone, Dupe, derive_more::Display, Allocative)]
pub enum CriticalPathBackendName {
    #[display(fmt = "longest-path-graph")]
    LongestPathGraph,
    #[display(fmt = "default")]
    Default,
}

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

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s == "longest-path-graph" {
            return Ok(Self::LongestPathGraph);
        }

        if s == "default" {
            return Ok(Self::Default);
        }

        Err(anyhow::anyhow!("Invalid backend name: `{}`", s))
    }
}

/// Created along with the BuildSignalsInstaller (ideally, BuildSignalsInstaller's definition would
/// live here, but that can't be done for now because it has some dependencies on buck2_build_api).
///
/// This can be started to actually start processing build signals.
pub trait DeferredBuildSignals: Send {
    fn start(
        self: Box<Self>,
        events: EventDispatcher,
        backend: CriticalPathBackendName,
    ) -> Box<dyn FinishBuildSignals>;
}

/// Returned by DeferredBuildSignals once started. Lets us report that we finished.
#[async_trait]
pub trait FinishBuildSignals: Send {
    async fn finish(self: Box<Self>) -> anyhow::Result<()>;
}

/// Start the backend for a DeferredBuildSignals instance.
///
/// Build listeners operate by creating a matched pair of signal senders and signal receivers.
/// Senders are Dupe and allow for arbitrarily many writeres. Receivers are not Dupe and are
/// expected to be driven by a single thread. This implies that, in order for the receiver to
/// function correctly and dispatch to build listeners, it must be run in a background task that is
/// periodically polled.
///
/// This function arranges for a background task to be spawned that drives the receiver, while
/// invoking the called function with a live BuildSignalSender that can be used to send events to
/// the listening receiver. Upon return of `scope`, the sender terminates the receiver by sending a
/// `BuildFinished` signal and joins the receiver task.
pub async fn scope<F, R, Fut>(
    deferred: Box<dyn DeferredBuildSignals>,
    events: EventDispatcher,
    backend: CriticalPathBackendName,
    func: F,
) -> anyhow::Result<R>
where
    F: FnOnce() -> Fut + Send,
    Fut: Future<Output = anyhow::Result<R>> + Send,
    R: Send,
{
    let handle = deferred.start(events, backend);
    let result = func().await;
    let res = handle
        .finish()
        .await
        .context("Error computing critical path");
    if let Err(e) = res {
        soft_error!("critical_path_computation_failed", e)?;
    }
    result
}

pub trait HasCriticalPathBackend {
    fn set_critical_path_backend(&mut self, backend: CriticalPathBackendName);

    fn get_critical_path_backend(&self) -> CriticalPathBackendName;
}

impl HasCriticalPathBackend for UserComputationData {
    fn set_critical_path_backend(&mut self, backend: CriticalPathBackendName) {
        self.data.set(backend);
    }

    fn get_critical_path_backend(&self) -> CriticalPathBackendName {
        *self
            .data
            .get::<CriticalPathBackendName>()
            .expect("CriticalPathBackendName should be set")
    }
}