/* 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 <functional>
#include <iosfwd>
#include <map>
#include <set>
#include <string>
#include <vector>

#include <cm/optional>
#include <cm/string_view>

#include "cmDiagnostics.h"
#include "cmGeneratorExpression.h"
#include "cmMessageType.h"

class cmExportSet;
class cmGeneratorTarget;
class cmGeneratorFileSet;
class cmLocalGenerator;

/** \class cmExportFileGenerator
 * \brief Generate files exporting targets from a build or install tree.
 *
 * cmExportFileGenerator is the interface class for generating export files.
 */
class cmExportFileGenerator
{
public:
  cmExportFileGenerator();
  virtual ~cmExportFileGenerator() = default;

  struct ExportInfo
  {
    std::vector<std::string> Files;
    std::set<std::string> Sets;
    std::set<std::string> Namespaces;
  };

  /** Set the full path to the export file to generate.  */
  void SetExportFile(char const* mainFile);
  std::string const& GetMainExportFileName() const;

  /** Set the namespace in which to place exported target names.  */
  void SetNamespace(std::string const& ns) { this->Namespace = ns; }
  std::string GetNamespace() const { return this->Namespace; }

  /** Add a configuration to be exported.  */
  void AddConfiguration(std::string const& config);

  /** Create and actually generate the export file.  Returns whether there was
      an error.  */
  bool GenerateImportFile();

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

  // Collect properties with detailed information about targets beyond
  // their location on disk.
  void SetImportDetailProperties(std::string const& config,
                                 std::string const& suffix,
                                 cmGeneratorTarget const* target,
                                 ImportPropertyMap& properties);

  enum class ImportLinkPropertyTargetNames
  {
    Yes,
    No,
  };
  template <typename T>
  void SetImportLinkProperty(std::string const& suffix,
                             cmGeneratorTarget const* target,
                             std::string const& propName,
                             std::vector<T> const& entries,
                             ImportPropertyMap& properties,
                             ImportLinkPropertyTargetNames targetNames);

  /** Generate the export file to the given output stream.  Returns whether
      there was an error.  */
  virtual bool GenerateImportFile(std::ostream& os) = 0;

  /** Each subclass knows how to generate its kind of export file.  */
  virtual bool GenerateMainFile(std::ostream& os) = 0;

  /** Generate per-configuration target information to the given output
      stream.  */
  virtual void GenerateImportConfig(std::ostream& os,
                                    std::string const& config);

  /** Each subclass knows where the target files are located.  */
  virtual void GenerateImportTargetsConfig(std::ostream& os,
                                           std::string const& config,
                                           std::string const& suffix) = 0;

  /** Record a target referenced by an exported target. */
  virtual bool NoteLinkedTarget(cmGeneratorTarget const* target,
                                std::string const& linkedName,
                                cmGeneratorTarget const* linkedTarget);

  /** Each subclass knows how to deal with a target that is  missing from an
   *  export set.  */
  virtual void HandleMissingTarget(std::string& link_libs,
                                   cmGeneratorTarget const* depender,
                                   cmGeneratorTarget* dependee) = 0;

  /** Complain when a duplicate target is encountered.  */
  virtual void ComplainAboutDuplicateTarget(
    std::string const& targetName) const = 0;

  virtual cm::string_view GetImportPrefixWithSlash() const = 0;

  void AddImportPrefix(std::string& exportDirs) const;

  void PopulateInterfaceProperty(std::string const& propName,
                                 cmGeneratorTarget const* target,
                                 ImportPropertyMap& properties) const;
  void PopulateInterfaceProperty(std::string const& propName,
                                 cmGeneratorTarget const* target,
                                 cmGeneratorExpression::PreprocessContext,
                                 ImportPropertyMap& properties);
  void PopulateFileSetInterfaceProperty(
    std::string const& propName, cmGeneratorTarget const* target,
    cmGeneratorFileSet const* fileSet,
    cmGeneratorExpression::PreprocessContext, ImportPropertyMap& properties);
  bool PopulateInterfaceLinkLibrariesProperty(
    cmGeneratorTarget const* target, cmGeneratorExpression::PreprocessContext,
    ImportPropertyMap& properties);

  bool PopulateInterfaceProperties(
    cmGeneratorTarget const* target,
    std::string const& includesDestinationDirs,
    cmGeneratorExpression::PreprocessContext preprocessRule,
    ImportPropertyMap& properties);

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

