#include "helpers/Colors.hpp"
#include "helpers/StringUtils.hpp"
#include "core/PluginManager.hpp"
#include "core/DataState.hpp"
#include "helpers/Sys.hpp"

#include <vector>
#include <string>
#include <print>

#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprutils::Utils;

constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager
┃
┣ add <url> [git rev]           → Install a new plugin repository from git. Git revision
┃                                 is optional, when set, commit locks are ignored.
┣ remove <url|name|author/name> → Remove an installed plugin repository.
┣ enable <name|author/name>     → Enable a plugin.
┣ disable <name|author/name>    → Disable a plugin.
┣ update                        → Check and update all plugins if needed.
┣ reload                        → Reload hyprpm state. Ensure all enabled plugins are loaded.
┣ list                          → List all installed plugins.
┣ purge-cache                   → Remove the entire hyprpm cache, built plugins, hyprpm settings and headers.
┃
┣ Flags:
┃
┣ --no-nix       |              → Disable `nix develop` for build commands, even if Hyprland is nix.
┣ --notify       | -n           → Send a hyprland notification confirming successful plugin load.
┃                                 Warnings/Errors trigger notifications regardless of this flag.
┣ --help         | -h           → Show this menu.
┣ --verbose      | -v           → Enable too much logging.
┣ --force        | -f           → Force an operation ignoring checks (e.g. update -f).
┣ --no-shallow   | -s           → Disable shallow cloning of Hyprland sources.
┣ --hl-url       |              → Pass a custom hyprland source url.
┗
)#";

