/* 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 <map>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>

#include <cm/optional>

#include "cmDiagnostics.h"
#include "cmJSONState.h"
#include "cmStateTypes.h" // IWYU pragma: keep

#include "CTest/cmCTestTypes.h" // IWYU pragma: keep

enum class PackageResolveMode;

class cmCMakePresetsGraph
{
public:
  std::string errors;
  cmJSONState parseState;

  enum class ArchToolsetStrategy
  {
    Set,
    External,
  };

  enum class TraceEnableMode
  {
    Disable,
    Default,
    Expand,
  };

  struct CacheVariable
  {
    std::string Type;
    std::string Value;
  };

  class Condition;

  struct File
  {
    std::unordered_set<File*> ReachableFiles;
    std::string Filename;
    int Version;
  };

  class Preset
  {
  public:
    Preset() = default;
    Preset(Preset&& /*other*/) = default;
    Preset(Preset const& /*other*/) = default;
    Preset& operator=(Preset const& /*other*/) = default;
    virtual ~Preset() = default;
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
    Preset& operator=(Preset&& /*other*/) = default;
#else
    // The move assignment operators for several STL classes did not become
    // noexcept until C++17, which causes some tools to warn about this move
    // assignment operator throwing an exception when it shouldn't.
    Preset& operator=(Preset&& /*other*/) = delete;
