#include "Hyprpaper.hpp"
#include "../helpers/Memory.hpp"

#include <optional>
#include <format>
#include <filesystem>
#include <print>

#include <hyprpaper_core-client.hpp>

#include <hyprutils/string/VarList2.hpp>
using namespace Hyprutils::String;

using namespace std::string_literals;

constexpr const char*          SOCKET_NAME = ".hyprpaper.sock";
static SP<CCHyprpaperCoreImpl> g_coreImpl;

constexpr const uint32_t       PROTOCOL_VERSION_SUPPORTED = 2;

//
static hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) {
    if (sv == "contain")
        return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_CONTAIN;
    if (sv == "fit" || sv == "stretch")
        return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_STRETCH;
    if (sv == "tile")
        return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE;
    return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER;
}

static std::expected<std::string, std::string> resolvePath(const std::string_view& sv) {
    std::error_code ec;
    auto            can = std::filesystem::canonical(sv, ec);

    if (ec)
        return std::unexpected(std::format("invalid path: {}", ec.message()));

    return can;
}

static std::expected<std::string, std::string> getFullPath(const std::string_view& sv) {
    if (sv.empty())
        return std::unexpected("empty path");

    if (sv[0] == '~') {
        static auto HOME = getenv("HOME");
        if (!HOME || HOME[0] == '\0')
            return std::unexpected("home path but no $HOME");

        return resolvePath(std::string{HOME} + "/"s + std::string{sv.substr(1)});
    }

    return resolvePath(sv);
}

static std::expected<void, std::string> doWallpaper(const std::string_view& RHS) {
    CVarList2         args(std::string{RHS}, 0, ',');

    const std::string MONITOR  = std::string{args[0]};
    const auto&       PATH_RAW = args[1];
    const auto&       FIT      = args[2];

    if (PATH_RAW.empty())
        return std::unexpected("not enough args");

    const auto RTDIR = getenv("XDG_RUNTIME_DIR");

    if (!RTDIR || RTDIR[0] == '\0')
        return std::unexpected("can't send: no XDG_RUNTIME_DIR");

    const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");

    if (!HIS || HIS[0] == '\0')
        return std::unexpected("can't send: no HYPRLAND_INSTANCE_SIGNATURE (not running under hyprland)");

    const auto PATH = getFullPath(PATH_RAW);

    if (!PATH)
        return std::unexpected(std::format("bad path: {}", PATH_RAW));

    auto socketPath = RTDIR + "/hypr/"s + HIS + "/"s + SOCKET_NAME;

    auto socket = Hyprwire::IClientSocket::open(socketPath);

    if (!socket)
        return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)");

    g_coreImpl = makeShared<CCHyprpaperCoreImpl>(PROTOCOL_VERSION_SUPPORTED);

    socket->addImplementation(g_coreImpl);

    if (!socket->waitForHandshake())
        return std::unexpected("can't send: wire handshake failed");

    auto spec = socket->getSpec(g_coreImpl->protocol()->specName());

    if (!spec)
        return std::unexpected("can't send: hyprpaper doesn't have the spec?!");

    auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer())));

    if (!manager)
        return std::unexpected("wire error: couldn't create manager");

    auto wallpaper = makeShared<CCHyprpaperWallpaperObject>(manager->sendGetWallpaperObject());

    if (!wallpaper)
        return std::unexpected("wire error: couldn't create wallpaper object");

    bool                       canExit = false;
    std::optional<std::string> err;

    wallpaper->setFailed([&canExit, &err](uint32_t code) {
        canExit = true;
        switch (code) {
            case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH: err = std::format("failed to set wallpaper: Invalid path", code); break;
            case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_MONITOR: err = std::format("failed to set wallpaper: Invalid monitor", code); break;
            default: err = std::format("failed to set wallpaper: unknown error, code {}", code); break;
        }
    });
    wallpaper->setSuccess([&canExit]() { canExit = true; });

    wallpaper->sendPath(PATH->c_str());
    wallpaper->sendMonitorName(MONITOR.c_str());
    if (!FIT.empty())
        wallpaper->sendFitMode(fitFromString(FIT));

    wallpaper->sendApply();

    while (!canExit) {
        socket->dispatchEvents(true);
    }

    if (err)
        return std::unexpected(*err);

    return {};
}

static std::expected<void, std::string> doListActive() {
    const auto RTDIR = getenv("XDG_RUNTIME_DIR");

    if (!RTDIR || RTDIR[0] == '\0')
        return std::unexpected("can't send: no XDG_RUNTIME_DIR");

    const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");

    if (!HIS || HIS[0] == '\0')
        return std::unexpected("can't send: no HYPRLAND_INSTANCE_SIGNATURE (not running under hyprland)");

    auto socketPath = RTDIR + "/hypr/"s + HIS + "/"s + SOCKET_NAME;

    auto socket = Hyprwire::IClientSocket::open(socketPath);

    if (!socket)
        return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)");

    g_coreImpl = makeShared<CCHyprpaperCoreImpl>(PROTOCOL_VERSION_SUPPORTED);

    socket->addImplementation(g_coreImpl);

    if (!socket->waitForHandshake())
        return std::unexpected("can't send: wire handshake failed");

    auto spec = socket->getSpec(g_coreImpl->protocol()->specName());

    if (!spec)
        return std::unexpected("can't send: hyprpaper doesn't have the spec?!");

    if (spec->specVer() < 2)
        return std::unexpected("can't send: hyprpaper protocol version too low (hyprpaper too old)");

    auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer())));

    if (!manager)
        return std::unexpected("wire error: couldn't create manager");

    auto status = makeShared<CCHyprpaperStatusObject>(manager->sendGetStatusObject());

    status->setActiveWallpaper([](const char* mon, const char* wp) { std::println("{}: {}", mon, wp); });

    socket->roundtrip();

    return {};
}

std::expected<void, std::string> Hyprpaper::makeHyprpaperRequest(const std::string_view& rq) {
    if (!rq.contains(' '))
        return std::unexpected("Invalid request");

    if (!rq.starts_with("/hyprpaper "))
        return std::unexpected("Invalid request");

    std::string_view LHS, RHS;
    auto             spacePos = rq.find(' ', 12);
    LHS                       = rq.substr(11, spacePos - 11);
    RHS                       = rq.substr(spacePos + 1);

    if (LHS == "wallpaper")
        return doWallpaper(RHS);
    else if (LHS == "listactive")
        return doListActive();
    else
        return std::unexpected("invalid hyprpaper request");

    return {};
}