int                        main(int argc, char** argv, char** envp) {
    std::vector<std::string> ARGS{argc};
    for (int i = 0; i < argc; ++i) {
        ARGS[i] = std::string{argv[i]};
    }

    if (ARGS.size() < 2) {
        std::println(stderr, "{}", HELP);
        return 1;
    }

    std::vector<std::string> command;
    bool                     notify = false, verbose = false, force = false, noShallow = false, noNix = false;
    std::string              customHlUrl;

    for (int i = 1; i < argc; ++i) {
        if (ARGS[i].starts_with("-")) {
            if (ARGS[i] == "--help" || ARGS[i] == "-h") {
                std::println("{}", HELP);
                return 0;
            } else if (ARGS[i] == "--notify" || ARGS[i] == "-n") {
                notify = true;
            } else if (ARGS[i] == "--notify-fail" || ARGS[i] == "-nn") {
                // TODO: Deprecated since v.053.0. Remove in version>0.56.0
                std::println(stderr, "{}", failureString("Deprececated flag."));
                g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] -n flag is deprecated, see hyprpm --help.");
            } else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") {
                verbose = true;
            } else if (ARGS[i] == "--no-nix") {
                noNix = true;
            } else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") {
                noShallow = true;
            } else if (ARGS[i] == "--hl-url") {
                if (i + 1 >= argc) {
                    std::println(stderr, "Missing argument for --hl-url");
                    return 1;
                }
                customHlUrl = ARGS[i + 1];
                i++;
            } else if (ARGS[i] == "--force" || ARGS[i] == "-f") {
                force = true;
                std::println("{}", statusString("!", Colors::RED, "Using --force, I hope you know what you are doing."));
            } else {
                std::println(stderr, "Unrecognized option {}", ARGS[i]);
                return 1;
            }
        } else
            command.push_back(ARGS[i]);
    }

    if (command.empty()) {
        std::println(stderr, "{}", HELP);
        return 1;
    }

    g_pPluginManager                  = std::make_unique<CPluginManager>();
    g_pPluginManager->m_bVerbose      = verbose;
    g_pPluginManager->m_bNoShallow    = noShallow;
    g_pPluginManager->m_bNoNix        = noNix;
    g_pPluginManager->m_szCustomHlUrl = customHlUrl;
    g_pPluginManager->m_szArgv0       = argv[0];

    if (command[0] == "add") {
        if (command.size() < 2) {
            std::println(stderr, "{}", failureString("Not enough args for add."));
            return 1;
        }

        std::string rev = "";
        if (command.size() >= 3)
            rev = command[2];

        const auto RUNNINGHLVER   = g_pPluginManager->getHyprlandVersion();
        const auto INSTALLEDHLVER = g_pPluginManager->getHyprlandVersion(false);
        auto       GLOBALSTATE    = DataState::getGlobalState();

        if (GLOBALSTATE.headersAbiCompiled != RUNNINGHLVER.abiHash || GLOBALSTATE.headersAbiCompiled != INSTALLEDHLVER.abiHash) {
            std::println(stderr, "{}", failureString("Headers outdated, please run hyprpm update."));
            return 1;
        }

        NSys::root::cacheSudo();
        CScopeGuard x([] { NSys::root::dropSudo(); });

        g_pPluginManager->updateHeaders(false);

        return g_pPluginManager->addNewPluginRepo(command[1], rev) ? 0 : 1;
    } else if (command[0] == "remove") {
        if (command.size() < 2) {
            std::println(stderr, "{}", failureString("Not enough args for remove."));
            return 1;
        }

        NSys::root::cacheSudo();
        CScopeGuard x([] { NSys::root::dropSudo(); });

        return g_pPluginManager->removePluginRepo(SPluginRepoIdentifier::fromString(command[1])) ? 0 : 1;
    } else if (command[0] == "update") {
        NSys::root::cacheSudo();
        CScopeGuard x([] { NSys::root::dropSudo(); });

        const auto  HLVER            = g_pPluginManager->getHyprlandVersion(false);
        const auto  GLOBALSTATE      = DataState::getGlobalState();
        const bool  COMPILEDOUTDATED = HLVER.abiHash != GLOBALSTATE.headersAbiCompiled;
        const bool  headersValid     = g_pPluginManager->headersValid() == HEADERS_OK;
        const bool  headers          = g_pPluginManager->updateHeaders(force);

        if (headers) {
            const bool pluginsUpdated = g_pPluginManager->updatePlugins(!headersValid || force || COMPILEDOUTDATED);

            if (!pluginsUpdated && COMPILEDOUTDATED) {
                std::println(stderr, "{}",
                             failureString("Hyprland's ABI changed and some repositories failed to update: no plugins will be loaded until every repository updates "
                                           "successfully.\n  Fix or remove the failed repositories, then re-run hyprpm update."));
                g_pPluginManager->notify(ICON_ERROR, 0, 10000,
                                         "[hyprpm] Some plugin repos failed to update, no plugins were loaded. Fix or remove them, then re-run hyprpm update.");
                return 1;
            }

            const auto loadState = g_pPluginManager->ensurePluginsLoadState();

            if (loadState == LOADSTATE_HYPRLAND_UPDATED)
                g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] Updated plugins, but Hyprland was updated. Please restart Hyprland.");

            if (!pluginsUpdated || loadState != LOADSTATE_OK)
                return 1;
        } else {
            g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Couldn't update headers");
            return 1;
        }
    } else if (command[0] == "enable") {
        if (command.size() < 2) {
            std::println(stderr, "{}", failureString("Not enough args for enable."));
            return 1;
        }

        if (!g_pPluginManager->enablePlugin(SPluginRepoIdentifier::fromString(command[1]))) {
            std::println(stderr, "{}", failureString("Couldn't enable plugin (missing?)"));
            return 1;
        }

        NSys::root::cacheSudo();
        CScopeGuard x([] { NSys::root::dropSudo(); });

        auto        ret = g_pPluginManager->ensurePluginsLoadState();

        if (ret == LOADSTATE_HYPRLAND_UPDATED)
            g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] Enabled plugin, but Hyprland was updated. Please restart Hyprland.");

        if (ret != LOADSTATE_OK)
            return 1;
    } else if (command[0] == "disable") {
        if (command.size() < 2) {
            std::println(stderr, "{}", failureString("Not enough args for disable."));
            return 1;
        }

        if (!g_pPluginManager->disablePlugin(SPluginRepoIdentifier::fromString(command[1]))) {
            std::println(stderr, "{}", failureString("Couldn't disable plugin (missing?)"));
            return 1;
        }

        NSys::root::cacheSudo();
        CScopeGuard x([] { NSys::root::dropSudo(); });

        auto        ret = g_pPluginManager->ensurePluginsLoadState();

        if (ret != LOADSTATE_OK)
            return 1;
    } else if (command[0] == "reload") {
        auto ret = g_pPluginManager->ensurePluginsLoadState(force);

        if (ret != LOADSTATE_OK) {
            switch (ret) {
                case LOADSTATE_FAIL:
                case LOADSTATE_PARTIAL_FAIL: g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins"); break;
                case LOADSTATE_HEADERS_OUTDATED:
                    g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Failed to load plugins: Outdated headers. Please run hyprpm update manually.");
                    break;
                default: break;
            }

            return 1;
        } else if (notify) {
            g_pPluginManager->notify(ICON_OK, 0, 4000, "[hyprpm] Loaded plugins");
        }
    } else if (command[0] == "purge-cache") {
        NSys::root::cacheSudo();
        CScopeGuard x([] { NSys::root::dropSudo(); });
        DataState::purgeAllCache();
    } else if (command[0] == "list") {
        g_pPluginManager->listAllPlugins();
    } else {
        std::println(stderr, "{}", HELP);
        return 1;
    }

    return 0;
}
