--[[
    Commander 3.0 - String Utilities

    Centralized string matching and manipulation functions.
    Used by intellisense providers and search commands.
]]

local ADDON_NAME, C3 = ...

local StringUtils = {}

--- Basic fuzzy match (character-by-character subsequence)
--- Returns true if all characters in needle appear in haystack in order
---@param needle string|nil Search string (empty/nil matches everything)
---@param haystack string|nil String to search in
---@return boolean matches Whether needle is a subsequence of haystack
function StringUtils.fuzzyMatch(needle, haystack)
    if not needle or needle == "" then return true end
    if not haystack then return false end
    needle = needle:lower()
    haystack = haystack:lower()
    local j = 1
    for i = 1, #needle do
        j = haystack:find(needle:sub(i, i), j, true)
        if not j then return false end
        j = j + 1
    end
    return true
end

--- Fuzzy match with score for ranking results
--- Higher scores indicate better matches
---@param needle string|nil Search string
---@param haystack string|nil String to search in
---@return boolean matches Whether it matches
---@return number score Match quality (0-100)
function StringUtils.fuzzyMatchWithScore(needle, haystack)
    if not needle or needle == "" then return true, 100 end
    if not haystack or haystack == "" then return false, 0 end

    local needleLower = needle:lower()
    local haystackLower = haystack:lower()

    -- Exact match (best)
    if needleLower == haystackLower then return true, 100 end

    -- Prefix match (very good)
    if haystackLower:find(needleLower, 1, true) == 1 then return true, 90 end

    -- Contains match (good)
    if haystackLower:find(needleLower, 1, true) then return true, 70 end

    -- Character-by-character fuzzy (acceptable)
    local j = 1
    for i = 1, #needleLower do
        j = haystackLower:find(needleLower:sub(i, i), j, true)
        if not j then return false, 0 end
        j = j + 1
    end
    return true, 50
end

--- Check if a string starts with a prefix (case-insensitive)
---@param str string String to check
---@param prefix string Prefix to look for
---@return boolean startsWith Whether str starts with prefix
function StringUtils.startsWith(str, prefix)
    if not str or not prefix then return false end
    return str:lower():find(prefix:lower(), 1, true) == 1
end

--- Check if a string contains a substring (case-insensitive)
---@param str string String to search in
---@param substr string Substring to find
---@return boolean contains Whether str contains substr
function StringUtils.contains(str, substr)
    if not str or not substr then return false end
    return str:lower():find(substr:lower(), 1, true) ~= nil
end

--- Trim whitespace from both ends of a string
---@param str string String to trim
---@return string trimmed String with leading/trailing whitespace removed
function StringUtils.trim(str)
    if not str then return "" end
    return str:match("^%s*(.-)%s*$") or ""
end

--- Split a string by delimiter
---@param str string String to split
---@param delimiter string Delimiter pattern
---@return table parts Array of string parts
function StringUtils.split(str, delimiter)
    if not str then return {} end
    delimiter = delimiter or "%s+"
    local parts = {}
    for part in str:gmatch("[^" .. delimiter .. "]+") do
        parts[#parts + 1] = part
    end
    return parts
end

--- Split query into whitespace-separated tokens
--- Used by multi-word fuzzy matching in intellisense and lookup
---@param query string The search query
---@return table tokens Array of token strings
function StringUtils.splitTokens(query)
    local tokens = {}
    if not query then return tokens end
    for token in query:gmatch("%S+") do
        tokens[#tokens + 1] = token
    end
    return tokens
end

--- Normalize string for matching: remove apostrophes and other punctuation
--- Allows "mimirons" to match "Mimiron's"
---@param str string Input string
---@return string Normalized string
function StringUtils.normalizeForMatch(str)
    if not str then return "" end
    -- Remove apostrophes, hyphens (keep spaces for word boundaries)
    return str:gsub("[''`%-]", "")
end

--- Check if all words in needle match the haystack
--- Each word can match via fuzzy subsequence OR substring
---@param needle string Multi-word query (e.g., "grand yak")
---@param haystack string Text to search (e.g., "Grand Expedition Yak")
---@return boolean
function StringUtils.hasMultiWordMatch(needle, haystack)
    if not needle or needle == "" then return true end
    if not haystack or haystack == "" then return false end

    -- Normalize both for matching (removes apostrophes, hyphens)
    local needleNorm = StringUtils.normalizeForMatch(needle:lower())
    local haystackNorm = StringUtils.normalizeForMatch(haystack:lower())

    local tokens = StringUtils.splitTokens(needleNorm)

    -- Single token: use standard subsequence matching
    if #tokens <= 1 then
        -- Try substring first on normalized
        if haystackNorm:find(needleNorm, 1, true) then
            return true
        end
        -- Fall back to subsequence on original
        return StringUtils.fuzzyMatch(needle, haystack)
    end

    -- Multi-token: each token must match somewhere
    for _, token in ipairs(tokens) do
        -- Try substring match first (more intuitive for words)
        if not haystackNorm:find(token, 1, true) then
            -- Fall back to subsequence match on original
            if not StringUtils.fuzzyMatch(token, haystack) then
                return false
            end
        end
    end

    return true
end

C3.StringUtils = StringUtils
return StringUtils
