/* -*- mode: c++ -*-
 * Copyright 2025 Dracal Technologies Inc. All rights reserved.
 */

#pragma once

#include <dracal/common/platform.hpp>

#include <algorithm>
#include <array>
#include <codecvt>
#include <cstdint>
#include <limits>
#include <locale>
#include <span>
#include <string>
#include <type_traits>
#include <vector>

namespace dracal::str {

/**
 * @brief Check if the string starts with the given prefix.
 * @param str The string to check.
 * @param prefix The prefix to look for.
 * @return True if starts with the prefix.
 */
bool starts_with(const std::string &str, const std::string &prefix) noexcept;

/**
 * @brief Check if the string contains with the given substring.
 * @param str The string to check.
 * @param substring The substring to look for.
 * @return True if contains with the substring.
 */
bool contains(const std::string &str, const std::string &substring) noexcept;

/**
 * @brief Check if the string contains at least one of the substrings.
 * @param str The string to check.
 * @param substrings The substrings to look for.
 * @return True if contains with the substring or if the substrings is empty.
 */
bool contains(const std::string &str, const std::vector<std::string> &substrings) noexcept;

/**
 * @brief Check if the string ends with the given postfix.
 * @param str The string to check.
 * @param postfix The postfix to look for.
 * @return True if ends with the postfix.
 */
bool ends_with(const std::string &str, const std::string &postfix) noexcept;

/**
 * @brief Convert the given string to lower case.
 * @param str The string to convert.
 * @return The lower case string.
 */
std::string to_lower(std::string str);

/**
 * @brief Convert the given string to lower case.
 * @param str The string to convert.
 * @param pos The position.
 * @return The lower case string.
 */
std::string to_lower(std::string str, const int pos);

/**
 * @brief Convert the given string to lower case at the given position.
 * @param str The string to convert.
 * @param pos The position.
 * @return The string with lower case character.
 */
std::string to_lower(std::string str, const unsigned int pos);

/**
 * @brief  Convert the given string to lower case at the given position.
 * @param str The string to convert.
 * @param pos The position.
 * @return The string with lower case character.
 */
std::string to_upper(std::string str);

/**
 * @brief Convert the given string to upper case at the given position.
 * @param str The string to convert.
 * @param pos The position.
 * @return The string with upper case character.
 */
std::string to_upper(std::string str, const int pos);

/**
 * @brief Convert the given string to upper case at the given position.
 * @param str The string to convert.
 * @param pos The position.
 * @return The string with upper case character.
 */
std::string to_upper(std::string str, const unsigned int pos);

/**
 * @brief Check if all characters in the string are alphabetic.
 * @param str The string to check.
 * @return True if all characters are alphabetic, false otherwise.
 */
bool is_alpha(const std::string &str);

/**
 * @brief Check if the character at the given position is alphabetic.
 * @param str The string to check.
 * @param pos The position.
 * @return True if the character at pos is alphabetic, false otherwise.
 */
bool is_alpha(const std::string &str, const int pos);

/**
 * @brief Check if the character at the given position is alphabetic.
 * @param str The string to check.
 * @param pos The position.
 * @return True if the character at pos is alphabetic, false otherwise.
 */
bool is_alpha(const std::string &str, const unsigned int pos);

/**
 * @brief Check if all characters in the string are numeric.
 * @param str The string to check.
 * @return True if all characters are numeric, false otherwise.
 */
bool is_numeric(const std::string &str);

/**
 * @brief Check if the character at the given position is numeric.
 * @param str The string to check.
 * @param pos The position.
 * @return True if the character at pos is numeric, false otherwise.
 */
bool is_numeric(const std::string &str, const int pos);

/**
 * @brief Check if the character at the given position is numeric.
 * @param str The string to check.
 * @param pos The position.
 * @return True if the character at pos is numeric, false otherwise.
 */
bool is_numeric(const std::string &str, const unsigned int pos);

/**
 * @brief Check if all characters in the string are alphanumeric.
 * @param str The string to check.
 * @return True if all characters are alphanumeric, false otherwise.
 */
bool is_alpha_numeric(const std::string &str);

/**
 * @brief Check if the character at the given position is alphanumeric.
 * @param str The string to check.
 * @param pos The position.
 * @return True if the character at pos is alphanumeric, false otherwise.
 */
bool is_alpha_numeric(const std::string &str, const int pos);

/**
 * @brief Check if the character at the given position is alphanumeric.
 * @param str The string to check.
 * @param pos The position.
 * @return True if the character at pos is alphanumeric, false otherwise.
 */
bool is_alpha_numeric(const std::string &str, const unsigned int pos);

/**
 * @brief Replace all the occurence of the FROM string to the TO string in the given string.
 * @param str The string to convert.
 * @param from The string to search for.
 * @param to The string to replace with.
 * @return The string with replacements.
 */
std::string replace(const std::string &str, const std::string &from, const std::string &to);

/**
 * @brief Trim left spaces in the given string. Works directly on the given string, no copy is made.
 * @param str The string to trim.
 */
void trim_left_inplace(std::string &str);

/**
 * @brief Trim right spaces in the given string. Works directly on the given string, no copy is made.
 * @param str The string to trim.
 */
void trim_right_inplace(std::string &str);

/**
 * @brief Trim both left and right spaces in the given string. Works directly on the given string, no copy is made.
 * @param str The string to trim.
 */
void trim_inplace(std::string &str);

/**
 * @brief Trim prefix in the given string. Works directly on the given string, no copy is made.
 * @param str The string to trim.
 * @param prefix The prefix to remove.
 */
void trim_prefix_inplace(std::string &str, const std::string &prefix);

/**
 * @brief Trim all before the prefix in the given string. Works directly on the given string, no copy is made.
 * @param str The string to trim.
 * @param prefix The prefix to remove.
 */
void trim_before_prefix_inplace(std::string &str, const std::string &prefix);

/**
 * @brief Trim postfix in the given string. Works directly on the given string, no copy is made.
 * @param str The string to trim.
 * @param postfix The postfix to remove.
 */
void trim_postfix_inplace(std::string &str, const std::string &postfix);

/**
 * @brief Removes any null terminators from the string. Works directly on the given string, no copy is made.
 *
 * Removes any null terminators ('\0') from the string `str`
 *
 * @param str The string from which to remove null terminators.
 */
void erase_null_terminators_inplace(std::string &str);

/**
 * @brief Trim left spaces in the given string. Creates a copy of the string, the orignial string is left untouched.
 * @param str The string to trim.
 * @return The trimmed string.
 */
std::string trim_left(const std::string &str);

/**
 * @brief Trim right spaces in the given string. Creates a copy of the string, the orignial string is left untouched.
 * @param str The string to trim.
 * @return The trimmed string.
 */
std::string trim_right(const std::string &str);

/**
 * @brief Trim both left and right spaces in the given string. Creates a copy of the string, the orignial string is left
 * untouched.
 * @param str The string to trim.
 * @return The trimmed string.
 */
std::string trim(const std::string &str);

/**
 * @brief Trim prefix in the given string. Creates a copy of the string, the orignial string is left untouched.
 * @param str The string to trim.
 * @param prefix The prefix to remove.
 * @return The trimmed string.
 */
std::string trim_prefix(const std::string &str, const std::string &prefix);

/**
 * @brief Trim all before the prefix in the given string. Creates a copy of the string, the orignial string is left
 * untouched.
 * @param str The string to trim.
 * @param prefix The prefix to remove.
 * @return The trimmed string.
 */
std::string trim_before_prefix(const std::string &str, const std::string &prefix);

/**
 * @brief Trim postfix in the given string. Creates a copy of the string, the original string is left untouched.
 * @param str The string to trim.
 * @param postfix The postfix to remove.
 * @return The trimmed string.
 */
std::string trim_postfix(const std::string &str, const std::string &postfix);

/**
 * @brief Trim the filename extension. Creates a copy of the string, the original string is left untouched.
 * @param filename The filename to trim.
 * @param keep_path Keep the path if present or not.
 * @return The trimmed string.
 */
std::string trim_extension(const std::string &filename, const bool keep_path = true);

/**
 * @brief Trim all characters after the first occurrence of the given delimiter.
 * @param str The string to trim.
 * @param delimiter The delimiter to search for.
 * @return The trimmed string or original string if delimiter not found.
 */
std::string trim_all_after_first(const std::string &str, const std::string &delimiter);

/**
 * @brief Trim all characters before the last occurrence of the given delimiter as well as the delimiter.
 * @param str The string to trim.
 * @param delimiter The delimiter to search for.
 * @return The trimmed string starting from last delimiter or original string if delimiter not found.
 */
std::string trim_all_before_last(const std::string &str, const std::string &delimiter);

/**
 * @brief Removes any null terminators from the string and returns the trimmed string. Creates a copy of the
 * string, the orignial string is left untouched.
 *
 * Removes any null terminators ('\0') from the string `str`.
 *
 * @param str The string from which to remove null terminators.
 * @return The trimmed string without null terminators.
 */
std::string erase_null_terminators(std::string str);

/**
 * @brief Prepends a prefix to the given string. Creates a copy of the string, the orignial string is left untouched.
 * @param str The string to be modified.
 * @param prefix The prefix to add at the start.
 * @param only_if_missing To avoid duplicated prefixes.
 * @return The modified string.
 */
std::string prepend_prefix(const std::string &str, const std::string &prefix, const bool only_if_missing = true);

/**
 * @brief Postpends a postfix to the given string. Creates a copy of the string, the orignial string is left untouched.
 * @param str The string to be modified.
 * @param postfix The postfix to add at the end.
 * @param only_if_missing To avoid duplicated postfixes.
 * @return The modified string.
 */
std::string append_postfix(const std::string &str, const std::string &postfix, const bool only_if_missing = true);

/**
 * @brief Splits the given string on the given delimeters.
 * @param str The string to split.
 * @param delims The delimeters to split on.
 * @param keep_empty_parts Keep the empty parts.
 * @return Vector containing the splited substrings.
 */
std::vector<std::string> split(const std::string &str, const std::string &delims = " ",
                               const bool keep_empty_parts = false);

/**
 * @brief Joins the given list with the provided delimeters.
 * @param list The string to join.
 * @param delims The delimeters to add in-between joined strings.
 * @return The joined string.
 */
std::string join(const std::vector<std::string> &list, const std::string &delims = " ",
                 const bool add_delims_at_end = false);

/**
 * @brief Joins the given list with the provided delimeters from start index.
 * @param list The string to join.
 * @param delims The delimeters to add in-between joined strings.
 * @param start The start index to start join.
 * @return The joined string.
 */
std::string join_from(const std::vector<std::string> &list, const std::string &delims = " ", unsigned int start = 0,
                      const bool add_delims_at_end = false);

#if defined(DRACAL_WINDOWS)
/**
 * @brief Converts std::wstring into std::string.
 * @param wstr The std::wstring to convert.
 * @return The converted std::string.
 */
std::string convert(const std::wstring &wstr);

/**
 * @brief Converts std::string into std::wstring.
 * @param str The std::string to convert.
 * @return The converted std::wstring.
 */
std::wstring convert(const std::string &str);
#endif

#if defined(DRACAL_HAS_SPAN)
std::string convect_utf16le_to_utf8(const std::span<const unsigned char> &data);
#endif

/**
 * @brief Extracts the executable name without the path and the extension if any.
 *
 * @param raw_executable_name The raw executable name with full path and extension. Typically argv[0].
 * @return The extracted executable name.
 */
std::string extract_executable_name(const char *raw_executable_name);

/**
 * @brief Generates a random UUID version 4 (RFC 4122).
 * @return A string containing a randomly generated UUID in the format "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".
 */
std::string uuid_v4();

/**
 * @brief Safely copies a std::string to a fixed-size char array with null termination.
 * @tparam N The size of the destination char array.
 * @param dest The destination char array.
 * @param str The source string to copy.
 * @return True if successful, false otherwise.
 */
template <std::size_t N> bool copy(char (&dest)[N], const std::string &str) {
    static_assert(N > 0, "Destination array size must be greater than 0");

    if (str.empty()) {
        dest[0] = '\0';
        return true;
    }

    const std::size_t max_length = N - 1;
    const std::size_t str_length = str.length();
    const std::size_t copy_length = str_length > max_length ? max_length : str_length;

    // cppcheck-suppress knownConditionTrueFalse
    if (copy_length != str_length) {
        return false;
    }

    std::copy_n(str.c_str(), copy_length, dest);
    dest[copy_length] = '\0';

    return true;
}

template <std::size_t N> bool validate_size([[maybe_unused]] char (&dest)[N], const std::string &str) {
    static_assert(N > 0, "Destination array size must be greater than 0");
    return str.length() < N;
}

/**
 * @brief Safely converts a string to an unsigned integer type. Returns the default value if the
 *        conversion fails or the value exceeds the type's range.
 * @tparam T An unsigned integer type (uint8_t, uint16_t, uint32_t, uint64_t).
 * @param[in] s The string to convert.
 * @param[in] default_val The value to return on failure.
 * @return The converted value, or default_val on failure.
 */
template <typename T> T to_uint(const std::string &s, T default_val = 0) noexcept {
    static_assert(std::is_unsigned_v<T> && std::is_integral_v<T>, "T must be an unsigned integer type");
    try {
        const unsigned long long val = std::stoull(s);
        if (val > std::numeric_limits<T>::max()) {
            return default_val;
        }
        return static_cast<T>(val);
    } catch (...) {
        return default_val;
    }
}

/**
 * @brief Safely converts a string to an unsigned integer type, reporting success.
 * @tparam T An unsigned integer type (uint8_t, uint16_t, uint32_t, uint64_t).
 * @param[in]  s   The string to convert.
 * @param[out] out The output value, set only on success.
 * @return True if the conversion succeeded and the value fits in T, false otherwise.
 */
template <typename T> bool try_to_uint(const std::string &s, T &out) noexcept {
    static_assert(std::is_unsigned_v<T> && std::is_integral_v<T>, "T must be an unsigned integer type");
    try {
        const unsigned long long val = std::stoull(s);
        if (val > std::numeric_limits<T>::max()) {
            return false;
        }
        out = static_cast<T>(val);
        return true;
    } catch (...) {
        return false;
    }
}

/**
 * @brief Safely converts a string to a signed integer type. Returns the default value if the
 *        conversion fails or the value is outside the type's range.
 * @tparam T A signed integer type (int8_t, int16_t, int32_t, int64_t).
 * @param[in] s           The string to convert.
 * @param[in] default_val The value to return on failure.
 * @return The converted value, or default_val on failure.
 */
template <typename T> T to_int(const std::string &s, T default_val = 0) noexcept {
    static_assert(std::is_signed_v<T> && std::is_integral_v<T>, "T must be a signed integer type");
    try {
        const long long val = std::stoll(s);
        if (val < std::numeric_limits<T>::min() || val > std::numeric_limits<T>::max()) {
            return default_val;
        }
        return static_cast<T>(val);
    } catch (...) {
        return default_val;
    }
}

/**
 * @brief Safely converts a string to a signed integer type, reporting success.
 * @tparam T A signed integer type (int8_t, int16_t, int32_t, int64_t).
 * @param[in]  s   The string to convert.
 * @param[out] out The output value, set only on success.
 * @return True if the conversion succeeded and the value fits in T, false otherwise.
 */
template <typename T> bool try_to_int(const std::string &s, T &out) noexcept {
    static_assert(std::is_signed_v<T> && std::is_integral_v<T>, "T must be a signed integer type");
    try {
        const long long val = std::stoll(s);
        if (val < std::numeric_limits<T>::min() || val > std::numeric_limits<T>::max()) {
            return false;
        }
        out = static_cast<T>(val);
        return true;
    } catch (...) {
        return false;
    }
}

/**
 * @brief Safely converts a string to double. Returns the default value if the conversion fails.
 * @param[in] s           The string to convert.
 * @param[in] default_val The value to return on failure.
 * @return The converted value, or default_val on failure.
 */
double to_double(const std::string &s, double default_val = 0.0) noexcept;

/**
 * @brief Safely converts a string to double, reporting success.
 * @param[in]  s   The string to convert.
 * @param[out] out The output value, set only on success.
 * @return True if conversion succeeded, false otherwise.
 */
bool try_to_double(const std::string &s, double &out) noexcept;

/**
 * @brief Safely converts a string to float. Returns the default value if the conversion fails.
 * @param[in] s           The string to convert.
 * @param[in] default_val The value to return on failure.
 * @return The converted value, or default_val on failure.
 */
float to_float(const std::string &s, float default_val = 0.0f) noexcept;

/**
 * @brief Safely converts a string to float, reporting success.
 * @param[in]  s   The string to convert.
 * @param[out] out The output value, set only on success.
 * @return True if conversion succeeded, false otherwise.
 */
bool try_to_float(const std::string &s, float &out) noexcept;

} // namespace dracal::str