local IsAddOnLoaded = C_AddOns.IsAddOnLoaded

local WA_IterateGroupMembers = function(reversed, forceParty)
    local unit = (not forceParty and IsInRaid()) and 'raid' or 'party'
    local numGroupMembers = unit == 'party' and GetNumSubgroupMembers() or
                                GetNumGroupMembers()
    local i = reversed and numGroupMembers or (unit == 'party' and 0 or 1)
    return function()
        local ret
        if i == 0 and unit == 'party' then
            ret = 'player'
        elseif i <= numGroupMembers and i > 0 then
            ret = unit .. i
        end
        i = i + (reversed and -1 or 1)
        return ret
    end
end

local function NormalizeName(s)
    if not s then return nil end
    -- убрать цвет/ссылки/текстуры/маски и любые остаточные пайпы
    s = s:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
    s = s:gsub("|H.-|h", ""):gsub("|h", "")
    s = s:gsub("|T.-|t", "")
    s = s:gsub("|K.-|k", "")
    s = s:gsub("%|", "") -- убрать любые оставшиеся |
    s = s:match("^%s*(.-)%s*$") or s -- trim
    s = s:gsub("%-.+$", "") -- убрать суффикс реалма
    return s
end

-- Build a set of player names from the current group/raid
local function BuildPlayerSet()
    local t = {}
    for unit in WA_IterateGroupMembers() do
        local unitName = UnitName(unit)
        if unitName then t[unitName] = true end
    end
    return t
end

local function ExtractIndex(line, mode)
    if mode == 'numeric' then
        local idx, rest = line:match("^%s*(%d+)%.%s*(.+)$")
        if idx then return tonumber(idx), rest, 'numeric' end

    elseif mode == 'token' then
        -- {token} or {token}.
        local token, rest = line:match("^%s*(%b{})%.?%s*(.+)$")
        if token then return token, rest, 'token' end
        -- Alphanumeric token like "2L.", "3S.", "T1.", "PHASE2."
        local alnum, rest2 = line:match("^%s*([%w_%-]+)%.%s*(.+)$")
        if alnum then return alnum, rest2, 'token' end

    elseif mode == 'auto' then
        local idx, rest = line:match("^%s*(%d+)%.%s*(.+)$")
        if idx then return tonumber(idx), rest, 'numeric' end
        local token, rest2 = line:match("^%s*(%b{})%.?%s*(.+)$")
        if token then return token, rest2, 'token' end
        -- Alphanumeric fallback
        local alnum, rest3 = line:match("^%s*([%w_%-]+)%.%s*(.+)$")
        if alnum then return alnum, rest3, 'token' end
    end
    return nil, nil, nil
end