#endif

    std::string Name;
    std::vector<std::string> Inherits;
    bool Hidden = false;
    File* OriginFile;
    std::string DisplayName;
    std::string Description;

    std::shared_ptr<Condition> ConditionEvaluator;
    bool ConditionResult = true;

    std::map<std::string, cm::optional<std::string>> Environment;

    std::string ErrorDetail;

    virtual bool VisitPresetInherit(Preset const& parent) = 0;
    virtual bool VisitPresetBeforeInherit() { return true; }

    virtual bool VisitPresetAfterInherit(int /* version */,
                                         cmJSONState* /*state*/)
    {
      return true;
    }
  };

  class ConfigurePreset : public Preset
  {
  public:
    ConfigurePreset() = default;
    ConfigurePreset(ConfigurePreset&& /*other*/) = default;
    ConfigurePreset(ConfigurePreset const& /*other*/) = default;
    ConfigurePreset& operator=(ConfigurePreset const& /*other*/) = default;
    ~ConfigurePreset() override = default;
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
    ConfigurePreset& operator=(ConfigurePreset&& /*other*/) = default;
#else
    // The move assignment operators for several STL classes did not become
    // noexcept until C++17, which causes some tools to warn about this move
    // assignment operator throwing an exception when it shouldn't.
    ConfigurePreset& operator=(ConfigurePreset&& /*other*/) = delete;
#endif

    std::string Generator;
    std::string Architecture;
    cm::optional<ArchToolsetStrategy> ArchitectureStrategy;
    std::string Toolset;
    cm::optional<ArchToolsetStrategy> ToolsetStrategy;
    std::string ToolchainFile;
    std::string GraphVizFile;
    std::string BinaryDir;
    std::string InstallDir;

    std::map<std::string, cm::optional<CacheVariable>> CacheVariables;

    std::map<cmDiagnosticCategory, bool> Warnings;
    std::map<cmDiagnosticCategory, bool> Errors;
    cm::optional<bool> WarnSystemVars;
    cm::optional<bool> WarnDev;  // Deprecated synonym for Warnings.CMD_AUTHOR
    cm::optional<bool> ErrorDev; // Deprecated synonym for Errors.CMD_AUTHOR

    cm::optional<bool> DebugOutput;
    cm::optional<bool> DebugTryCompile;
    cm::optional<bool> DebugFind;

    cm::optional<TraceEnableMode> TraceMode;
    cm::optional<cmTraceEnums::TraceOutputFormat> TraceFormat;
    std::vector<std::string> TraceSource;
    std::string TraceRedirect;

    bool VisitPresetInherit(Preset const& parent) override;
    bool VisitPresetBeforeInherit() override;
    bool VisitPresetAfterInherit(int version, cmJSONState* state) override;

    static char const* kind() { return "configure"; }
  };

  class BuildPreset : public Preset
  {
  public:
    BuildPreset() = default;
    BuildPreset(BuildPreset&& /*other*/) = default;
    BuildPreset(BuildPreset const& /*other*/) = default;
    BuildPreset& operator=(BuildPreset const& /*other*/) = default;
    ~BuildPreset() override = default;
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
    BuildPreset& operator=(BuildPreset&& /*other*/) = default;
#else
    // The move assignment operators for several STL classes did not become
    // noexcept until C++17, which causes some tools to warn about this move
    // assignment operator throwing an exception when it shouldn't.
    BuildPreset& operator=(BuildPreset&& /*other*/) = delete;
#endif

    std::string ConfigurePreset;
    cm::optional<bool> InheritConfigureEnvironment;
    cm::optional<unsigned int> Jobs;
    std::vector<std::string> Targets;
    std::string Configuration;
    cm::optional<bool> CleanFirst;
    cm::optional<bool> Verbose;
    std::vector<std::string> NativeToolOptions;
    cm::optional<PackageResolveMode> ResolvePackageReferences;

    bool VisitPresetInherit(Preset const& parent) override;
    bool VisitPresetAfterInherit(int /* version */,
                                 cmJSONState* /*state*/) override;

    static char const* kind() { return "build"; }
  };

  class TestPreset : public Preset
  {
  public:
    TestPreset() = default;
    TestPreset(TestPreset&& /*other*/) = default;
    TestPreset(TestPreset const& /*other*/) = default;
    TestPreset& operator=(TestPreset const& /*other*/) = default;
    ~TestPreset() override = default;
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
    TestPreset& operator=(TestPreset&& /*other*/) = default;
#else
    // The move assignment operators for several STL classes did not become
    // noexcept until C++17, which causes some tools to warn about this move
    // assignment operator throwing an exception when it shouldn't.
    TestPreset& operator=(TestPreset&& /*other*/) = delete;
#endif

    struct OutputOptions
    {
      enum class VerbosityEnum
      {
        Default,
        Verbose,
        Extra
      };

      cm::optional<bool> ShortProgress;
      cm::optional<VerbosityEnum> Verbosity;
      cm::optional<bool> Debug;
      cm::optional<bool> OutputOnFailure;
      cm::optional<bool> Quiet;
      std::string OutputLogFile;
      std::string OutputJUnitFile;
      cm::optional<bool> LabelSummary;
      cm::optional<bool> SubprojectSummary;
      cm::optional<int> MaxPassedTestOutputSize;
      cm::optional<int> MaxFailedTestOutputSize;
      cm::optional<cmCTestTypes::TruncationMode> TestOutputTruncation;
      cm::optional<int> MaxTestNameWidth;
    };

    struct IncludeOptions
    {
      struct IndexOptions
      {
        cm::optional<int> Start;
        cm::optional<int> End;
        cm::optional<int> Stride;
        std::vector<int> SpecificTests;

        std::string IndexFile;
      };

      std::string Name;
      std::string Label;
      cm::optional<IndexOptions> Index;
      cm::optional<bool> UseUnion;
    };

    struct ExcludeOptions
    {
      struct FixturesOptions
      {
        std::string Any;
        std::string Setup;
        std::string Cleanup;
      };

      std::string Name;
      std::string Label;
      cm::optional<FixturesOptions> Fixtures;
    };

    struct FilterOptions
    {
      cm::optional<IncludeOptions> Include;
      cm::optional<ExcludeOptions> Exclude;
    };

    struct ExecutionOptions
    {
      enum class ShowOnlyEnum
      {
        Human,
        JsonV1
      };

      struct RepeatOptions
      {
        enum class ModeEnum
        {
          UntilFail,
          UntilPass,
          AfterTimeout
        };

        ModeEnum Mode;
        int Count;
      };

      enum class NoTestsActionEnum
      {
        Default,
        Error,
        Ignore
      };

      cm::optional<bool> StopOnFailure;
      cm::optional<bool> EnableFailover;
      cm::optional<cm::optional<unsigned int>> Jobs;
      std::string ResourceSpecFile;
      cm::optional<int> TestLoad;
      cm::optional<ShowOnlyEnum> ShowOnly;

      cm::optional<RepeatOptions> Repeat;
      cm::optional<bool> InteractiveDebugging;
      cm::optional<bool> ScheduleRandom;
      cm::optional<int> Timeout;
      cm::optional<NoTestsActionEnum> NoTestsAction;
      std::vector<std::string> TestPassthroughArguments;
    };

    std::string ConfigurePreset;
    cm::optional<bool> InheritConfigureEnvironment;
    std::string Configuration;
    std::vector<std::string> OverwriteConfigurationFile;
    cm::optional<OutputOptions> Output;
    cm::optional<FilterOptions> Filter;
    cm::optional<ExecutionOptions> Execution;

    bool VisitPresetInherit(Preset const& parent) override;
    bool VisitPresetAfterInherit(int /* version */,
                                 cmJSONState* /*state*/) override;

    static char const* kind() { return "test"; }
  };

  class PackagePreset : public Preset
  {
  public:
    PackagePreset() = default;
    PackagePreset(PackagePreset&& /*other*/) = default;
    PackagePreset(PackagePreset const& /*other*/) = default;
    PackagePreset& operator=(PackagePreset const& /*other*/) = default;
    ~PackagePreset() override = default;
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
    PackagePreset& operator=(PackagePreset&& /*other*/) = default;
#else
    // The move assignment operators for several STL classes did not become
    // noexcept until C++17, which causes some tools to warn about this move
    // assignment operator throwing an exception when it shouldn't.
    PackagePreset& operator=(PackagePreset&& /*other*/) = delete;
#endif

    std::string ConfigurePreset;
    cm::optional<bool> InheritConfigureEnvironment;
    std::vector<std::string> Generators;
    std::vector<std::string> Configurations;
    std::map<std::string, std::string> Variables;
    std::string ConfigFile;

    cm::optional<bool> DebugOutput;
    cm::optional<bool> VerboseOutput;

    std::string PackageName;
    std::string PackageVersion;
    std::string PackageDirectory;
    std::string VendorName;

    bool VisitPresetInherit(Preset const& parent) override;
    bool VisitPresetAfterInherit(int /* version */,
                                 cmJSONState* /*state*/) override;

    static char const* kind() { return "package"; }
  };

  class WorkflowPreset : public Preset
  {
  public:
    WorkflowPreset() = default;
    WorkflowPreset(WorkflowPreset&& /*other*/) = default;
    WorkflowPreset(WorkflowPreset const& /*other*/) = default;
    WorkflowPreset& operator=(WorkflowPreset const& /*other*/) = default;
    ~WorkflowPreset() override = default;
#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
    WorkflowPreset& operator=(WorkflowPreset&& /*other*/) = default;
#else
    // The move assignment operators for several STL classes did not become
    // noexcept until C++17, which causes some tools to warn about this move
    // assignment operator throwing an exception when it shouldn't.
    WorkflowPreset& operator=(WorkflowPreset&& /*other*/) = delete;
#endif

    struct WorkflowStep
    {
      enum class Type
      {
        Configure,
        Build,
        Test,
        Package,
      };
      Type PresetType;
      std::string PresetName;
    };

    std::vector<WorkflowStep> Steps;

    bool VisitPresetInherit(Preset const& parent) override;
    bool VisitPresetAfterInherit(int /* version */,
                                 cmJSONState* /* state */) override;

    static char const* kind() { return "workflow"; }
  };

  template <typename T>
  struct PresetPair
  {
    T Unexpanded;
    cm::optional<T> Expanded;
  };

  enum class PresetResolveStatus
  {
    Success,
    NotFound,
    Hidden,
    InvalidMacroExpansion,
    Disabled,
  };

  // Result type for preset resolution
  template <class T>
  struct PresetResolveResult
  {
    using Status = PresetResolveStatus;

    Status StatusCode = Status::Success;
    std::string ErrorPresetName;
    T const* Preset = nullptr;
  };

  template <class T>
  PresetResolveResult<T> ResolvePreset(
    std::string const& presetName,
    std::map<std::string, PresetPair<T>> const& presets) const;

  // Returns an error message for a preset resolve status,
  // or cm::nullopt on Success.
  template <class T>
  static cm::optional<std::string> FormatPresetError(
    PresetResolveStatus status, std::string const& errorPresetName,
    std::string const& directory);

  std::map<std::string, PresetPair<ConfigurePreset>> ConfigurePresets;
  std::map<std::string, PresetPair<BuildPreset>> BuildPresets;
  std::map<std::string, PresetPair<TestPreset>> TestPresets;
  std::map<std::string, PresetPair<PackagePreset>> PackagePresets;
  std::map<std::string, PresetPair<WorkflowPreset>> WorkflowPresets;

  std::vector<std::string> ConfigurePresetOrder;
  std::vector<std::string> BuildPresetOrder;
  std::vector<std::string> TestPresetOrder;
  std::vector<std::string> PackagePresetOrder;
  std::vector<std::string> WorkflowPresetOrder;

  std::string SourceDir;
  std::vector<std::unique_ptr<File>> Files;

  int GetVersion(Preset const& preset) const
  {
    return preset.OriginFile->Version;
  }

  enum class ReadOption
  {
    RequireFiles,
    AllowNoFiles,
  };

  bool ReadProjectPresets(
    std::string const& sourceDir, std::string const& presetsFile,
    ReadOption readFilesOption = ReadOption::RequireFiles);

  std::string GetGeneratorForPreset(std::string const& presetName) const;

  void PrintConfigurePresetList() const;
  void PrintConfigurePresetList(
    std::function<bool(ConfigurePreset const&)> const& filter) const;
  void PrintBuildPresetList() const;
  void PrintTestPresetList() const;
  void PrintPackagePresetList() const;
  void PrintPackagePresetList(
    std::function<bool(PackagePreset const&)> const& filter) const;
  void PrintWorkflowPresetList() const;
  void PrintAllPresets() const;

