// Copyright 2025 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.packages;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.collect.ImmutableSortedMap;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.packages.TargetRecorder.MacroNamespaceViolationException;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import java.util.OptionalLong;
import javax.annotation.Nullable;

/**
 * A package-like entity (either a full {@link Package} or a package piece) which serves as a
 * container of {@link Target} and {@link MacroInstance} objects and links them to the package's
 * metadata and declarations.
 *
 * <p>Each {@link Target} or {@link MacroInstance} object is uniquely owned one packageoid (for
 * targets, that's the packageoid returned by {@link Target#getPackageoid()}). In some cases, the
 * target or macro instance may also be referenced from other packageoids, provided that this
 * doesn't create a skyframe cycle.
 *
 * <p>To obtain a {@link Package} from a {@link Packageoid}, use a PackageProvider or skyframe
 * machinery.
 */
public abstract class Packageoid {
  /** Sentinel value for package overhead being empty. */
  protected static final long PACKAGE_OVERHEAD_UNSET = -1;

  // ==== General package metadata fields ====

  protected final Package.Metadata metadata;

  protected Package.Declarations declarations;

  // ==== Common metadata fields ====

  /**
   * True iff this packageoid's Starlark files contained lexical or grammatical errors, or
   * experienced errors during evaluation, or semantic errors during the construction of any rule.
   *
   * <p>Note: A packageoid containing errors does not necessarily prevent a build; if all the rules
   * needed for a given build were constructed prior to the first error, the build may proceed.
   */
  protected boolean containsErrors = false;

  /**
   * The first detailed error encountered during this packageoid's construction and evaluation, or
   * {@code null} if there were no such errors or all its errors lacked details.
   */
  @Nullable protected FailureDetail failureDetail;

  protected long computationSteps = 0;

  /**
   * A rough approximation of the memory and general accounting costs associated with a loaded
   * packageoid. A value of -1 means it is unset. Stored as a long to take up less memory per pkg.
   */
  protected long packageOverhead = PACKAGE_OVERHEAD_UNSET;

  // ==== Common target and macro fields ====

  /**
   * The collection of all targets defined in this packageoid, indexed by name. Null until the
   * packageoid is fully initialized by its builder's {@code finishBuild()}.
   */
  // TODO(bazel-team): Clarify what this map contains when a rule and its output both share the same
  // name.
  @Nullable protected ImmutableSortedMap<String, Target> targets;

  /**
   * Returns the metadata of the package; in other words, information which is known about a package
   * before BUILD file evaluation has started.
   */
  public Package.Metadata getMetadata() {
    return metadata;
  }

  /**
   * Returns the package's identifier. This is a convenience wrapper for {@link
   * Package.Metadata#packageIdentifier()}.
   */
  public PackageIdentifier getPackageIdentifier() {
    return getMetadata().packageIdentifier();
  }

  /**
   * Returns data about the package which is known after BUILD file evaluation without expanding
   * symbolic macros.
   */
  public Package.Declarations getDeclarations() {
    return declarations;
  }

  /**
   * Returns the label for the package's BUILD file.
   *
   * <p>Typically, <code>getBuildFileLabel().getName().equals("BUILD")</code> -- though not
   * necessarily: data in a subdirectory of a test package may use a different filename to avoid
   * inadvertently creating a new package.
   */
  public Label getBuildFileLabel() {
    return getMetadata().buildFileLabel();
  }

  /**
   * Returns a short, lower-case description of this packageoid, e.g. for use in logging and error
   * messages.
   */
  public abstract String getShortDescription();

  /**
   * Returns an (immutable, ordered) view of all the targets belonging to this packageoid. Note that
   * if this packageoid is a package piece, this method does not search for targets in any other
   * package pieces.
   */
  public ImmutableSortedMap<String, Target> getTargets() {
    return targets;
  }

  /**
   * Returns true if errors were encountered during evaluation of this packageoid.
   *
   * <p>If a packageoid contains errors, it may be incomplete and its contents should not be relied
   * upon for critical operations. All rules in such a packageoid will have their {@link
   * Rule#containsErrors()} flag set to true.
   */
  public boolean containsErrors() {
    return containsErrors;
  }

  /**
   * Marks this packageoid as in error.
   *
   * <p>This method may only be called while the packageoid is being constructed. Intended only for
   * use by {@link Rule#reportError}, since its callers might not have access to the packageoid's
   * builder instance.
   *
   * @throws IllegalStateException if this packageoid has completed construction.
   */
  void setContainsErrors() {
    checkState(
        targets == null,
        "setContainsErrors() can only be called while the packageoid is being constructed");
    containsErrors = true;
  }

  /**
   * Returns the first {@link FailureDetail} describing one of the packageoid's errors, or {@code
   * null} if it has no errors or all its errors lack details.
   */
  @Nullable
  public FailureDetail getFailureDetail() {
    return failureDetail;
  }

  /**
   * Returns the number of Starlark computation steps executed during the evaluation of this
   * packageoid.
   */
  public long getComputationSteps() {
    return computationSteps;
  }

  /** Returns package overhead as configured by the configured {@link PackageOverheadEstimator}. */
  public OptionalLong getPackageOverhead() {
    return packageOverhead == PACKAGE_OVERHEAD_UNSET
        ? OptionalLong.empty()
        : OptionalLong.of(packageOverhead);
  }

  /**
   * Throws {@link MacroNamespaceViolationException} if the given target (which must be a member of
   * this packageoid) violates macro naming rules.
   */
  public abstract void checkMacroNamespaceCompliance(Target target)
      throws MacroNamespaceViolationException;

  /**
   * Returns the target (a member of this packagoid) whose name is "targetName". First rules are
   * searched, then output files, then input files. The target name must be valid, as defined by
   * {@code LabelValidator#validateTargetName}.
   *
   * <p>Use with care. In particular, note that {@code target.getPackageoid().getTarget("sibling")}
   * will succeed for all package-wide sibling targets if the packageoid is a package, but will
   * throw for targets belonging to a different package piece if the packageoid is a package piece.
   *
   * @throws NoSuchTargetException if the specified target was not found in this packageoid.
   */
  public abstract Target getTarget(String targetName) throws NoSuchTargetException;

  protected Packageoid(Package.Metadata metadata, Package.Declarations declarations) {
    this.metadata = checkNotNull(metadata);
    this.declarations = checkNotNull(declarations);
  }
}
