#include "fetch-settings.hh"
#include "eval-settings.hh"
#include "common-eval-args.hh"
#include "shared.hh"
#include "config-global.hh"
#include "filetransfer.hh"
#include "eval.hh"
#include "fetchers.hh"
#include "registry.hh"
#include "flake/flakeref.hh"
#include "flake/settings.hh"
#include "store-api.hh"
#include "command.hh"
#include "tarball.hh"
#include "fetch-to-store.hh"
#include "compatibility-settings.hh"
#include "eval-settings.hh"

namespace nix {

namespace fs { using namespace std::filesystem; }

fetchers::Settings fetchSettings;

static GlobalConfig::Register rFetchSettings(&fetchSettings);

EvalSettings evalSettings {
    settings.readOnlyMode,
    {
        {
            "flake",
            [](EvalState & state, std::string_view rest) {
                experimentalFeatureSettings.require(Xp::Flakes);
                // FIXME `parseFlakeRef` should take a `std::string_view`.
                auto flakeRef = parseFlakeRef(fetchSettings, std::string { rest }, {}, true, false);
                debug("fetching flake search path element '%s''", rest);
                auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
                return state.rootPath(state.store->toRealPath(storePath));
            },
        },
    },
};

static GlobalConfig::Register rEvalSettings(&evalSettings);


flake::Settings flakeSettings;

static GlobalConfig::Register rFlakeSettings(&flakeSettings);


CompatibilitySettings compatibilitySettings {};

static GlobalConfig::Register rCompatibilitySettings(&compatibilitySettings);


MixEvalArgs::MixEvalArgs()
{
    addFlag({
        .longName = "arg",
        .description = "Pass the value *expr* as the argument *name* to Nix functions.",
        .category = category,
        .labels = {"name", "expr"},
        .handler = {[&](std::string name, std::string expr) { autoArgs.insert_or_assign(name, AutoArg{AutoArgExpr{expr}}); }}
    });

    addFlag({
        .longName = "argstr",
        .description = "Pass the string *string* as the argument *name* to Nix functions.",
        .category = category,
        .labels = {"name", "string"},
        .handler = {[&](std::string name, std::string s) { autoArgs.insert_or_assign(name, AutoArg{AutoArgString{s}}); }},
    });

    addFlag({
        .longName = "arg-from-file",
        .description = "Pass the contents of file *path* as the argument *name* to Nix functions.",
        .category = category,
        .labels = {"name", "path"},
        .handler = {[&](std::string name, std::string path) { autoArgs.insert_or_assign(name, AutoArg{AutoArgFile{path}}); }},
        .completer = completePath
    });

    addFlag({
        .longName = "arg-from-stdin",
        .description = "Pass the contents of stdin as the argument *name* to Nix functions.",
        .category = category,
        .labels = {"name"},
        .handler = {[&](std::string name) { autoArgs.insert_or_assign(name, AutoArg{AutoArgStdin{}}); }},
    });

    addFlag({
        .longName = "include",
        .shortName = 'I',
        .description = R"(
  Add *path* to search path entries used to resolve [lookup paths](@docroot@/language/constructs/lookup-path.md)

  This option may be given multiple times.

  Paths added through `-I` take precedence over the [`nix-path` configuration setting](@docroot@/command-ref/conf-file.md#conf-nix-path) and the [`NIX_PATH` environment variable](@docroot@/command-ref/env-common.md#env-NIX_PATH).
  )",
        .category = category,
        .labels = {"path"},
        .handler = {[&](std::string s) {
            lookupPath.elements.emplace_back(LookupPath::Elem::parse(s));
        }}
    });

    addFlag({
        .longName = "impure",
        .description = "Allow access to mutable paths and repositories.",
        .category = category,
        .handler = {[&]() {
            evalSettings.pureEval = false;
        }},
    });

    addFlag({
        .longName = "override-flake",
        .description = "Override the flake registries, redirecting *original-ref* to *resolved-ref*.",
        .category = category,
        .labels = {"original-ref", "resolved-ref"},
        .handler = {[&](std::string _from, std::string _to) {
            auto from = parseFlakeRef(fetchSettings, _from, fs::current_path().string());
            auto to = parseFlakeRef(fetchSettings, _to, fs::current_path().string());
            fetchers::Attrs extraAttrs;
            if (to.subdir != "") extraAttrs["dir"] = to.subdir;
            fetchers::overrideRegistry(from.input, to.input, extraAttrs);
        }},
        .completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
            completeFlakeRef(completions, openStore(), prefix);
        }}
    });

    addFlag({
        .longName = "eval-store",
        .description =
          R"(
            The [URL of the Nix store](@docroot@/store/types/index.md#store-url-format)
            to use for evaluation, i.e. to store derivations (`.drv` files) and inputs referenced by them.
          )",
        .category = category,
        .labels = {"store-url"},
        .handler = {&evalStoreUrl},
    });
}

Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
{
    auto res = state.buildBindings(autoArgs.size());
    for (auto & [name, arg] : autoArgs) {
        auto v = state.allocValue();
        std::visit(overloaded {
            [&](const AutoArgExpr & arg) {
                state.mkThunk_(*v, state.parseExprFromString(arg.expr, compatibilitySettings.nixShellShebangArgumentsRelativeToScript ? state.rootPath(absPath(getCommandBaseDir())) : state.rootPath(".")));
            },
            [&](const AutoArgString & arg) {
                v->mkString(arg.s);
            },
            [&](const AutoArgFile & arg) {
                v->mkString(readFile(arg.path.string()));
            },
            [&](const AutoArgStdin & arg) {
                v->mkString(readFile(STDIN_FILENO));
            }
        }, arg);
        res.insert(state.symbols.create(name), v);
    }
    return res.finish();
}

SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * baseDir)
{
    if (EvalSettings::isPseudoUrl(s)) {
        auto accessor = fetchers::downloadTarball(
            state.store,
            state.fetchSettings,
            EvalSettings::resolvePseudoUrl(s));
        auto storePath = fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy);
        return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
    }

    else if (hasPrefix(s, "flake:")) {
        experimentalFeatureSettings.require(Xp::Flakes);
        auto flakeRef = parseFlakeRef(fetchSettings, std::string(s.substr(6)), {}, true, false);
        auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
        return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
    }

    else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
        Path p(s.substr(1, s.size() - 2));
        return state.findFile(p);
    }

    else
        return state.rootPath(baseDir ? absPath(s, *baseDir) : absPath(s));
}

}
