#pragma once

#include <map>

#include <absl/container/btree_map.h>

#include "libproto/worker.pb.h"
#include "libstore/store-api.hh"
#include "libutil/hash.hh"
#include "libutil/types.hh"

namespace nix {

/* Extension of derivations in the Nix store. */
const std::string drvExtension = ".drv";

/* Abstract syntax of derivations. */

struct DerivationOutput {
  Path path;
  // TODO(grfn): make these two fields a Hash
  std::string hashAlgo; /* hash used for expected hash computation */
  std::string hash;     /* expected hash, may be null */
  DerivationOutput() {}
  DerivationOutput(Path path, std::string hashAlgo, std::string hash) {
    this->path = path;
    this->hashAlgo = hashAlgo;
    this->hash = hash;
  }

  explicit DerivationOutput(
      const nix::proto::Derivation_DerivationOutput& proto_derivation_output)
      : path(proto_derivation_output.path().path()),
        hashAlgo(proto_derivation_output.hash_algo()),
        hash(proto_derivation_output.hash()) {}

  void parseHashInfo(bool& recursive, Hash& hash) const;

  [[nodiscard]] nix::proto::Derivation_DerivationOutput to_proto() const;
};

// TODO(tazjin): Determine whether this actually needs to be ordered.
using DerivationOutputs = absl::btree_map<std::string, DerivationOutput>;

/* For inputs that are sub-derivations, we specify exactly which
   output IDs we are interested in. */
using DerivationInputs = absl::btree_map<Path, StringSet>;

using StringPairs = absl::btree_map<std::string, std::string>;

struct BasicDerivation {
  DerivationOutputs outputs; /* keyed on symbolic IDs */
  PathSet inputSrcs;         /* inputs that are sources */
  std::string platform;
  Path builder;
  Strings args;
  StringPairs env;

  BasicDerivation() = default;

  // Convert the given proto derivation to a BasicDerivation in the given
  // nix::Store.
  static BasicDerivation from_proto(
      const nix::proto::Derivation* proto_derivation, const nix::Store& store);

  [[nodiscard]] nix::proto::Derivation to_proto() const;

  virtual ~BasicDerivation(){};

  /* Return the path corresponding to the output identifier `id' in
     the given derivation. */
  Path findOutput(const std::string& id) const;

  bool isBuiltin() const;

  /* Return true iff this is a fixed-output derivation. */
  bool isFixedOutput() const;

  /* Return the output paths of a derivation. */
  PathSet outputPaths() const;
};

struct Derivation : BasicDerivation {
  DerivationInputs inputDrvs; /* inputs that are sub-derivations */

  /* Print a derivation. */
  std::string unparse() const;
};

class Store;

/* Write a derivation to the Nix store, and return its path. */
Path writeDerivation(const ref<Store>& store, const Derivation& drv,
                     const std::string& name, RepairFlag repair = NoRepair);

/* Read a derivation from a file. */
Derivation readDerivation(const Path& drvPath);

Derivation parseDerivation(const std::string& s);

/* Check whether a file name ends with the extension for
   derivations. */
bool isDerivation(const std::string& fileName);

Hash hashDerivationModulo(Store& store, Derivation drv);

/* Memoisation of hashDerivationModulo(). */
typedef std::map<Path, Hash> DrvHashes;

extern DrvHashes drvHashes;  // FIXME: global, not thread-safe

/* Split a string specifying a derivation and a set of outputs
   (/nix/store/hash-foo!out1,out2,...) into the derivation path and
   the outputs. */
using DrvPathWithOutputs = std::pair<std::string, std::set<std::string> >;
DrvPathWithOutputs parseDrvPathWithOutputs(absl::string_view path);

Path makeDrvPathWithOutputs(const Path& drvPath,
                            const std::set<std::string>& outputs);

bool wantOutput(const std::string& output, const std::set<std::string>& wanted);

struct Source;
struct Sink;

Source& readDerivation(Source& in, Store& store, BasicDerivation& drv);
Sink& operator<<(Sink& out, const BasicDerivation& drv);

std::string hashPlaceholder(const std::string& outputName);

}  // namespace nix
