local m = require 'lpeglabel'

---@class catched
---@operator add: catched
local mt = {}

local function catchedTable()
    return setmetatable({}, mt)
end

function mt.__add(a, b)
    if not a or not b then
        return a or b
    end
    local t = catchedTable()
    for _, v in ipairs(a) do
        t[#t+1] = v
    end
    for _, v in ipairs(b) do
        t[#t+1] = v
    end
    return t
end

local function parseTokens(script, seps)
    local parser = m.P {
        m.Ct(m.V 'Token'^0),
        Token = m.Cp() * (m.V 'Mark' + m.V 'Nl' + m.V 'Text'),
        Mark  = m.Cc 'ML' * m.P '<' * m.C(m.S(seps))
              + m.Cc 'MR' * m.C(m.S(seps)) * m.P '>',
        Nl    = m.Cc 'NL' * m.C(m.P '\r\n' + m.S '\r\n'),
        Text  = m.Cc 'TX' * m.C((1 - m.V 'Nl' - m.V 'Mark')^1),
    }
    local results = parser:match(script)
    return results
end

---@param script string
---@param seps string
---@return string
---@return table<string, catched>
return function (script, seps)
    local tokens = parseTokens(script, seps)
    local newBuf = {}
    local result = {}
    local marks  = {}

    for s in seps:gmatch '.' do
        result[s] = catchedTable()
    end

    local lineOffset = 1
    local line       = 0
    local skipOffset = 0
    for i = 1, #tokens, 3 do
        local offset = tokens[i + 0]
        local mode   = tokens[i + 1]
        local text   = tokens[i + 2]
        if mode == 'TX' then
            newBuf[#newBuf+1] = text
        end
        if mode == 'NL' then
            newBuf[#newBuf+1] = text
            line = line + 1
            lineOffset = offset + #text - skipOffset
        end
        if mode == 'ML' then
            marks[#marks+1] = {
                char     = text,
                position = line * 10000 + offset - skipOffset - lineOffset,
            }
            skipOffset = skipOffset + 1 + #text
        end
        if mode == 'MR' then
            for j = #marks, 1, -1 do
                local mark = marks[j]
                if mark.char == text then
                    local position = line * 10000 + offset - skipOffset - lineOffset
                    result[text][#result[text]+1] = { mark.position, position }
                    table.remove(marks, j)
                    break
                end
            end
            skipOffset = skipOffset + 1 + #text
        end
    end

    return table.concat(newBuf), result
end
