/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once

#include "cmConfigure.h" // IWYU pragma: keep

#include <iosfwd>
#include <map>
#include <set>
#include <string>
#include <vector>

#include "cmExportFileGenerator.h"
#include "cmFindPackageStack.h"
#include "cmGeneratorExpression.h"
#include "cmSbomArguments.h"

class cmExportSet;
class cmGeneratorTarget;
class cmLocalGenerator;
struct cmSbomDocument;
struct cmSpdxDocument;
struct cmSpdxPackage;
struct cmSpdxCreationInfo;

/** \class cmSbomBuilder
 *  \brief Abstract base for SBOM document generators.
 *
 *  Concrete leaves (cmBuildSbomBuilder, cmInstallSbomBuilder) supply two
 *  pure virtuals:
 *
 *  - Generate(): selects the generator-expression preprocess context
 *    (BuildInterface vs InstallInterface) and delegates to the shared
 *    GenerateForTargets() body.
 *  - FindExportInfoFor(): consults the mode's export map (build-tree vs
 *    install-tree) to resolve cross-export references.
 *
 *  All shared SPDX assembly, link-graph walking, and serialization lives
 *  here so that the leaves stay trivial.
 */
class cmSbomBuilder
{
public:
  virtual ~cmSbomBuilder() = default;

  /** Compute phase: wire the local generator, run Compute() on the owned
   *  export sets, and populate the SbomTargets cache so peer SBOMs can
   *  query CoversTarget() before any Generate() runs. */
  void Compute(cmLocalGenerator* lg);

  /** Produce the SBOM document on `os`.  Implementations pick the
   *  generator-expression preprocess context (BuildInterface vs
   *  InstallInterface) and delegate to GenerateForTargets. */
  virtual bool Generate(std::ostream& os) = 0;

  /** Identifier this SBOM publishes itself as (the SPDX document name and
   *  the namespace under which other SBOMs refer to its contents). */
  std::string const& GetPackageName() const { return this->PackageName; }

  /** True if `target` is one of the targets this SBOM directly describes. */
  bool CoversTarget(cmGeneratorTarget const* target) const;

  /** True if `set` is one of the export sets this SBOM was built from. */
  bool CoversExportSet(cmExportSet const* set) const;

  /** Names of peer SBOMs (same build/install mode) that cover a target.
   *  Used by NoteLinkedTarget to attribute a cross-reference when no
   *  install(export) namespace is available.  Sorted alphabetically. */
  struct SbomInfo
  {
    std::vector<std::string> Packages;
  };

protected:
  cmSbomBuilder(cmSbomArguments args, std::vector<cmExportSet*> exportSets,
                cmLocalGenerator* lg);

  /** Mode-specific: where does `target` appear in the project's exports?
   *  Build leaves consult cmGlobalGenerator::FindBuildExportInfo;
   *  install leaves consult cmGlobalGenerator::FindInstallExportInfo. */
  virtual cmExportFileGenerator::ExportInfo FindExportInfoFor(
    cmGeneratorTarget const* target) const = 0;

  /** Mode-specific: which peer SBOMs cover `target`?
   *  Build leaves consult cmGlobalGenerator::FindBuildSbomInfo;
   *  install leaves consult cmGlobalGenerator::FindInstallSbomInfo. */
  virtual SbomInfo FindSbomInfoFor(cmGeneratorTarget const* target) const = 0;

  /** The set of targets the SBOM directly describes - derived from the
   *  associated export sets. */
  std::set<cmGeneratorTarget const*> CollectTargets() const;

  /** Generate an sbom for the targets in this->SbomTargets. Each leaf class
   * calls this internally */
  bool GenerateForTargets(
    std::ostream& os,
    cmGeneratorExpression::PreprocessContext preprocessContext);

  using ImportPropertyMap = std::map<std::string, std::string>;

  struct TargetProperties
  {
    cmSpdxPackage const* Package;
    cmGeneratorTarget const* Target;
    ImportPropertyMap Properties;
  };

  void WriteSbom(cmSbomDocument& doc, std::ostream& os) const;

  cmSpdxCreationInfo GenerateCreationInfo() const;
  cmSpdxDocument GenerateSbom(cmSpdxCreationInfo const* ci) const;
  cmSpdxPackage GenerateImportTarget(cmSpdxCreationInfo const* ci,
                                     cmGeneratorTarget const* target) const;

  bool AddPackageInformation(cmSpdxPackage& artifact, std::string const& name,
                             cmPackageInformation const& package) const;

  bool GenerateProperties(
    cmSbomDocument& doc, cmSpdxDocument* project, cmSpdxCreationInfo const* ci,
    TargetProperties const& current,
    std::vector<TargetProperties> const& allTargets) const;

  void GenerateLinkProperties(
    cmSbomDocument& doc, cmSpdxDocument* project, cmSpdxCreationInfo const* ci,
    std::string const& libraries, TargetProperties const& current,
    std::vector<TargetProperties> const& allTargets) const;

  bool NoteLinkedTarget(cmGeneratorTarget const* target,
                        std::string const& linkedName,
                        cmGeneratorTarget const* linkedTarget);

  bool AddTargetNamespace(std::string& input, cmGeneratorTarget const* target,
                          cmLocalGenerator const* lg);

  void ResolveTargetsInGeneratorExpression(std::string& input,
                                           cmGeneratorTarget const* target,
                                           cmLocalGenerator const* lg);
  void ResolveTargetsInGeneratorExpressions(std::string& input,
                                            cmGeneratorTarget const* target);

  bool PopulateInterfaceLinkLibrariesProperty(
    cmGeneratorTarget const* target,
    cmGeneratorExpression::PreprocessContext preprocessRule,
    ImportPropertyMap& properties);

  bool PopulateLinkLibrariesProperty(cmGeneratorTarget const* target,
                                     cmGeneratorExpression::PreprocessContext,
                                     ImportPropertyMap& properties);

  // Set at construction or via setters
  cmLocalGenerator* LocalGenerator = nullptr;

  // Inputs
  std::vector<cmExportSet*> ExportSets;

private:
  struct LinkInfo
  {
    std::string Package;
    std::string Component;
  };

  // Metadata
  std::string const PackageName;
  std::string const Namespace;
  std::string const PackageVersion;
  std::string const PackageDescription;
  std::string const PackageWebsite;
  std::string const PackageUrl;
  std::string const PackageLicense;
  cmSbomArguments::SbomFormat const PackageFormat;

  // Derived from inputs at generate time
  std::set<cmGeneratorTarget const*> SbomTargets;

  // Accumulated during generation
  std::map<std::string, LinkInfo> LinkTargets;
  std::map<std::string, cmPackageInformation> Requirements;
};
