// Copyright 2023 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package com.google.devtools.build.lib.bazel.bzlmod;

import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import javax.annotation.Nullable;

/**
 * A SkyFunction that computes the {@link RepositoryMapping#entries()} for the repos generated by a
 * module extension. While the repos differ in {@link RepositoryMapping#contextRepo()}, the entries
 * are identical and sharing the instance ensures that memory and CPU usage are only linear in the
 * number of repos.
 */
public class ModuleExtensionRepoMappingEntriesFunction implements SkyFunction {

  @Override
  @Nullable
  public SkyValue compute(SkyKey skyKey, Environment env) throws InterruptedException {
    BazelDepGraphValue bazelDepGraphValue =
        (BazelDepGraphValue) env.getValue(BazelDepGraphValue.KEY);
    if (bazelDepGraphValue == null) {
      return null;
    }

    var moduleExtensionId = ((ModuleExtensionRepoMappingEntriesValue.Key) skyKey).argument();
    var extensionEvalValue =
        (SingleExtensionValue) env.getValue(SingleExtensionValue.key(moduleExtensionId));
    if (extensionEvalValue == null) {
      return null;
    }

    return computeRepoMappingEntries(moduleExtensionId, extensionEvalValue, bazelDepGraphValue);
  }

  /**
   * Calculates repo mappings for a repo generated from a module extension. Such a repo can see all
   * repos generated by the same module extension, as well as all repos that the Bazel module
   * hosting the extension can see (see above).
   */
  private ModuleExtensionRepoMappingEntriesValue computeRepoMappingEntries(
      ModuleExtensionId extensionId,
      SingleExtensionValue extensionEvalValue,
      BazelDepGraphValue bazelDepGraphValue) {
    // Find the key of the module containing this extension. This will be used to compute additional
    // mappings -- any repo generated by an extension contained in the module "foo" can additionally
    // see all repos that "foo" can see.
    ModuleKey moduleKey =
        bazelDepGraphValue
            .getCanonicalRepoNameLookup()
            .get(extensionId.bzlFileLabel().getRepository());
    // NOTE(wyv): This means that if "foo" has a bazel_dep with the repo name "bar", and the
    // extension generates an internal repo name "bar", then within a repo generated by the
    // extension, "bar" will refer to the latter. We should explore a way to differentiate between
    // the two to avoid any surprises.
    // LINT.IfChange
    ImmutableMap.Builder<String, RepositoryName> entries = ImmutableMap.builder();
    entries.putAll(bazelDepGraphValue.getFullRepoMapping(moduleKey).entries());
    entries.putAll(extensionEvalValue.canonicalRepoNameToInternalNames().inverse());
    entries.putAll(bazelDepGraphValue.getRepoOverrides().row(extensionId));
    var builtEntries = entries.buildKeepingLast();
    return ModuleExtensionRepoMappingEntriesValue.create(
        builtEntries, builtEntries.hashCode(), moduleKey);
    // LINT.ThenChange(//src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionEvalStarlarkThreadContext.java)
  }
}
