#include "ui/paths.h"

#include "platform/paths_platform.h"

#include <algorithm>
#include <cctype>
#include <string_view>

namespace nebula::ui {
namespace {

constexpr std::string_view kNebulaScheme = "nebula://";
constexpr std::string_view kInternalFallbackPage = "404.html";

struct InternalPage {
    std::string_view slug;
    std::string_view file_name;
};

constexpr InternalPage kInternalPages[] = {
    {"home", "home.html"},
    {"settings", "settings.html"},
    {"downloads", "downloads.html"},
    {"bigpicture", "bigpicture.html"},
    {"big-picture", "bigpicture.html"},
    {"gpu-diagnostics", "gpu-diagnostics.html"},
    {"setup", "setup.html"},
    {"404", "404.html"},
    {"insecure", "insecure.html"},
};

std::string GetUrlWithoutDecoration(std::string url) {
    const size_t split = url.find_first_of("?#");
    if (split != std::string::npos) {
        url.resize(split);
    }
    return url;
}

std::string GetUrlDecoration(const std::string& url) {
    const size_t split = url.find_first_of("?#");
    return split == std::string::npos ? std::string{} : url.substr(split);
}

std::string ToLowerAscii(std::string value) {
    std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) {
        return static_cast<char>(std::tolower(ch));
    });
    return value;
}

std::string PageFileUrl(std::string_view page_name) {
    const auto path = GetUiPagePath(std::string(page_name));
    return path.empty() ? std::string{} : FilePathToUrl(path);
}

std::string PercentEncode(const std::string& value) {
    constexpr char kHex[] = "0123456789ABCDEF";
    std::string encoded;
    encoded.reserve(value.size());
    for (unsigned char ch : value) {
        if (std::isalnum(ch) || ch == '-' || ch == '_' || ch == '.' || ch == '~') {
            encoded += static_cast<char>(ch);
        } else {
            encoded += '%';
            encoded += kHex[ch >> 4];
            encoded += kHex[ch & 0x0F];
        }
    }
    return encoded;
}

std::string InternalPageName(const std::string& url) {
    std::string target = ToLowerAscii(GetUrlWithoutDecoration(url));
    if (!target.starts_with(kNebulaScheme)) {
        return {};
    }

    target.erase(0, kNebulaScheme.size());
    while (!target.empty() && target.front() == '/') {
        target.erase(target.begin());
    }
    while (!target.empty() && target.back() == '/') {
        target.pop_back();
    }
    return target.empty() ? "home" : target;
}

std::string InternalUrlForSlug(std::string_view slug) {
    return std::string(kNebulaScheme) + std::string(slug);
}

const InternalPage* FindInternalPageBySlug(std::string_view slug) {
    for (const auto& page : kInternalPages) {
        if (page.slug == slug) {
            return &page;
        }
    }
    return nullptr;
}

const InternalPage* FindInternalPageByFileUrl(const std::string& url) {
    const std::string base_url = GetUrlWithoutDecoration(url);
    for (const auto& page : kInternalPages) {
        if (PageFileUrl(page.file_name) == base_url) {
            return &page;
        }
    }
    return nullptr;
}

std::string g_app_profile;

}  // namespace

void SetAppProfile(std::string_view profile) {
    g_app_profile = std::string(profile);
}

const std::string& GetAppProfile() {
    return g_app_profile;
}

std::filesystem::path GetExecutableDirectory() {
    return nebula::platform::ExecutableDirectory();
}

std::filesystem::path GetUserDataDirectory() {
    auto root = nebula::platform::DefaultUserDataRoot();
    if (root.empty()) {
        root = GetExecutableDirectory();
    }
    if (root.empty()) {
        return {};
    }

    std::filesystem::path user_data = root / "Nebula" / "User Data";
    if (!g_app_profile.empty() && g_app_profile != "desktop") {
        user_data = root / "Nebula" / ("User Data " + g_app_profile);
    }
    std::error_code ec;
    std::filesystem::create_directories(user_data, ec);
    return user_data;
}

std::filesystem::path GetCacheDirectory() {
    auto user_data = GetUserDataDirectory();
    if (user_data.empty()) {
        return {};
    }

    std::filesystem::path cache = user_data / "Cache";
    std::error_code ec;
    std::filesystem::create_directories(cache, ec);
    return cache;
}

std::filesystem::path GetExtensionsDirectory() {
    auto user_data = GetUserDataDirectory();
    if (user_data.empty()) {
        return {};
    }

    std::filesystem::path extensions = user_data / "extensions";
    std::error_code ec;
    std::filesystem::create_directories(extensions, ec);
    return extensions;
}

