#include <algorithm>

#include <hyprutils/string/VarList2.hpp>
#include <hyprutils/string/String.hpp>

using namespace Hyprutils::String;

CVarList2::CVarList2(std::string&& in, const size_t lastArgNo, const char delim, const bool removeEmpty, const bool allowEscape) :
    m_inStringCopy(std::move(in)), m_inString(m_inStringCopy) {
    construct(m_inString, lastArgNo, delim, removeEmpty, allowEscape);
}

CVarList2::CVarList2(std::string_view in, const size_t lastArgNo, const char delim, const bool removeEmpty, const bool allowEscape) : m_inString(in) {
    construct(m_inString, lastArgNo, delim, removeEmpty, allowEscape);
}

CVarList2::CVarList2(const char* in, const size_t lastArgNo, const char delim, const bool removeEmpty, const bool allowEscape) : m_inString(in) {
    construct(m_inString, lastArgNo, delim, removeEmpty, allowEscape);
}

void CVarList2::construct(std::string_view in, const size_t lastArgNo, const char delim, const bool removeEmpty, const bool allowEscape) {
    if (m_inString.empty())
        return;

    auto                isDelimiter = [&delim](const char& c) { return delim == 's' ? std::isspace(c) : delim == c; };

    size_t              argBegin = 0;
    std::vector<size_t> escapedIndices; // local to the current arg
    for (size_t i = 0; i < m_inString.size(); ++i) {
        const char& c = m_inString[i];

        if (!isDelimiter(c))
            continue;

        if (allowEscape) {
            // we allow escape, so this might be escaped. Check first
            if (i - argBegin != 0) {
                const char& previousC = m_inString[i - 1];
                if (i - argBegin == 1) {
                    if (previousC == '\\') {
                        escapedIndices.emplace_back(i - argBegin - 1);
                        continue; // escaped
                    }
                    // fall to breaking, not escaped
                } else {
                    const char& prevPreviousC = m_inString[i - 2];
                    if (previousC == '\\') {
                        // whether or not escaped, pop char
                        escapedIndices.emplace_back(i - argBegin - 1);

                        if (prevPreviousC != '\\') {
                            // escaped
                            continue;
                        }
                    }

                    // fall to breaking, not escaped, but mark the \\ to be popped
                }
                // fall to breaking, couldn't be escaped
            }
        }

        // here we found a delimiter and need to break up the string (not escaped)

        if (escapedIndices.empty()) {
            // we didn't escape anything, so we can use inString
            const auto ARG = trim(std::string_view{m_inString}.substr(argBegin, i - argBegin));

            if (!ARG.empty() || !removeEmpty)
                m_args.emplace_back(ARG);
        } else {
            // we escaped something, fixup the string, add to copies, then emplace
            std::string cpy = std::string{m_inString.substr(argBegin, i - argBegin)};
            for (size_t i = 0; i < escapedIndices.size(); ++i) {
                cpy = cpy.substr(0, escapedIndices[i] - i) + cpy.substr(escapedIndices[i] - i + 1);
            }
            m_copyStrings.emplace_back(std::move(cpy));
            m_args.emplace_back(trim(std::string_view{m_copyStrings.back()}));
        }

        // update next argBegin
        argBegin = i + 1;
        escapedIndices.clear();
    }

    // append anything left
    if (argBegin < m_inString.size()) {
        if (escapedIndices.empty()) {
            // we didn't escape anything, so we can use inString
            const auto ARG = trim(std::string_view{m_inString}.substr(argBegin, m_inString.size() - argBegin));

            if (!ARG.empty() || !removeEmpty)
                m_args.emplace_back(ARG);
        } else {
            // we escaped something, fixup the string, add to copies, then emplace
            std::string cpy = std::string{m_inString.substr(argBegin, m_inString.size() - argBegin)};
            for (size_t i = 0; i < escapedIndices.size(); ++i) {
                cpy = cpy.substr(0, escapedIndices[i] - i) + cpy.substr(escapedIndices[i] - i + 1);
            }
            m_copyStrings.emplace_back(std::move(cpy));
            m_args.emplace_back(trim(std::string_view{m_copyStrings.back()}));
        }
    }
}

std::string CVarList2::join(const std::string& joiner, size_t from, size_t to) const {
    if (to == 0 || to <= from)
        to = m_args.size();

    std::string roll;
    for (size_t i = from; i < to && i < m_args.size(); ++i) {
        roll += m_args[i];
        if (i + 1 < to && i + 1 < m_args.size())
            roll += joiner;
    }
    return roll;
}

void CVarList2::append(std::string&& arg) {
    m_copyStrings.emplace_back(std::move(arg));
    m_args.emplace_back(m_copyStrings.back());
}

bool CVarList2::contains(const std::string& el) {
    return std::ranges::any_of(m_args, [&el](const auto& e) { return e == el; });
}
