#include "lix/libcmd/command.hh"
#include "lix/libmain/shared.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libmain/common-args.hh"
#include "lix/libutil/async.hh"
#include "path-info.hh"

#include <algorithm>
#include <array>

namespace nix {

struct CmdPathInfo : StorePathsCommand, MixJSON
{
    bool showSize = false;
    bool showClosureSize = false;
    bool humanReadable = false;
    bool showSigs = false;

    CmdPathInfo()
    {
        addFlag({
            .longName = "size",
            .shortName = 's',
            .description = "Print the size of the NAR serialisation of each path.",
            .handler = {&showSize, true},
        });

        addFlag({
            .longName = "closure-size",
            .shortName = 'S',
            .description = "Print the sum of the sizes of the NAR serialisations of the closure of each path.",
            .handler = {&showClosureSize, true},
        });

        addFlag({
            .longName = "human-readable",
            .shortName = 'h',
            .description = "With `-s` and `-S`, print sizes in a human-friendly format such as `5.67G`.",
            .handler = {&humanReadable, true},
        });

        addFlag({
            .longName = "sigs",
            .description = "Show signatures.",
            .handler = {&showSigs, true},
        });
    }

    std::string description() override
    {
        return "query information about store paths";
    }

    std::string doc() override
    {
        return
          #include "path-info.md"
          ;
    }

    Category category() override { return catSecondary; }

    void printSize(uint64_t value)
    {
        if (!humanReadable) {
            std::cout << fmt("\t%11d", value);
            return;
        }

        static const std::array<char, 9> idents{{
            ' ', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'
        }};
        size_t power = 0;
        double res = value;
        while (res > 1024 && power < idents.size()) {
            ++power;
            res /= 1024;
        }
        std::cout << fmt("\t%6.1f%c", res, idents.at(power));
    }

    void run(ref<Store> store, Installables && installables) override
    {
        StorePathSet paths;

        if (all) {
            if (installables.size()) {
                throw UsageError("'--all' does not expect arguments");
            }

            paths = aio().blockOn(store->queryAllValidPaths());
        } else {
            auto state = getEvaluator()->begin(aio());

            if (operateOn == OperateOn::Output) {
                for (auto i : installables) {
                    for (auto b : i->toDerivedPaths(*state)) {
                        std::visit(
                            overloaded{
                                [&](const DerivedPath::Built & bfd) {
                                    for (auto & [_, output] :
                                         aio().blockOn(resolveDerivedPath(*store, bfd, &*getEvalStore())))
                                    {
                                        paths.insert(output);
                                    }
                                },
                                [&](const DerivedPath::Opaque & bo) { paths.insert(bo.path); },
                            },
                            b.path.raw()
                        );
                    }
                }
            } else {
                auto drvPaths = Installable::toDerivations(*state, store, installables, true);
                paths.insert(drvPaths.begin(), drvPaths.end());
            }

            if (recursive) {
                // XXX: This only computes the store path closure, ignoring
                // intermediate realisations
                StorePathSet closure;
                aio().blockOn(store->computeFSClosure(paths, closure));

                paths.insert(closure.begin(), closure.end());
            }
        }

        auto sorted = aio().blockOn(store->topoSortPaths(paths));
        std::reverse(sorted.begin(), sorted.end());

        run(store, std::move(sorted));
    }

    void run(ref<Store> store, StorePaths && storePaths) override
    {
        // Wipe the progress bar to prevent interference with the output.
        // It's not needed any more because expensive evaluation or builds are already done here.
        logger->pause();

        size_t pathLen = 0;
        for (auto & storePath : storePaths)
            pathLen = std::max(pathLen, store->printStorePath(storePath).size());

        if (json) {
            std::cout << aio()
                             .blockOn(store->pathInfoToJSON(
                                 // FIXME: preserve order?
                                 StorePathSet(storePaths.begin(), storePaths.end()),
                                 true,
                                 showClosureSize,
                                 HashFormat::SRI,
                                 AllowInvalid
                             ))
                             .dump();
        }

        else {

            for (auto & storePath : storePaths) {
                auto info = aio().blockOn(store->queryPathInfo(storePath));
                auto storePathS = store->printStorePath(info->path);

                std::cout << storePathS;

                if (showSize || showClosureSize || showSigs)
                    std::cout << std::string(std::max(0, (int) pathLen - (int) storePathS.size()), ' ');

                if (showSize)
                    printSize(info->narSize);

                if (showClosureSize)
                    printSize(aio().blockOn(store->getClosureSize(info->path)).first);

                if (showSigs) {
                    std::cout << '\t';
                    Strings ss;
                    if (info->ultimate) ss.push_back("ultimate");
                    if (info->ca) ss.push_back("ca:" + renderContentAddress(info->ca));
                    for (auto & sig : info->sigs) ss.push_back(sig);
                    std::cout << concatStringsSep(" ", ss);
                }

                std::cout << std::endl;
            }

        }
    }
};

void registerNixPathInfo()
{
    registerCommand<CmdPathInfo>("path-info");
}

}
