"""Rules for Wails binding generation."""

load(":private/common.bzl", "bash_launcher", "write_manifest")

def _normalize_relative_path(path):
    if not path or path == ".":
        return ""

    return path.strip("/")

def _join_relative_path(base, child):
    normalized_base = _normalize_relative_path(base)
    normalized_child = _normalize_relative_path(child)

    if normalized_base and normalized_child:
        return normalized_base + "/" + normalized_child
    if normalized_base:
        return normalized_base
    return normalized_child

def _package_glob_patterns(package_dir, out_dir):
    normalized = package_dir.strip("./")
    generated_dir = _join_relative_path(normalized, out_dir)

    exclude_patterns = ["**/node_modules/**", "**/dist/**"]
    if generated_dir:
        exclude_patterns.append(generated_dir + "/**")

    if not normalized:
        return ("**", exclude_patterns)

    return (
        normalized + "/**",
        exclude_patterns + [
            normalized + "/node_modules/**",
            normalized + "/dist/**",
        ],
    )

def _workspace_package_dir(label_package, package_dir):
    if label_package and package_dir and package_dir != ".":
        return label_package + "/" + package_dir
    if label_package:
        return label_package
    return package_dir

def _wails_generate_bindings_impl(ctx):
    toolchain = ctx.toolchains["//wails:toolchain_type"].wails
    out_tree = ctx.actions.declare_directory(ctx.label.name + "_out")
    workspace_package_dir = _workspace_package_dir(ctx.label.package, ctx.attr.package_dir or ".")
    manifest = write_manifest(
        ctx,
        ctx.label.name + ".manifest",
        ctx.files.srcs,
        workspace_package_dir,
    )

    build_args = ctx.actions.args()
    build_args.add("--clean=%s" % ("true" if ctx.attr.clean else "false"))
    build_args.add("--manifest", manifest.path)
    build_args.add("--mode", "action")
    build_args.add("--out", out_tree.path)
    build_args.add("--out-dir", ctx.attr.out_dir)
    build_args.add("--ts=%s" % ("true" if ctx.attr.ts else "false"))
    build_args.add("--wails", toolchain.executable.path)
    for extra_arg in ctx.attr.extra_args:
        build_args.add("--extra-arg", extra_arg)

    ctx.actions.run(
        executable = ctx.executable._tool,
        arguments = [build_args],
        inputs = depset(ctx.files.srcs + [manifest, toolchain.go_executable]),
        outputs = [out_tree],
        tools = [
            ctx.executable._tool,
            toolchain.files_to_run,
        ],
        env = {
            "GO_BIN": toolchain.go_executable.path,
        },
        mnemonic = "WailsGenerateBindings",
        progress_message = "Generating Wails bindings for %s" % ctx.label,
    )

    launcher = ctx.actions.declare_file(ctx.label.name)
    resolve_lines = """
tool="$(resolve_runfile "{tool_short_path}")"
wails="$(resolve_runfile "{wails_short_path}")"
go_bin="$(resolve_runfile "{go_short_path}")"
workspace_root="${{BUILD_WORKSPACE_DIRECTORY:-}}"
if [[ -z "$workspace_root" ]]; then
  echo "BUILD_WORKSPACE_DIRECTORY is required for bazel run bindings generation" >&2
  exit 1
fi
workspace_package_dir="{workspace_package_dir}"
if [[ -n "$workspace_package_dir" && "$workspace_package_dir" != "." ]]; then
  workspace_package_dir="${{workspace_root}}/${{workspace_package_dir}}"
else
  workspace_package_dir="${{workspace_root}}"
fi
""".format(
        tool_short_path = ctx.executable._tool.short_path,
        wails_short_path = toolchain.executable.short_path,
        go_short_path = toolchain.go_executable.short_path,
        workspace_package_dir = workspace_package_dir or ".",
    )

    extra_args_lines = "\n".join([
        'cmd+=(--extra-arg %s)' % _shell_quote(arg)
        for arg in ctx.attr.extra_args
    ])
    command_lines = """
cmd=(
  "$tool"
  --clean={clean}
  --mode workspace
  --out-dir {out_dir}
  --package-dir "$workspace_package_dir"
  --ts={ts}
  --wails "$wails"
)
{extra_args_lines}
export GO_BIN="$go_bin"
exec "${{cmd[@]}}"
""".format(
        clean = "true" if ctx.attr.clean else "false",
        out_dir = _shell_quote(ctx.attr.out_dir),
        ts = "true" if ctx.attr.ts else "false",
        extra_args_lines = extra_args_lines,
    )

    ctx.actions.write(
        output = launcher,
        is_executable = True,
        content = bash_launcher(resolve_lines, command_lines),
    )

    runfiles = ctx.runfiles(
        files = [
            ctx.executable._tool,
            toolchain.executable,
            toolchain.go_executable,
        ],
        transitive_files = depset(transitive = [
            ctx.attr._tool[DefaultInfo].default_runfiles.files,
            toolchain.default_runfiles.files,
            toolchain.go_default_runfiles.files,
        ]),
    )

    return [
        DefaultInfo(
            executable = launcher,
            files = depset([out_tree]),
            runfiles = runfiles,
        ),
    ]

def _shell_quote(value):
    return "'" + value.replace("'", "'\"'\"'") + "'"

_wails_generate_bindings_rule = rule(
    implementation = _wails_generate_bindings_impl,
    attrs = {
        "clean": attr.bool(default = True),
        "extra_args": attr.string_list(),
        "out_dir": attr.string(mandatory = True),
        "package_dir": attr.string(default = "."),
        "srcs": attr.label_list(
            allow_files = True,
            mandatory = True,
        ),
        "ts": attr.bool(default = True),
        "_tool": attr.label(
            default = "//wails/tools:generate_bindings_action",
            cfg = "exec",
            executable = True,
        ),
    },
    executable = True,
    toolchains = ["//wails:toolchain_type"],
)

def wails_generate_bindings(
        name,
        out_dir,
        package_dir = ".",
        clean = True,
        ts = True,
        extra_args = None,
        tags = None,
        visibility = None):
    include_pattern, exclude_patterns = _package_glob_patterns(package_dir, out_dir)

    _wails_generate_bindings_rule(
        name = name,
        clean = clean,
        extra_args = extra_args or [],
        out_dir = out_dir,
        package_dir = package_dir,
        srcs = native.glob([include_pattern], exclude = exclude_patterns),
        tags = tags,
        visibility = visibility,
        ts = ts,
    )
