# Copyright Google Inc. All Rights Reserved.
#
# Use of this source code is governed by an MIT-style license that can be
# found in the LICENSE file at https://angular.io/license

load(":rules_typescript.bzl",
    "tsc_wrapped_tsconfig",
    "COMMON_ATTRIBUTES",
    "compile_ts",
    "DEPS_ASPECTS",
    "ts_providers_dict_to_struct",
    "json_marshal",
)

# Calculate the expected output of the template compiler for every source in
# in the library. Most of these will be produced as empty files but it is
# unknown, without parsing, which will be empty.
def _expected_outs(ctx, label):
  devmode_js_files = []
  closure_js_files = []
  declaration_files = []
  summary_files = []

  codegen_inputs = ctx.files.srcs

  for src in ctx.files.srcs + ctx.files.assets:
    if src.short_path.endswith(".ts") and not src.short_path.endswith(".d.ts"):
      basename = src.short_path[len(ctx.label.package) + 1:-len(".ts")]
      devmode_js = [
          ".ngfactory.js",
          ".ngsummary.js",
          ".js",
      ]
      summaries = [".ngsummary.json"]

    elif src.short_path.endswith(".css"):
      basename = src.short_path[len(ctx.label.package) + 1:-len(".css")]
      devmode_js = [
          ".css.shim.ngstyle.js",
          ".css.ngstyle.js",
      ]
      summaries = []

    closure_js = [f.replace(".js", ".closure.js") for f in devmode_js]
    declarations = [f.replace(".js", ".d.ts") for f in devmode_js]

    devmode_js_files += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in devmode_js]
    closure_js_files += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in closure_js]
    declaration_files += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in declarations]
    summary_files += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in summaries]

  return struct(
    closure_js = closure_js_files,
    devmode_js = devmode_js_files,
    declarations = declaration_files,
    summaries = summary_files,
  )

def _ngc_tsconfig(ctx, files, srcs, **kwargs):
  outs = _expected_outs(ctx, ctx.label)
  if "devmode_manifest" in kwargs:
    expected_outs = outs.devmode_js + outs.declarations + outs.summaries
  else:
    expected_outs = outs.closure_js

  return dict(tsc_wrapped_tsconfig(ctx, files, srcs, **kwargs), **{
      "angularCompilerOptions": {
          "generateCodeForLibraries": False,
          # FIXME: wrong place to de-dupe
          "expectedOut": depset([o.path for o in expected_outs]).to_list()
      }
  })

def _collect_summaries_aspect_impl(target, ctx):
  results = target.angular.summaries if hasattr(target, "angular") else depset()

  # If we are visiting empty-srcs ts_library, this is a re-export
  srcs = ctx.rule.attr.srcs if hasattr(ctx.rule.attr, "srcs") else []

  # "re-export" rules should expose all the files of their deps
  if not srcs:
    for dep in ctx.rule.attr.deps:
      if (hasattr(dep, "angular")):
        results += dep.angular.summaries

  return struct(collect_summaries_aspect_result = results)

_collect_summaries_aspect = aspect(
    implementation = _collect_summaries_aspect_impl,
    attr_aspects = ["deps"],
)

def _compile_action(ctx, inputs, outputs, config_file_path):
  summaries = depset()
  for dep in ctx.attr.deps:
    if hasattr(dep, "collect_summaries_aspect_result"):
      summaries += dep.collect_summaries_aspect_result

  action_inputs = inputs + summaries.to_list() + ctx.files.assets
  # print("ASSETS", [a.path for a in ctx.files.assets])
  # print("INPUTS", ctx.label, [o.path for o in summaries if o.path.find("core/src") > 0])

  if hasattr(ctx.attr, "node_modules"):
    action_inputs += [f for f in ctx.files.node_modules
                      if f.path.endswith(".ts") or f.path.endswith(".json")]
  if hasattr(ctx.attr, "tsconfig") and ctx.file.tsconfig:
    action_inputs += [ctx.file.tsconfig]

  arguments = ["--node_options=--expose-gc"]
  # One at-sign makes this a params-file, enabling the worker strategy.
  # Two at-signs escapes the argument so it's passed through to ngc
  # rather than the contents getting expanded.
  if ctx.attr._supports_workers:
    arguments += ["@@" + config_file_path]
  else:
    arguments += ["-p", config_file_path]

  ctx.action(
      progress_message = "Compiling Angular templates (ngc) %s" % ctx.label,
      mnemonic = "AngularTemplateCompile",
      inputs = action_inputs,
      outputs = outputs,
      arguments = arguments,
      executable = ctx.executable.compiler,
      execution_requirements = {
          "supports-workers": str(int(ctx.attr._supports_workers)),
      },
  )

def _prodmode_compile_action(ctx, inputs, outputs, config_file_path):
  outs = _expected_outs(ctx, ctx.label)
  _compile_action(ctx, inputs, outputs + outs.closure_js, config_file_path)

def _devmode_compile_action(ctx, inputs, outputs, config_file_path):
  outs = _expected_outs(ctx, ctx.label)
  _compile_action(ctx, inputs, outputs + outs.devmode_js + outs.declarations + outs.summaries, config_file_path)

def ng_module_impl(ctx, ts_compile_actions):
  providers = ts_compile_actions(
      ctx, is_library=True, compile_action=_prodmode_compile_action,
      devmode_compile_action=_devmode_compile_action,
      tsc_wrapped_tsconfig=_ngc_tsconfig,
      outputs = _expected_outs)

  #addl_declarations = [_expected_outs(ctx)]
  #providers["typescript"]["declarations"] += addl_declarations
  #providers["typescript"]["transitive_declarations"] += addl_declarations
  providers["angular"] = {
    "summaries": _expected_outs(ctx, ctx.label).summaries
  }

  return providers

def _ng_module_impl(ctx):
  return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts))

NG_MODULE_ATTRIBUTES = {
    "srcs": attr.label_list(allow_files = [".ts"]),

    "deps": attr.label_list(aspects = DEPS_ASPECTS + [_collect_summaries_aspect]),

    "assets": attr.label_list(allow_files = [
      ".css",
      # TODO(alexeagle): change this to ".ng.html" when usages updated
      ".html",
    ]),

    # TODO(alexeagle): wire up when we have i18n in bazel
    "no_i18n": attr.bool(default = False),

    "compiler": attr.label(
        default = Label("//src/ngc-wrapped"),
        executable = True,
        cfg = "host",
    ),

    "_supports_workers": attr.bool(default = True),
}

ng_module = rule(
    implementation = _ng_module_impl,
    attrs = COMMON_ATTRIBUTES + NG_MODULE_ATTRIBUTES + {
        "tsconfig": attr.label(allow_files = True, single_file = True),

        # @// is special syntax for the "main" repository
        # The default assumes the user specified a target "node_modules" in their
        # root BUILD file.
        "node_modules": attr.label(
            default = Label("@//:node_modules")
        ),
    },
)