--[[
    INTERNAL JSON DECODER - NO GAME-SPECIFIC RULES LIVE HERE

    UE4SS Lua does not provide a JSON library. This small decoder implements
    exactly the standard JSON values needed by RSDWTools' items.json export.
    Catalogue contributors should normally edit scanner_sorting.lua instead.
]]

local Json = {}

function Json.Decode(input)
    local position = 1
    local parseValue

    local function fail(message)
        error(string.format("JSON parse error at byte %d: %s", position, message))
    end

    local function peek()
        return input:sub(position, position)
    end

    local function take()
        local character = input:sub(position, position)
        position = position + 1
        return character
    end

    local function skipWhitespace()
        while peek():match("[ \t\r\n]") do
            position = position + 1
        end
    end

    local function parseUnicodeEscape()
        local hexadecimal = input:sub(position, position + 3)

        if #hexadecimal ~= 4 or not hexadecimal:match("^[0-9a-fA-F]+$") then
            fail("invalid Unicode escape")
        end

        position = position + 4
        local code = tonumber(hexadecimal, 16)

        if code <= 0x7F then
            return string.char(code)
        elseif code <= 0x7FF then
            return string.char(
                0xC0 + math.floor(code / 0x40),
                0x80 + code % 0x40
            )
        end

        return string.char(
            0xE0 + math.floor(code / 0x1000),
            0x80 + math.floor(code / 0x40) % 0x40,
            0x80 + code % 0x40
        )
    end

    local function parseString()
        if take() ~= '"' then
            fail("expected string")
        end

        local output = {}

        while true do
            local character = take()

            if character == "" then
                fail("unterminated string")
            elseif character == '"' then
                return table.concat(output)
            elseif character == "\\" then
                local escaped = take()
                local escapes = {
                    ['"'] = '"',
                    ["\\"] = "\\",
                    ["/"] = "/",
                    b = "\b",
                    f = "\f",
                    n = "\n",
                    r = "\r",
                    t = "\t",
                }

                if escaped == "u" then
                    output[#output + 1] = parseUnicodeEscape()
                elseif escapes[escaped] then
                    output[#output + 1] = escapes[escaped]
                else
                    fail("invalid escape sequence")
                end
            else
                output[#output + 1] = character
            end
        end
    end

    local function parseNumber()
        local start = position

        if peek() == "-" then
            position = position + 1
        end

        while peek():match("%d") do
            position = position + 1
        end

        if peek() == "." then
            position = position + 1
            while peek():match("%d") do
                position = position + 1
            end
        end

        if peek() == "e" or peek() == "E" then
            position = position + 1
            if peek() == "+" or peek() == "-" then
                position = position + 1
            end
            while peek():match("%d") do
                position = position + 1
            end
        end

        local number = tonumber(input:sub(start, position - 1))
        if number == nil then
            fail("invalid number")
        end

        return number
    end

    local function parseArray()
        take() -- Opening [
        local array = {}
        skipWhitespace()

        if peek() == "]" then
            position = position + 1
            return array
        end

        while true do
            array[#array + 1] = parseValue()
            skipWhitespace()

            local separator = take()
            if separator == "]" then
                return array
            elseif separator ~= "," then
                fail("expected ',' or ']'")
            end

            skipWhitespace()
        end
    end

    local function parseObject()
        take() -- Opening {
        local object = {}
        skipWhitespace()

        if peek() == "}" then
            position = position + 1
            return object
        end

        while true do
            if peek() ~= '"' then
                fail("expected object key")
            end

            local key = parseString()
            skipWhitespace()

            if take() ~= ":" then
                fail("expected ':'")
            end

            skipWhitespace()
            object[key] = parseValue()
            skipWhitespace()

            local separator = take()
            if separator == "}" then
                return object
            elseif separator ~= "," then
                fail("expected ',' or '}'")
            end

            skipWhitespace()
        end
    end

    function parseValue()
        skipWhitespace()
        local character = peek()

        if character == "{" then
            return parseObject()
        elseif character == "[" then
            return parseArray()
        elseif character == '"' then
            return parseString()
        elseif character == "-" or character:match("%d") then
            return parseNumber()
        elseif input:sub(position, position + 3) == "true" then
            position = position + 4
            return true
        elseif input:sub(position, position + 4) == "false" then
            position = position + 5
            return false
        elseif input:sub(position, position + 3) == "null" then
            position = position + 4
            return nil
        end

        fail("unexpected token")
    end

    local decoded = parseValue()
    skipWhitespace()

    if position <= #input then
        fail("trailing data")
    end

    return decoded
end

return Json
