#pragma once
///@file

#include "nix/util/hash.hh"
#include "nix/util/json-impls.hh"
#include "nix/store/path.hh"
#include "nix/store/derived-path.hh"

namespace nix {

/**
 * A reference is either to a to-be-registered output (by name),
 * or to an already-registered store object (by `Input`).
 *
 * `Ref<SingleDerivedPath` is a representation of something that can be
 * turned into a placeholder. (Regular own-output placeholder in the
 * first case, `DownstreamPlaceholder` in the second case.)
 */
template<typename Input>
using DrvRef = std::variant<OutputName, Input>;

/**
 * Downstream Placeholders are opaque and almost certainly unique values
 * used to allow derivations to refer to store objects which are yet to
 * be built and for we do not yet have store paths for.
 *
 * They correspond to `DerivedPaths` that are not `DerivedPath::Opaque`,
 * except for the cases involving input addressing or fixed outputs
 * where we do know a store path for the derivation output in advance.
 *
 * Unlike `DerivationPath`, however, `DownstreamPlaceholder` is
 * purposefully opaque and obfuscated. This is so they are hard to
 * create by accident, and so substituting them (once we know what the
 * path to store object is) is unlikely to capture other stuff it
 * shouldn't.
 *
 * We use them with `Derivation`: the `render()` method is called to
 * render an opaque string which can be used in the derivation, and the
 * resolving logic can substitute those strings for store paths when
 * resolving `Derivation.inputDrvs` to `BasicDerivation.inputSrcs`.
 */
class DownstreamPlaceholder
{
    /**
     * `DownstreamPlaceholder` is just a newtype of `Hash`.
     * This its only field.
     */
    Hash hash;

    /**
     * Newtype constructor
     */
    DownstreamPlaceholder(Hash hash)
        : hash(hash)
    {
    }

public:
    /**
     * This creates an opaque and almost certainly unique string
     * deterministically from the placeholder.
     */
    std::string render() const;

    /**
     * Create a placeholder for an unknown output of a content-addressed
     * derivation.
     *
     * The derivation itself is known (we have a store path for it), but
     * the output doesn't yet have a known store path.
     *
     * @param xpSettings Stop-gap to avoid globals during unit tests.
     */
    static DownstreamPlaceholder unknownCaOutput(
        const StorePath & drvPath,
        OutputNameView outputName,
        const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);

    /**
     * Create a placehold for the output of an unknown derivation.
     *
     * The derivation is not yet known because it is a dynamic
     * derivaiton --- it is itself an output of another derivation ---
     * and we just have (another) placeholder for it.
     *
     * @param xpSettings Stop-gap to avoid globals during unit tests.
     */
    static DownstreamPlaceholder unknownDerivation(
        const DownstreamPlaceholder & drvPlaceholder,
        OutputNameView outputName,
        const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);

    /**
     * Convenience constructor that handles both cases (unknown
     * content-addressed output and unknown derivation), delegating as
     * needed to `unknownCaOutput` and `unknownDerivation`.
     *
     * Recursively builds up a placeholder from a
     * `SingleDerivedPath::Built.drvPath` chain.
     */
    static DownstreamPlaceholder fromSingleDerivedPathBuilt(
        const SingleDerivedPath::Built & built,
        const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
};

} // namespace nix

namespace nlohmann {

template<typename Item>
struct adl_serializer<nix::DrvRef<Item>>
{
    static nix::DrvRef<Item> from_json(const json & json);
    static void to_json(json & json, const nix::DrvRef<Item> & t);
};

extern template struct adl_serializer<nix::DrvRef<nix::StorePath>>;
extern template struct adl_serializer<nix::DrvRef<nix::SingleDerivedPath>>;

} // namespace nlohmann