local function CollectNames(chunk, playerSet)
    local names, seen = {}, {}
    local myName = NormalizeName(UnitName("player") or (select(1, UnitFullName("player"))) or "")

    for token in chunk:gmatch("[^,]+") do
        local name = NormalizeName(token)
        if name and name ~= "" then
            local matched
            if playerSet[name] then
                matched = name
            else
                local low = name:lower()
                for playerName in pairs(playerSet) do
                    local plow = playerName:lower()
                    if plow:find(low, 1, true) or low:find(plow, 1, true) then
                        matched = playerName
                        break
                    end
                end
                if not matched and Merfin and Merfin.isMyAlias and Merfin.isMyAlias(name) then
                    matched = myName ~= "" and myName or name
                end
            end

            if matched then
                local key = matched:lower()
                if not seen[key] then
                    names[#names + 1] = matched
                    seen[key] = true
                end
            end
        end
    end
    return names
end

local Parsers = {}

Parsers.plain = function(text, opts)
    if not text then return end
    local playerSet = BuildPlayerSet()
    local order, lineCount = {}, 0
    local indexMode = opts and opts.index_mode or 'numeric' -- 'numeric' | 'token' | 'auto'

    for raw in text:gmatch("[^\r\n]+") do
        local idx, rest, kind = ExtractIndex(raw, indexMode)
        if idx and rest then
            local names = CollectNames(rest, playerSet)
            if #names > 0 then
                if kind == 'numeric' then
                    if type(idx) == "number" then
                        order[idx] = names
                    else
                        lineCount = lineCount + 1
                        order[lineCount] = names
                    end
                else
                    order[idx] = names
                end
                lineCount = lineCount + 1
            end
        end
    end

    return order, lineCount
end

Parsers.stacked = function(text, opts)
    if not text then return end
    local playerSet = BuildPlayerSet()
    local order, stacks = {}, {}
    local indexMode = opts and opts.index_mode or 'numeric'

    for raw in text:gmatch("[^\r\n]+") do
        local idx, rest, kind = ExtractIndex(raw, indexMode)
        if idx and rest then
            local sc, namesStr = rest:match("^%(%s*(%d+)%s*%)%s*(.+)$")
            if sc and namesStr then
                local names = CollectNames(namesStr, playerSet)
                if #names > 0 then
                    if kind == 'numeric' then
                        order[#order + 1] = names
                        stacks[#stacks + 1] = tonumber(sc)
                    else
                        order[idx] = names
                        stacks[idx] = tonumber(sc)
                    end
                end
            end
        end
    end

    return order, stacks
end

-- Add a new parser that supports:
-- T1. Name1, Name2
-- T2. Name3, Name4
-- ...
-- 1. (3) T1
-- 2. (2) T2
-- 3. (2) T3
Parsers.teams_stacked = function(text, opts)
    if not text then return end

    local playerSet = BuildPlayerSet()
    local order = {} -- order[teamIndex] = { names... }
    local stacks = {} -- stacks[i] = { stacks = <num>, team = <teamIndex> }

    -- Optional settings
    local teamPrefix = (opts and opts.team_prefix) or "T" -- allows changing "T" to another prefix if needed
    local allowLooseNames = true -- if true, matches nicknames from LiquidAPI too

    -- First pass: collect teams "T<number>. <names>"
    for raw in text:gmatch("[^\r\n]+") do
        -- Example: "T1. Name1, Name2"
        local teamStr, namesStr = raw:match(
                                      "^%s*" .. teamPrefix .. "(%d+)%.%s*(.+)$")
        if teamStr and namesStr then
            local teamIndex = tonumber(teamStr)
            if teamIndex then
                local names = CollectNames(namesStr, playerSet)
                if #names > 0 then order[teamIndex] = names end
            end
        end
    end

    -- Second pass: build stacks schedule "i. (X) T<team>"
    -- Examples:
    -- "1. (3) T1"
    -- "2. (2) T2"
    local scheduleIndex = 0
    for raw in text:gmatch("[^\r\n]+") do
        -- Capture: "1. (3) T1" => idx=1, sc=3, teamStr=1
        local idxStr, scStr, teamStr = raw:match(
                                           "^%s*(%d+)%.%s*%(%s*(%d+)%s*%)%s*" ..
                                               teamPrefix .. "(%d+)%s*$")
        if idxStr and scStr and teamStr then
            local i = tonumber(idxStr)
            local sc = tonumber(scStr)
            local teamIndex = tonumber(teamStr)

            if i and sc and teamIndex then
                -- Ensure sequence is continuous by array position, independent from explicit "i."
                scheduleIndex = scheduleIndex + 1

                -- Ensure the team exists in 'order' even if not defined above, to avoid nil lookups later
                if not order[teamIndex] then
                    order[teamIndex] = {}
                end

                stacks[scheduleIndex] = {stacks = sc, team = teamIndex}
            end
        end
    end

    return order, stacks
end

Merfin = Merfin or {}
Merfin._MRTParsers = {
    plain = Parsers.plain,
    stacked = Parsers.stacked,
    teams_stacked = Parsers.teams_stacked
}

Merfin.RegisterMRTParser = function(name, fn)
    if type(name) == "string" and type(fn) == "function" then
        Merfin._MRTParsers[name] = fn
    end
end

local function ParseAssignmentString(text, opts)
    local algo = (opts and opts.algorithm) or 'plain'
    local parser = Merfin._MRTParsers[algo]
    if parser then return parser(text, opts) end
end

Merfin.GetMRTOrder = function(keyWordStart, keyWordEnd, opts)
    local order

    if not (IsAddOnLoaded("ExRT") or IsAddOnLoaded("MRT")) or not _G.VExRT or
        not _G.VExRT.Note or not _G.VExRT.Note.Text1 then return end

    local isNoteActive = false
    local lines = {}

    for line in _G.VExRT.Note.Text1:gmatch("[^\r\n]+") do
        local trimmed = strtrim(line:lower())
        if trimmed == keyWordStart then
            isNoteActive = true
        elseif trimmed == keyWordEnd then
            break
        elseif isNoteActive then
            lines[#lines + 1] = line
        end
    end

    local textToParse = table.concat(lines, "\n")
    local a, b = ParseAssignmentString(textToParse, opts)

    return isNoteActive, a, b
end

-- Returns true if 'alia' is in Merfin.myAlias (case-insensitive; trims spaces; ignores realm suffix)
Merfin.isMyAlias = function(alia)
    if not Merfin or type(Merfin.myAlias) ~= "table" then return false end
    if type(alia) ~= "string" or alia == "" then return false end

    local function norm(s)
        s = tostring(s or "")
        s = s:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
        s = s:gsub("|H.-|h", ""):gsub("|h", "")
        s = s:gsub("|T.-|t", ""):gsub("|K.-|k", "")
        s = s:gsub("%|", "")
        s = s:match("^%s*(.-)%s*$") or s
        s = s:lower()
        s = s:gsub("%-.+$", "")
        return s
    end

    local key = norm(alia)
    for i = 1, #Merfin.myAlias do
        if norm(Merfin.myAlias[i]) == key then return true end
    end
    return false
end
