/*****************************************************************************
 * Copyright (c) 2014-2026 OpenRCT2 developers
 *
 * For a complete list of all authors, please refer to contributors.md
 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
 *
 * OpenRCT2 is licensed under the GNU General Public License version 3.
 *****************************************************************************/

#pragma once

#include "StringTypes.h"

#include <charconv>
#include <cstdarg>
#include <cstddef>
#include <optional>
#include <stdexcept>
#include <string_view>
#include <vector>

namespace OpenRCT2::String
{
    struct Hash
    {
        using is_transparent = void;

        size_t operator()(const char* txt) const
        {
            return std::hash<std::string_view>{}(txt);
        }
        size_t operator()(std::string_view txt) const
        {
            return std::hash<std::string_view>{}(txt);
        }
        size_t operator()(const std::string& txt) const
        {
            return std::hash<std::string>{}(txt);
        }
    };

    std::string toStd(const utf8* str);
    std::string toUtf8(std::wstring_view src);
    std::wstring toWideChar(std::string_view src);

    /**
     * Creates a string_view from a char pointer with a length up to either the
     * first null terminator or a given maximum length, whatever is smallest.
     */
    std::string_view toStringView(const char* ch, size_t maxLen);

    bool isNullOrEmpty(const utf8* str);
    int32_t compare(const std::string& a, const std::string& b, bool ignoreCase = false);
    int32_t compare(const utf8* a, const utf8* b, bool ignoreCase = false);

    bool equals(u8string_view a, u8string_view b);
    bool equals(const utf8* a, const utf8* b, bool ignoreCase = false);
    bool iequals(u8string_view a, u8string_view b);
    bool iequals(const utf8* a, const utf8* b);

    bool startsWith(std::string_view str, std::string_view match, bool ignoreCase = false);
    bool endsWith(std::string_view str, std::string_view match, bool ignoreCase = false);
    bool contains(std::string_view haystack, std::string_view needle, bool ignoreCase = false);
    size_t indexOf(const utf8* str, utf8 match, size_t startIndex = 0);
    ptrdiff_t lastIndexOf(const utf8* str, utf8 match);

    /**
     * Gets the length of the given string in codepoints.
     */
    size_t lengthOf(const utf8* str);

    /**
     * Gets the size of the given string in bytes excluding the null terminator.
     */
    size_t sizeOf(const utf8* str);

    utf8* set(utf8* buffer, size_t bufferSize, const utf8* src);
    utf8* set(utf8* buffer, size_t bufferSize, const utf8* src, size_t srcSize);
    utf8* append(utf8* buffer, size_t bufferSize, const utf8* src);
    utf8* format(utf8* buffer, size_t bufferSize, const utf8* format, ...);
    u8string stdFormat(const utf8* format, ...);
    u8string formatVA(const utf8* format, va_list args);
    utf8* appendFormat(utf8* buffer, size_t bufferSize, const utf8* format, ...);

    /**
     * Splits the given string by a delimiter and returns the values as a new string array.
     * @returns the number of values.
     */
    std::vector<std::string_view> split(std::string_view s, std::string_view delimiter);

    utf8* skipBOM(utf8* buffer);
    const utf8* skipBOM(const utf8* buffer);

    size_t getCodepointLength(codepoint_t codepoint);
    codepoint_t getNextCodepoint(utf8* ptr, utf8** nextPtr = nullptr);
    codepoint_t getNextCodepoint(const utf8* ptr, const utf8** nextPtr = nullptr);
    utf8* writeCodepoint(utf8* dst, codepoint_t codepoint);
    void appendCodepoint(std::string& str, codepoint_t codepoint);

    bool isWhiteSpace(codepoint_t codepoint);
    utf8* trim(utf8* str);
    const utf8* trimStart(const utf8* str);
    [[nodiscard]] std::string trimStart(const std::string& s);
    [[nodiscard]] std::string trim(const std::string& s);

    /**
     * Converts a multi-byte string from one code page to UTF-8.
     */
    std::string convertToUtf8(std::string_view src, int32_t srcCodePage);

    /**
     * Returns an uppercased version of a UTF-8 string.
     */
    std::string toUpper(std::string_view src);

    template<typename T>
    inline std::optional<T> tryParse(std::string_view input)
    {
        static_assert(!std::is_same_v<T, float> && !std::is_same_v<T, double>, "Float support is currently unsupported");

        if (input.empty())
        {
            return std::nullopt;
        }
        T result;
        auto [ptr, ec] = std::from_chars(input.data(), input.data() + input.size(), result);
        if (ec == std::errc() && ptr == input.data() + input.size())
        {
            return result;
        }
        return std::nullopt;
    }

    template<typename T>
    inline T parse(std::string_view input)
    {
        static_assert(!std::is_same_v<T, float> && !std::is_same_v<T, double>, "Float support is currently unsupported");

        if (input.empty())
        {
            throw std::invalid_argument("Input is empty");
        }
        T result;
        auto [ptr, ec] = std::from_chars(input.data(), input.data() + input.size(), result);
        if (ec == std::errc::invalid_argument)
        {
            throw std::invalid_argument("Invalid argument in conversion");
        }
        if (ec == std::errc::result_out_of_range)
        {
            throw std::out_of_range("Result out of range");
        }
        if (ec != std::errc())
        {
            throw std::runtime_error("Conversion error");
        }
        if (ptr != input.data() + input.size())
        {
            throw std::invalid_argument("Trailing characters after number");
        }
        return result;
    }

    /**
     * Returns string representation of a hexadecimal input, such as SHA256 hash
     */
    template<typename T>
    std::string StringFromHex(T input)
    {
        std::string result;
        result.reserve(input.size() * 2);
        for (auto b : input)
        {
            static_assert(sizeof(b) == 1);

            char buf[3];
            snprintf(buf, 3, "%02x", static_cast<int32_t>(b));
            result.append(buf);
        }
        return result;
    }

    /**
     * Returns codepoint size or no value if not valid
     */
    constexpr std::optional<int> utf8GetCodePointSize(std::string_view v)
    {
        if (!v.empty() && !(v[0] & 0x80))
        {
            return { 1 };
        }
        if (v.size() >= 2 && ((v[0] & 0xE0) == 0xC0))
        {
            return { 2 };
        }
        if (v.size() >= 3 && ((v[0] & 0xF0) == 0xE0))
        {
            return { 3 };
        }
        if (v.size() >= 4 && ((v[0] & 0xF8) == 0xF0))
        {
            return { 4 };
        }
        return std::nullopt;
    }

    /**
     * Truncates a string to at most `size` bytes,
     * making sure not to cut in the middle of a sequence.
     */
    std::string_view utf8Truncate(std::string_view v, size_t size);

    /**
     * Truncates a string to at most `size` codepoints,
     * making sure not to cut in the middle of a sequence.
     */
    std::string_view utf8TruncateCodePoints(std::string_view v, size_t size);

    // Escapes special characters in a string to the percentage equivalent that can be used in URLs.
    std::string urlEncode(std::string_view value);

    int32_t logicalCmp(char const* a, char const* b);
    char* safeUtf8Copy(char* destination, const char* source, size_t num);
    char* safeConcat(char* destination, const char* source, size_t size);
} // namespace OpenRCT2::String