private:
  enum class RootType
  {
    Any,
    Project,
    User,
  };

  enum class ReadReason
  {
    Root,
    Included,
  };

  bool ReadProjectPresetsInternal(std::string const& presetsFile,
                                  ReadOption readFilesOption);
  bool ReadJSONFile(std::string const& filename, RootType rootType,
                    ReadReason readReason, std::vector<File*>& inProgressFiles,
                    File*& file, std::string& errMsg);
  void ClearPresets();

  static std::string GetFilename(std::string const& sourceDir);
  static std::string GetUserFilename(std::string const& sourceDir);
};

extern template cmCMakePresetsGraph::PresetResolveResult<
  cmCMakePresetsGraph::ConfigurePreset>
cmCMakePresetsGraph::ResolvePreset(
  std::string const&,
  std::map<std::string,
           cmCMakePresetsGraph::PresetPair<
             cmCMakePresetsGraph::ConfigurePreset>> const&) const;

extern template cmCMakePresetsGraph::PresetResolveResult<
  cmCMakePresetsGraph::BuildPreset>
cmCMakePresetsGraph::ResolvePreset(
  std::string const&,
  std::map<
    std::string,
    cmCMakePresetsGraph::PresetPair<cmCMakePresetsGraph::BuildPreset>> const&)
  const;

