--[[
    ©️ 2024-2025 upio

    Console Utils, a utility made to help with logging dynamic messages in roblox console.
    https://www.upio.dev/
    https://www.mspaint.cc/

    Please do not redistribute or claim the code as your own.
    However you may use it anywhere without any credits (but credits are appreciated <3)
--]]

local global_env = (getgenv and getgenv()) or shared or _G or {}
if global_env["console_utils"] then return global_env.console_utils end

--// services \\--
local cloneref = (cloneref or clonereference or function(instance: any) return instance end)
local RunService = cloneref(game:GetService("RunService"))
local CoreGui = cloneref(game:GetService("CoreGui"))

--// module table \\--
local MAX_LINES = 2048 --// taken from corescripts roblox repo
local library = {
    custom_prints = {};
    render_stepped_conn = nil;
}

--// variables \\--
local ClientLog = nil;

if not global_env._console_message_counter then
    global_env._console_message_counter = 3000
end

--// functions \\--
function _find_first_child(root, paths)
    local instance = root

    for _, path in ipairs(paths) do
        instance = instance:FindFirstChild(path)
        if not instance then
            return false, nil
        end
    end

    return true, instance
end

function _internal_get_guid()
    global_env._console_message_counter = global_env._console_message_counter + 1
    return tostring(global_env._console_message_counter) .. tostring(tick())
end

function _internal_get_console()
    return _find_first_child(CoreGui, { "DevConsoleMaster", "DevConsoleWindow", "DevConsoleUI", "MainView", "ClientLog" })
end

--// module functions \\--
function library.custom_print(...)
    local custom_print = {
        message = "",
        image = "",
        color = Color3.fromRGB(255, 255, 255),
        timestamp = os.date("%H:%M:%S"),
        UMID = _internal_get_guid()
    }

    --// fetch data \\--
    if typeof(select(1, ...)) == "table" then
        local data = select(1, ...)

        if typeof(data.message) == "string" then
            custom_print.message = data.message
        end

        if typeof(data.image) == "string" then
            custom_print.image = data.image
        end

        if typeof(data.color) == "Color3" then
            custom_print.color = data.color
        end
    else
        local msg = select(1, ...)
        local img = select(2, ...)
        local clr = select(3, ...)

        if typeof(msg) == "string" then
            custom_print.message = msg
        end

        if typeof(img) == "string" then
            custom_print.image = img
        end

        if typeof(clr) == "Color3" then
            custom_print.color = clr
        end
    end

    --// for main loop \\--
    local logData = nil;
    local logName = nil;
    local logCooldown = false;

	--// insert into prints table, also cut old prints if over max lines \\--
	while #library.custom_prints > MAX_LINES do table.remove(library.custom_prints, 1); end
    table.insert(library.custom_prints, custom_print)

    custom_print.update = function(ClientLogChildren)
        if not ClientLog then return end
        
        --// Get the instance \\--
        if not logName then
            if logCooldown then return end
            logCooldown = true;

            for _, logFrame in ClientLogChildren do
				local msgInst = logFrame:FindFirstChild("msg")
                if not (msgInst and string.match(msgInst.Text, tostring(custom_print.UMID) .. "$")) then continue end

                logName = logFrame.Name;
                break;
            end

            task.delay(math.random() / 5, function() logCooldown = false; end)
            return;
        end

        if not (logData and logData.frame and logData.frame.Parent) then
            logData = nil;

            local logFrame = ClientLog:FindFirstChild(logName);
            if not logFrame then return end

            local msgInst, imgInst = logFrame:FindFirstChild("msg"), logFrame:FindFirstChild("image");
            if not (msgInst and imgInst) then return end

            if not string.match(msgInst.Text, tostring(custom_print.UMID) .. "$") then return end

            logData = {
                frame = logFrame;
                msg = msgInst;
                img = imgInst;
            };
            return
        end

        --// Update the message \\--
        if logData.msg then
            logData.msg.Text = custom_print.timestamp .. " -- " .. custom_print.message
            logData.msg.TextColor3 = custom_print.color
            logData.msg.TextWrapped = true
        end

        --// Update the image \\--
        if logData.img then
            logData.img.Image = custom_print.image
            logData.img.ImageColor3 = custom_print.color
        end
    end

    --// print functions \\--
    local log_module = {}

    log_module.update_message = function(...)
        local update_timestamp = true

        if typeof(select(1, ...)) == "table" then
            local data = select(1, ...)

            if typeof(data.message) == "string" then
                custom_print.message = data.message
            end

            if typeof(data.image) == "string" then
                custom_print.image = data.image
            end

            if typeof(data.color) == "Color3" then
                custom_print.color = data.color
            end

            if typeof(data.update_timestamp) == "boolean" then
                update_timestamp = data.update_timestamp
            end
        else
            local msg = select(1, ...)
            local img = select(2, ...)
            local clr = select(3, ...)
            local update = select(4, ...)

            if typeof(msg) == "string" then
                custom_print.message = msg
            end

            if typeof(img) == "string" then
                custom_print.image = img
            end

            if typeof(clr) == "Color3" then
                custom_print.color = clr
            end

            if typeof(update) == "boolean" then
                update_timestamp = update
            end
        end

        if update_timestamp then
            custom_print.timestamp = os.date("%H:%M:%S")
        end
    end

    log_module.cleanup = function()
        for i, print_data in pairs(library.custom_prints) do
            if print_data.UMID == custom_print.UMID then
                table.remove(library.custom_prints, i)
                break
            end
        end

        custom_print.update = function() end
    end

    -- unique message id
    print(custom_print.UMID)
    return log_module
end

function library.custom_console_progressbar(params)
    if typeof(params) == "string" then
        params = {msg = params}
    end

    local msg = params["msg"] or params["message"]
    local clr = params["clr"] or params["color"]
    local img = params["img"] or params["image"]

    local progressbar_length = params["length"] or 10

    local progressbar_char = "█"
    local progressbar_empty = "░"

    local message = library.custom_print(msg, img, clr)
    local progress = 0

    --// print module \\--
    local progressbar_module = {}

    progressbar_module.update_message = function(_message, _image, _color)
        message.update_message({
            message = _message,
            image = _image,
            color = _color,
            update_timestamp = false
        })
    end

    progressbar_module.update_progress = function(_progress)
        progress = _progress
        local progressbar_string = ""

        local normalized_progress = math.floor(progress / progressbar_length * 100)

        for i = 1, 10 do
            if i <= progress / progressbar_length * 10 then
                progressbar_string = progressbar_string .. progressbar_char
            else
                progressbar_string = progressbar_string .. progressbar_empty
            end
        end

        message.update_message(msg .. " [" .. progressbar_string .. "] " .. normalized_progress .. "%", img, clr, false)
    end

    progressbar_module.update_message_with_progress = function(_message, _progress)
        _progress = _progress or progress

        msg = _message
        progressbar_module.update_progress(_progress)
    end

    progressbar_module.cleanup = message.cleanup

    return progressbar_module
end

--// update loop \\--
library.render_stepped_conn = RunService.RenderStepped:Connect(function()
    if #library.custom_prints == 0 then return end

    --// update client log \\--
    if not (ClientLog and ClientLog.Parent) then
        local exists, newClientLog = _internal_get_console()
        if not (exists and newClientLog) then return end

        ClientLog = newClientLog;
    end

    --// prints update \\--
	local ClientLogChildren = ClientLog:GetChildren()
    for _, print_data in next, library.custom_prints do
        print_data.update(ClientLogChildren)
    end
end)

--// return the module \\--
global_env.console_utils = library
return library