#pragma once
///@file

#include <variant>

#include "nix/util/hash.hh"
#include "nix/store/path.hh"
#include "nix/store/derived-path.hh"
#include <nlohmann/json_fwd.hpp>
#include "nix/util/comparator.hh"
#include "nix/util/signature/signer.hh"

namespace nix {

class Store;
struct OutputsSpec;

/**
 * A general `Realisation` key.
 *
 * This is similar to a `DerivedPath::Opaque`, but the derivation is
 * identified by its "hash modulo" instead of by its store path.
 */
struct DrvOutput
{
    /**
     * The hash modulo of the derivation.
     *
     * Computed from the derivation itself for most types of
     * derivations, but computed from the (fixed) content address of the
     * output for fixed-output derivations.
     */
    Hash drvHash;

    /**
     * The name of the output.
     */
    OutputName outputName;

    std::string to_string() const;

    std::string strHash() const
    {
        return drvHash.to_string(HashFormat::Base16, true);
    }

    static DrvOutput parse(const std::string &);

    GENERATE_CMP(DrvOutput, me->drvHash, me->outputName);
};

struct Realisation
{
    DrvOutput id;
    StorePath outPath;

    StringSet signatures;

    /**
     * The realisations that are required for the current one to be valid.
     *
     * When importing this realisation, the store will first check that all its
     * dependencies exist, and map to the correct output path
     */
    std::map<DrvOutput, StorePath> dependentRealisations;

    nlohmann::json toJSON() const;
    static Realisation fromJSON(const nlohmann::json & json, const std::string & whence);

    std::string fingerprint() const;
    void sign(const Signer &);
    bool checkSignature(const PublicKeys & publicKeys, const std::string & sig) const;
    size_t checkSignatures(const PublicKeys & publicKeys) const;

    static std::set<Realisation> closure(Store &, const std::set<Realisation> &);
    static void closure(Store &, const std::set<Realisation> &, std::set<Realisation> & res);

    bool isCompatibleWith(const Realisation & other) const;

    StorePath getPath() const
    {
        return outPath;
    }

    GENERATE_CMP(Realisation, me->id, me->outPath);
};

/**
 * Collection type for a single derivation's outputs' `Realisation`s.
 *
 * Since these are the outputs of a single derivation, we know the
 * output names are unique so we can use them as the map key.
 */
typedef std::map<OutputName, Realisation> SingleDrvOutputs;

/**
 * Collection type for multiple derivations' outputs' `Realisation`s.
 *
 * `DrvOutput` is used because in general the derivations are not all
 * the same, so we need to identify firstly which derivation, and
 * secondly which output of that derivation.
 */
typedef std::map<DrvOutput, Realisation> DrvOutputs;

/**
 * Filter a SingleDrvOutputs to include only specific output names
 *
 * Moves the `outputs` input.
 */
SingleDrvOutputs filterDrvOutputs(const OutputsSpec &, SingleDrvOutputs &&);

struct OpaquePath
{
    StorePath path;

    StorePath getPath() const
    {
        return path;
    }

    GENERATE_CMP(OpaquePath, me->path);
};

/**
 * A store path with all the history of how it went into the store
 */
struct RealisedPath
{
    /*
     * A path is either the result of the realisation of a derivation or
     * an opaque blob that has been directly added to the store
     */
    using Raw = std::variant<Realisation, OpaquePath>;
    Raw raw;

    using Set = std::set<RealisedPath>;

    RealisedPath(StorePath path)
        : raw(OpaquePath{path})
    {
    }

    RealisedPath(Realisation r)
        : raw(r)
    {
    }

    /**
     * Get the raw store path associated to this
     */
    StorePath path() const;

    void closure(Store & store, Set & ret) const;
    static void closure(Store & store, const Set & startPaths, Set & ret);
    Set closure(Store & store) const;

    GENERATE_CMP(RealisedPath, me->raw);
};

class MissingRealisation : public Error
{
public:
    MissingRealisation(DrvOutput & outputId)
        : MissingRealisation(outputId.outputName, outputId.strHash())
    {
    }

    MissingRealisation(std::string_view drv, OutputName outputName)
        : Error(
              "cannot operate on output '%s' of the "
              "unbuilt derivation '%s'",
              outputName,
              drv)
    {
    }
};

} // namespace nix