extern template cmCMakePresetsGraph::PresetResolveResult<
  cmCMakePresetsGraph::TestPreset>
cmCMakePresetsGraph::ResolvePreset(
  std::string const&,
  std::map<
    std::string,
    cmCMakePresetsGraph::PresetPair<cmCMakePresetsGraph::TestPreset>> const&)
  const;

extern template cmCMakePresetsGraph::PresetResolveResult<
  cmCMakePresetsGraph::PackagePreset>
cmCMakePresetsGraph::ResolvePreset(
  std::string const&,
  std::map<std::string,
           cmCMakePresetsGraph::PresetPair<
             cmCMakePresetsGraph::PackagePreset>> const&) const;

extern template cm::optional<std::string>
cmCMakePresetsGraph::FormatPresetError<cmCMakePresetsGraph::ConfigurePreset>(
  cmCMakePresetsGraph::PresetResolveStatus, std::string const&,
  std::string const&);

extern template cm::optional<std::string>
cmCMakePresetsGraph::FormatPresetError<cmCMakePresetsGraph::BuildPreset>(
  cmCMakePresetsGraph::PresetResolveStatus, std::string const&,
  std::string const&);

extern template cm::optional<std::string>
cmCMakePresetsGraph::FormatPresetError<cmCMakePresetsGraph::TestPreset>(
  cmCMakePresetsGraph::PresetResolveStatus, std::string const&,
  std::string const&);

extern template cm::optional<std::string>
cmCMakePresetsGraph::FormatPresetError<cmCMakePresetsGraph::PackagePreset>(
  cmCMakePresetsGraph::PresetResolveStatus, std::string const&,
  std::string const&);
