"""
This module extension creates git_repositories loaded from a json file.

It is important to separate the implementation from the actual data because
the MODULE.bazel.lock file has a checksum of the .bzl files

"""

load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")

# https://bazel.build/rules/lib/globals/bzl.html#tag_class
_from_file_tag = tag_class(
    doc = "A JSON file containing attributes for git_repository",
    attrs = {
        "deps_json": attr.label(
            doc = "The deps.json file to read and create repos from.",
        ),
    },
)

# ctx is https://bazel.build/rules/lib/builtins/module_ctx
def _cpp_modules_impl(ctx):
    all_deps = set()
    direct_deps = []

    # https://bazel.build/rules/lib/builtins/bazel_module.html
    for module in ctx.modules:
        #https://bazel.build/rules/lib/builtins/bazel_module_tags
        if len(module.tags.from_file) != 1:
            fail("Must have exactly one from_file attribute set")
        json_file_label = module.tags.from_file[0].deps_json

        # https://bazel.build/rules/lib/builtins/module_ctx#read
        json_content = ctx.read(ctx.path(json_file_label))
        data = json.decode(json_content)

        if "direct" not in data:
            fail("JSON file must contain a 'direct' list: {}".format(json_file_label))
        if "indirect" not in data:
            fail("JSON file must contain a 'indirect' list (even if it's empty): {}".format(json_file_label))

        for repo_data in data["direct"]:
            name = repo_data.get("name")
            if name not in all_deps:
                git_repository(
                    build_file = repo_data.get("build_file"),
                    commit = repo_data.get("commit"),
                    name = name,
                    patch_cmds = repo_data.get("patch_cmds"),
                    patch_cmds_win = repo_data.get("patch_cmds_win"),
                    patches = repo_data.get("patches"),
                    remote = repo_data.get("remote"),
                )
                direct_deps.append(name)
            all_deps.add(name)

        for repo_data in data["indirect"]:
            name = repo_data.get("name")
            if name not in all_deps:
                git_repository(
                    build_file = repo_data.get("build_file"),
                    commit = repo_data.get("commit"),
                    name = name,
                    patch_cmds = repo_data.get("patch_cmds"),
                    patch_cmds_win = repo_data.get("patch_cmds_win"),
                    patches = repo_data.get("patches"),
                    remote = repo_data.get("remote"),
                )
            all_deps.add(name)

    # https://bazel.build/rules/lib/builtins/module_ctx#extension_metadata
    return ctx.extension_metadata(
        # By specifying the direct dependencies, bazel mod tidy will automatically
        # update the use_repo call to add or remove dependencies to the list.
        root_module_direct_deps = direct_deps,
        root_module_direct_dev_deps = [],
        # By setting this line, we are telling Bazel that the generated rules are
        # hermetic all on their own and that Bazel doesn't need to track the deps.json
        # file (e.g. via a sha256 checksum hash). This *is* the case because we are
        # generating git_repository rules, which include a specific commit, which is
        # effectively a hash of all the input contents.
        # This is a big deal because it means our autorollers can update only the deps.json
        # file and don't have to update anything in the MODULE.bazel.lock file after.
        reproducible = True,
    )

cpp_modules = module_extension(
    implementation = _cpp_modules_impl,
    tag_classes = {
        "from_file": _from_file_tag,
    },
)