  virtual void IssueMessage(MessageType type,
                            std::string const& message) const = 0;
  virtual void IssueDiagnostic(cmDiagnosticCategory category,
                               std::string const& message) const = 0;

  void ReportError(std::string const& errorMessage) const
  {
    this->IssueMessage(MessageType::FATAL_ERROR, errorMessage);
  }

  /** Find the set of export files and the unique namespace (if any) for a
   *  target. */
  virtual ExportInfo FindExportInfo(cmGeneratorTarget const* target) const = 0;

  enum FreeTargetsReplace
  {
    ReplaceFreeTargets,
    NoReplaceFreeTargets
  };

  void ResolveTargetsInGeneratorExpressions(
    std::string& input, cmGeneratorTarget const* target,
    FreeTargetsReplace replace = NoReplaceFreeTargets);

  virtual cmExportSet* GetExportSet() const { return nullptr; }

  virtual void ReplaceInstallPrefix(std::string& input) const;

  virtual std::string InstallNameDir(cmGeneratorTarget const* target,
                                     std::string const& config) = 0;

  /** Get the temporary location of the config-agnostic C++ module file.  */
  virtual std::string GetCxxModuleFile(std::string const& name) const = 0;

  virtual std::string GetCxxModulesDirectory() const = 0;
  virtual void GenerateCxxModuleConfigInformation(std::string const&,
                                                  std::ostream& os) const = 0;

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

  static std::string PropertyConfigSuffix(std::string const& config);

  // The namespace in which the exports are placed in the generated file.
  std::string Namespace;

  // The set of configurations to export.
  std::vector<std::string> Configurations;

  // The file to generate.
  std::string MainImportFile;
  std::string FileDir;
  std::string FileBase;
  std::string FileExt;
  bool AppendMode = false;

  // The set of targets included in the export.
  std::set<cmGeneratorTarget const*> ExportedTargets;

  std::vector<std::string> MissingTargets;

  std::set<cmGeneratorTarget const*> ExternalTargets;

private:
  void PopulateInterfaceProperty(std::string const& propName,
                                 std::string const& outputName,
                                 cmGeneratorTarget const* target,
                                 cmGeneratorExpression::PreprocessContext,
                                 ImportPropertyMap& properties);

  void PopulateCompatibleInterfaceProperties(
    cmGeneratorTarget const* target, ImportPropertyMap& properties) const;
  void PopulateCustomTransitiveInterfaceProperties(
    cmGeneratorTarget const* target,
    cmGeneratorExpression::PreprocessContext preprocessRule,
    ImportPropertyMap& properties);
  bool PopulateCxxModuleExportProperties(
    cmGeneratorTarget const* gte, ImportPropertyMap& properties,
    cmGeneratorExpression::PreprocessContext ctx,
    std::string const& includesDestinationDirs, std::string& errorMessage);
  bool PopulateExportProperties(cmGeneratorTarget const* gte,
                                ImportPropertyMap& properties,
                                std::string& errorMessage) const;

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

extern template void cmExportFileGenerator::SetImportLinkProperty<std::string>(
  std::string const&, cmGeneratorTarget const*, std::string const&,
  std::vector<std::string> const&, ImportPropertyMap& properties,
  ImportLinkPropertyTargetNames);

extern template void cmExportFileGenerator::SetImportLinkProperty<cmLinkItem>(
  std::string const&, cmGeneratorTarget const*, std::string const&,
  std::vector<cmLinkItem> const&, ImportPropertyMap& properties,
  ImportLinkPropertyTargetNames);

/** Walk a generator expression and rewrite the target-name slot of each
    `$<TARGET_PROPERTY:>`, `$<TARGET_NAME:>`, `$<LINK_ONLY:>`, and
    `$<COMPILE_ONLY:>` construct via the provided callback.  The callback
    receives the bare target name; if it mutates the name and returns true,
    the helper splices the new name back into the genex. Returns the
    parse-level error message, if an error was encountered. Callers handle
    install-prefix substitution and error reporting themselves. This is used
    by the cmExportFileGenerator hierarchy, as well as the cmSbomBuilder
    hierarchy.  */
cm::optional<std::string> cmResolveTargetsInGeneratorExpression(
  std::string& input,
  std::function<bool(std::string& name)> const& addTargetNamespace);
