"""This file implements an experimental, do-not-use-kind of rust_proto_library.

Disclaimer: This project is experimental, under heavy development, and should not
be used yet."""

load("@rules_proto//proto:defs.bzl", "ProtoInfo", "proto_common")
load("@rules_rust//rust:defs.bzl", "rust_common")
load(
    "//rust:aspects.bzl",
    "RustProtoInfo",
    "label_to_crate_name",
    "proto_rust_toolchain_label",
    "rust_cc_proto_library_aspect",
    "rust_upb_proto_library_aspect",
)

visibility([
    "//experimental/...",
    "//src/google/protobuf/...",
    "//rust/...",
])

def rust_proto_library(name, deps, **args):
    """Declares all the boilerplate needed to use Rust protobufs conveniently.

    Hopefully no user will ever need to read this code.


    Args:
        name: name of the Rust protobuf target.
        deps: proto_library target for which to generate Rust gencode.
        **args: other args passed to the rust_<kernel>_proto_library targets.
    """
    if not name.endswith("_rust_proto"):
        fail(
            "{}: Name rust_proto_library target should end with `_rust_proto`, but was '{}'"
                .format(name),
        )
    name = name.removesuffix("_rust_proto")
    alias_args = {}
    if "visibility" in args:
        alias_args["visibility"] = args.pop("visibility")
    native.alias(
        name = name + "_rust_proto",
        actual = select({
            "//rust:use_upb_kernel": name + "_upb_rust_proto",
            "//conditions:default": name + "_cpp_rust_proto",
        }),
        **alias_args
    )

    rust_upb_proto_library(
        name = name + "_upb_rust_proto",
        deps = deps,
        visibility = ["//visibility:private"],
        **args
    )

    rust_cc_proto_library(
        name = name + "_cpp_rust_proto",
        deps = deps,
        visibility = ["//visibility:private"],
        **args
    )

def _user_visible_label(ctx):
    label = str(ctx.label)
    label = label.removesuffix("_cpp_rust_proto")
    label = label.removesuffix("_upb_rust_proto")
    return label + "_rust_proto"

def _rust_proto_library_impl(ctx):
    if not ctx.label.name.endswith("_rust_proto"):
        fail(
            "{}: Name of rust_proto_library target should end with `_rust_proto`."
                .format(_user_visible_label(ctx)),
        )
    deps = ctx.attr.deps
    if not deps:
        fail(
            "{}: Exactly 1 dependency in `deps` attribute expected, none were provided."
                .format(_user_visible_label(ctx)),
        )
    if len(deps) > 1:
        fail(
            "{}: Exactly 1 dependency in `deps` attribute expected, too many were provided."
                .format(_user_visible_label(ctx)),
        )

    dep = deps[0]
    rust_proto_info = dep[RustProtoInfo]

    dep_variant_info = rust_proto_info.dep_variant_info
    crate_info = dep_variant_info.crate_info

    # Change the crate name from the hame of the proto_library to the name of the rust_proto_library.
    #
    # When the aspect visits proto_libraries, it doesn't know and cannot deduce the name of the
    # rust_proto_library (although the name of rust_proto_libraries is consistently ending with
    # _rust_proto, we can't rely on all proto_libraires to have a name consistently ending with
    # _proto), therefore we have to modify it after the fact here.
    #
    # Since Starlark providers are frozen once they leave the _impl function that defines them,
    # we have to create a shallow copy.
    toolchain = ctx.toolchains["@rules_rust//rust:toolchain_type"]
    fields = {field: getattr(crate_info, field) for field in dir(crate_info)}
    pkg, name = _user_visible_label(ctx).rsplit(":")
    label = struct(**{"name": name, "pkg": pkg})
    fields["name"] = label_to_crate_name(ctx, label, toolchain)
    crate_info_with_rust_proto_name = rust_common.crate_info(**fields)

    return [
        crate_info_with_rust_proto_name,
        dep_variant_info.dep_info,
        dep_variant_info.cc_info,
        DefaultInfo(files = dep_variant_info.crate_info.srcs),
    ]

def _make_rust_proto_library(is_upb):
    return rule(
        implementation = _rust_proto_library_impl,
        attrs = {
            "deps": attr.label_list(
                mandatory = True,
                providers = [ProtoInfo],
                aspects = [rust_upb_proto_library_aspect if is_upb else rust_cc_proto_library_aspect],
            ),
            "_proto_lang_toolchain": attr.label(
                default = Label(proto_rust_toolchain_label(is_upb)),
            ),
        },
        toolchains = [
            "@rules_rust//rust:toolchain_type",
        ],
    )

rust_upb_proto_library = _make_rust_proto_library(is_upb = True)
rust_cc_proto_library = _make_rust_proto_library(is_upb = False)