std::filesystem::path GetExtensionsStatePath() {
    auto user_data = GetUserDataDirectory();
    return user_data.empty() ? std::filesystem::path{} : user_data / "extensions_state.json";
}

std::filesystem::path GetSessionStatePath() {
    auto user_data = GetUserDataDirectory();
    return user_data.empty() ? std::filesystem::path{} : user_data / "session_state.json";
}

std::filesystem::path GetFirstRunStatePath() {
    auto user_data = GetUserDataDirectory();
    return user_data.empty() ? std::filesystem::path{} : user_data / "first_run_state.json";
}

std::filesystem::path GetUiPagePath(const std::string& page_name) {
    const auto exe_dir = GetExecutableDirectory();
    if (exe_dir.empty()) {
        return {};
    }

    return exe_dir / "ui" / "pages" / page_name;
}

std::string FilePathToUrl(std::filesystem::path path) {
    std::string value = nebula::platform::PathToUtf8(path);
    for (char& ch : value) {
        if (ch == '\\') {
            ch = '/';
        }
    }

    std::string encoded;
    encoded.reserve(value.size());
    for (char ch : value) {
        encoded += ch == ' ' ? "%20" : std::string(1, ch);
    }
    return encoded.starts_with('/') ? "file://" + encoded : "file:///" + encoded;
}

std::string GetChromeUrl() {
    const auto path = GetUiPagePath("chrome.html");
    const std::string fallback = PageFileUrl("home.html");
    return path.empty() ? (fallback.empty() ? "https://www.google.com" : fallback) : FilePathToUrl(path);
}

std::string GetHomeUrl() {
    return InternalUrlForSlug("home");
}

std::string GetSetupUrl() {
    return InternalUrlForSlug("setup");
}

std::string GetSettingsUrl() {
    return InternalUrlForSlug("settings");
}

std::string GetDownloadsUrl() {
    return InternalUrlForSlug("downloads");
}

std::string GetBigPictureUrl() {
    return InternalUrlForSlug("bigpicture");
}

std::string GetGpuDiagnosticsUrl() {
    return InternalUrlForSlug("gpu-diagnostics");
}

std::string GetMenuPopupUrl() {
    const auto path = GetUiPagePath("menu-popup.html");
    const std::string fallback = PageFileUrl("home.html");
    return path.empty() ? (fallback.empty() ? "https://www.google.com" : fallback) : FilePathToUrl(path);
}

std::string GetInsecureWarningUrl(const std::string& target_url) {
    return InternalUrlForSlug("insecure") + "?target=" + PercentEncode(target_url);
}

std::string GetNotFoundUrl(const std::string& target_url) {
    return InternalUrlForSlug("404") + "?url=" + PercentEncode(target_url);
}

std::string ResolveInternalUrl(const std::string& url) {
    const std::string page_name = InternalPageName(url);
    if (page_name.empty()) {
        return url;
    }

    if (const auto* page = FindInternalPageBySlug(page_name)) {
        const std::string file_url = PageFileUrl(page->file_name);
        return file_url.empty() ? url : file_url + GetUrlDecoration(url);
    }

    const std::string fallback_url = PageFileUrl(kInternalFallbackPage);
    return fallback_url.empty() ? url : fallback_url + "?url=" + PercentEncode(url);
}

std::string ToInternalUrl(const std::string& url) {
    const std::string page_name = InternalPageName(url);
    if (!page_name.empty()) {
        if (const auto* page = FindInternalPageBySlug(page_name)) {
            return InternalUrlForSlug(page->slug) + GetUrlDecoration(url);
        }
        return url;
    }

    if (const auto* page = FindInternalPageByFileUrl(url)) {
        return InternalUrlForSlug(page->slug) + GetUrlDecoration(url);
    }

    return url;
}

bool IsInternalHomeUrl(const std::string& url) {
    return GetUrlWithoutDecoration(ToInternalUrl(url)) == GetHomeUrl();
}

bool IsNebulaInternalUrl(const std::string& url) {
    return ToLowerAscii(url).starts_with(kNebulaScheme);
}

bool IsHttpUrl(const std::string& url) {
    return ToLowerAscii(url).starts_with("http://");
}

bool IsChromiumNewTabUrl(const std::string& url) {
    const std::string target = ToLowerAscii(GetUrlWithoutDecoration(url));
    return target == "about:blank" ||
           target == "chrome://newtab" ||
           target == "chrome://newtab/" ||
           target == "chrome://new-tab-page" ||
           target == "chrome://new-tab-page/" ||
           target == "chrome-search://local-ntp/local-ntp.html";
}

bool IsEmptyOrChromiumNewTabUrl(const std::string& url) {
    return url.empty() || IsChromiumNewTabUrl(url);
}

}  // namespace nebula::ui
