--:Minify:--
local C_PROMPT = 0xFFFF00
local C_CONT   = 0xDBDBDB
local C_OUT    = 0x00FFFF
local C_ERR    = 0xFF0000
local C_KEY    = 0x00FF00
local C_STR    = 0x00FF88
local C_NUM    = 0x24FFFF
local C_BOOL   = 0xFF6D00
local C_NIL    = 0x6D6D6D
local C_TABLE  = 0xDBDBDB

local function c(col) syscall.devctl(1, "sfgc", col) end
local function w(s)   syscall.write(1, tostring(s)) end

local MAX_DEPTH   = 6
local MAX_ENTRIES = 64

local function prettyVal(val, indent, seen)
    indent = indent or 0
    seen   = seen   or {}
    local t = type(val)

    if t == "nil" then
        c(C_NIL); w("nil")
    elseif t == "boolean" then
        c(C_BOOL); w(tostring(val))
    elseif t == "number" then
        c(C_NUM)
        if val ~= val then
            w("nan")
        elseif val == math.huge then
            w("inf")
        elseif val == -math.huge then
            w("-inf")
        elseif val == math.floor(val) and math.abs(val) < 1e15 then
            w(tostring(math.floor(val)))
        else
            w(tostring(val))
        end
    elseif t == "string" then
        c(C_STR)
        local s = string.format("%q", val)
        w(s)
    elseif t == "function" then
        c(C_TABLE); w(tostring(val))
    elseif t == "table" then
        if seen[val] then
            c(C_TABLE); w("<circular " .. tostring(val) .. ">")
            return
        end
        if indent >= MAX_DEPTH then
            c(C_TABLE); w("<table " .. tostring(val) .. ">")
            return
        end
        seen[val] = true

        local pad   = string.rep("  ", indent)
        local padIn = string.rep("  ", indent + 1)

        local arrKeys  = {}
        local hashKeys = {}
        local arrMax   = #val

        for k in pairs(val) do
            if type(k) == "number" and k >= 1 and k <= arrMax and k == math.floor(k) then
                arrKeys[#arrKeys+1] = k
            else
                hashKeys[#hashKeys+1] = k
            end
        end
        table.sort(arrKeys)

        table.sort(hashKeys, function(a, b)
            local ta, tb = type(a), type(b)
            if ta == tb then
                if ta == "string" then return a < b end
                if ta == "number" then return a < b end
                return tostring(a) < tostring(b)
            end
            return ta < tb
        end)

        local total = #arrKeys + #hashKeys
        if total == 0 then
            c(C_TABLE); w("{}")
            seen[val] = nil
            return
        end

        c(C_TABLE); w("{\n")

        local shown = 0
        local function printEntry(k, v, isLast)
            shown = shown + 1
            if shown > MAX_ENTRIES then return true end
            w(padIn)
            if type(k) == "number" and arrKeys[k] then
            else
                if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
                    c(C_KEY); w(k)
                else
                    c(C_TABLE); w("[")
                    prettyVal(k, indent+1, seen)
                    c(C_TABLE); w("]")
                end
                c(C_TABLE); w(" = ")
            end
            prettyVal(v, indent+1, seen)
            c(C_TABLE)
            if not isLast then w(",") end
            w("\n")
        end

        for i, k in ipairs(arrKeys) do
            w(padIn)
            prettyVal(val[k], indent+1, seen)
            c(C_TABLE)
            if i < total then w(",") end
            w("\n")
            shown = shown + 1
            if shown >= MAX_ENTRIES then
                c(C_NIL); w(padIn .. "-- ..." .. (total - shown) .. " more entries\n")
                break
            end
        end

        if shown < MAX_ENTRIES then
            for i, k in ipairs(hashKeys) do
                local isLast = (shown + 1 >= total)
                w(padIn)
                if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
                    c(C_KEY); w(k)
                else
                    c(C_TABLE); w("[")
                    prettyVal(k, indent+1, seen)
                    c(C_TABLE); w("]")
                end
                c(C_TABLE); w(" = ")
                prettyVal(val[k], indent+1, seen)
                c(C_TABLE)
                shown = shown + 1
                if shown < total then w(",") end
                w("\n")
                if shown >= MAX_ENTRIES then
                    local rem = total - shown
                    if rem > 0 then
                        c(C_NIL); w(padIn .. "-- ..." .. rem .. " more entries\n")
                    end
                    break
                end
            end
        end

        c(C_TABLE); w(pad .. "}")
        seen[val] = nil
    else
        c(C_TABLE); w(tostring(val))
    end
    c(1)
end

local function printResults(...)
    local n = select("#", ...)
    if n == 0 then return end
    for i = 1, n do
        if i > 1 then c(C_TABLE); w("\t") end
        prettyVal(select(i, ...), 0, {})
    end
    w("\n")
    c(1)
end

local luaEnv = setmetatable({}, {__index = _ENV})
luaEnv._G = luaEnv

luaEnv.print = function(...)
    local n = select("#", ...)
    for i = 1, n do
        if i > 1 then w("\t") end
        prettyVal(select(i, ...), 0, {})
    end
    w("\n")
    c(1)
end

luaEnv.pp = function(val)
    prettyVal(val, 0, {})
    w("\n")
    c(1)
end

luaEnv.exit = setmetatable({}, {
    __tostring = function() return "function: exit()" end,
    __call     = function() syscall.exit() end,
})

local function compile(code)
    local exprFn = load("return " .. code, "@lua", "t", luaEnv)
    if exprFn then return exprFn, true end
    local stmtFn, err = load(code, "@lua", "t", luaEnv)
    return stmtFn, false, err
end

local function isIncomplete(code)
    local _, err = load(code, "@lua", "t", luaEnv)
    return err and (err:find("<eof>") ~= nil or err:find("'end'") ~= nil
                 or err:find("'then'") ~= nil or err:find("'until'") ~= nil)
end

local function cleanErr(msg)
    return tostring(msg)
        :gsub("^%[string .-%]:", "")
        :gsub("^@lua:", "")
        :gsub("stack traceback:.*", "")
        :match("^%s*(.-)%s*$")
end

local function runCode(code)
    local fn, isExpr, err = compile(code)
    if not fn then
        c(C_ERR); w("[error] "); c(1); w(cleanErr(err) .. "\n")
        return
    end

    local results = table.pack(xpcall(fn, debug.traceback))
    local ok = table.remove(results, 1)
    results.n = results.n - 1

    if not ok then
        c(C_ERR); w("[error] "); c(1); w(cleanErr(results[1]) .. "\n")
    elseif isExpr and results.n > 0 then
        c(C_OUT); w("= ")
        printResults(table.unpack(results, 1, results.n))
    end
end

local function getUserInput(prompt, history)
    c(C_PROMPT); w(prompt); c(1)
    local pos = syscall.devctl(1, "gpos")
    local ox = tonumber(pos:sub(1, pos:find(";")-1))
    local oy = tonumber(pos:sub(pos:find(";")+1))

    local input    = ""
    local cursor   = 1
    local histIdx  = 0
    local blink    = false
    local dirty    = true

    local function redraw()
        syscall.devctl(1, "spos", ox, oy)
        w(input:sub(1, cursor-1))
        if blink then syscall.devctl(1,"sfgc",0x000000); syscall.devctl(1,"sbgc",1) end
        w(cursor > #input and " " or input:sub(cursor, cursor))
        syscall.devctl(1,"sfgc",0xFFFFFF); syscall.devctl(1,"sbgc",16)
        w(input:sub(cursor+1) .. " ")
        dirty = false
    end

    while true do
        local key = syscall.read(0)
        if key and key ~= "" then
            if     key == "[C" then
                if cursor > 1 then cursor = cursor - 1; dirty = true end
            elseif key == "[D" then
                if cursor <= #input then cursor = cursor + 1; dirty = true end
            elseif key == "[A" then
                if history and histIdx < #history then
                    histIdx = histIdx + 1
                    input = history[#history - histIdx + 1]
                    cursor = #input + 1; dirty = true
                end
            elseif key == "[B" then
                if histIdx > 1 then
                    histIdx = histIdx - 1
                    input = history[#history - histIdx + 1]
                    cursor = #input + 1; dirty = true
                elseif histIdx == 1 then
                    histIdx = 0; input = ""; cursor = 1; dirty = true
                end
            elseif key == "\b" then
                if cursor > 1 then
                    input = input:sub(1, cursor-2) .. input:sub(cursor)
                    cursor = cursor - 1; dirty = true
                end
            elseif key == "\n" then
                syscall.devctl(1,"sfgc",0xFFFFFF); syscall.devctl(1,"sbgc",16)
                syscall.devctl(1,"spos",ox,oy)
                w(input .. " \n")
                return input
            elseif #key == 1 and key:byte(1) >= 32 and key:byte(1) < 127 then
                input=string.sub(input,1,cursor-1)..key..string.sub(input,cursor)
                cursor=cursor+1;dirty=true
            end
        end
        local nb = (math.floor(syscall.getUptime() / 500) % 2) == 0
        if nb ~= blink then blink = nb; dirty = true end
        if dirty then redraw() end
    end
end

c(C_PROMPT); w("HyperionOS " .. _VERSION .. "\n")
c(C_NIL)
w("Interactive Lua REPL. exit() to quit.\n\n")
c(1)

local history = {}

while true do
    local code = getUserInput("lua> ", history)
    if code ~= "" then

        while isIncomplete(code) do
            code = code .. "\n" .. getUserInput("...  ", nil)
        end

        if code ~= history[#history] then
            history[#history+1] = code
        end

        runCode(code)
    end
end
