#ifdef _WIN32
    #include <windows.h>
#endif

#include <algorithm>
#include <ranges>
#include <fmt/format.h>
#include <regex>

#include <String/StringType.hpp>

#include "Helpers/SysError.hpp"
#include "Helpers/String.hpp"

namespace RC
{
    SysError::SysError(const int error_code, const std::error_category &category)
        : m_error_category(&category)
    {
        assign(static_cast<unsigned long>(error_code));
    }

    SysError::SysError(const unsigned long error_code, const std::error_category& category)
        : m_error_category(&category)
    {
        assign(error_code);
    }

    auto SysError::assign(unsigned long error_code) -> void
    {
        auto error_str = fmt::format("[0x{:x}] {}", error_code, to_string(format_error(static_cast<int>(error_code))));
        m_error_text.assign(to_wstring(error_str));
    }

#ifdef _WIN32
    SysError::SysError(const HRESULT hresult): m_error_category(&std::system_category())
    {
        assign(hresult);
    }

    auto SysError::assign(HRESULT hresult) -> void
    {
        if (HRESULT_FACILITY(hresult) == FACILITY_WIN32)
        {
            assign(static_cast<unsigned long>(hresult));
        }
        else
        {
           // this error code will most likely be formatted to an unrelated error message, inform the caller
           auto error_str = fmt::format("Failed to format the error message: HRESULT 0x{:x} is not a WIN32 error code", hresult);
           m_error_text.assign(to_wstring(error_str));
        }
    }
#endif

    auto SysError::category() const -> StringType
    {
        return to_wstring(m_error_category->name());
    }

    auto SysError::format_error(const int error_code) const -> StringType
    {
        const std::error_category& error_category = *m_error_category;
        const std::error_code ec(error_code, error_category);
        // remove new line(s) and tabs
        auto result = std::regex_replace(to_wstring(std::system_error(ec).what()), std::wregex(STR("(\t|\r?\n)")), STR(" "));
        // right trim
        result.erase(std::ranges::find_if(std::ranges::reverse_view(result), [](const CharType c) -> bool {
            return !std::isspace<CharType>(c, std::locale::classic());
        }).base(), result.end());

        return result;
    }
}
