CreateConVar("automine", "0", FCVAR_NONE, "Set automine to 1 to enable automining")

local menuOpen = false
local rockTypes = {
    Gold = true,
    Stone = true,
    Iron = true,
    Diamond = true,
    Ruby = true,
    Amethyst = true,
    Rare = true,
    Emerald = true,
    Bloodstone = true,
}
local currentTarget = nil
local lastAngles = nil
local lastPosition = nil
local stuckCounter = 0
local jumpCooldown = 0
local crouchCooldown = 0
local attackStart = 0
local keybind = KEY_DELETE
local pathLines = {}
local targetSwitchTime = 0
local switchTimer = 6

local function CreateAutomineMenu()
    if (menuOpen) then return end
    if (IsValid(frame)) then return end
    frame = vgui.Create("EliteUI.Frame")
    frame:SetTitle("Autominer")
    frame:SetSize(EliteUI.Scale(250), EliteUI.Scale(470))
    frame:Center()
    frame:MakePopup()

    local yPos = 40

    for rockType, _ in pairs(rockTypes) do
        local checkBox = vgui.Create("EliteUI.LabelledCheckbox", frame)
        checkBox:SetText(rockType)
        checkBox:SetPos(EliteUI.Scale(25), EliteUI.Scale(yPos))
        checkBox.Checkbox:SetToggle(rockTypes[rockType])
        checkBox:SizeToContents()
        checkBox.OnToggled = function(self, val)
            rockTypes[rockType] = val
            currentTarget = nil
        end
        yPos = yPos + 30
    end

    yPos = yPos + 5

    local craftButton = vgui.Create("EliteUI.TextButton", frame)
    craftButton:SetText("Show Crafter")
    craftButton:SetPos(EliteUI.Scale(25), EliteUI.Scale(yPos))
    craftButton:SetSize(EliteUI.Scale(200), EliteUI.Scale(30))
    craftButton.DoClick = function()
        CH_Mining.DashboardMenu()
        frame:Close()
    end

    yPos = yPos + 35

    local enableButton = vgui.Create("EliteUI.TextButton", frame)
    enableButton:SetText("Enable Automine")
    enableButton:SetPos(EliteUI.Scale(25), EliteUI.Scale(yPos))
    enableButton:SetSize(EliteUI.Scale(200), EliteUI.Scale(30))
    enableButton.DoClick = function()
        RunConsoleCommand("automine", "1")
        frame:Close()
    end

    yPos = yPos + 35

    local disableButton = vgui.Create("EliteUI.TextButton", frame)
    disableButton:SetText("Disable Automine")
    disableButton:SetPos(EliteUI.Scale(25), EliteUI.Scale(yPos))
    disableButton:SetSize(EliteUI.Scale(200), EliteUI.Scale(30))
    disableButton.DoClick = function()
        RunConsoleCommand("automine", "0")
        frame:Close()
    end

    yPos = yPos + 35

    local unloadButton = vgui.Create("EliteUI.TextButton", frame)
    unloadButton:SetText("Unload Automine")
    unloadButton:SetPos(EliteUI.Scale(25), EliteUI.Scale(yPos))
    unloadButton:SetSize(EliteUI.Scale(200), EliteUI.Scale(30))
    unloadButton.DoClick = function()
        hook.Remove("CreateMove", "automine")
        hook.Remove("Think", "OpenAutomineMenu")
        hook.Remove("PostDrawOpaqueRenderables", "DrawPathLines")
        RunConsoleCommand("automine", "0")
        frame:Remove()
        notification.AddLegacy("Automine has been Unloaded.", NOTIFY_GENERIC, 5)
    end
end

hook.Add("Think", "OpenAutomineMenu", function()
    if input.IsKeyDown(keybind) and not menuOpen then
        CreateAutomineMenu()
        menuOpen = true
    elseif not input.IsKeyDown(keybind) then
        menuOpen = false
    end
end)

local function GetNearestRock()
    local entities = ents.GetAll()
    local localPos = LocalPlayer():GetPos()
    local rocks = {}

    table.sort(entities, function(a, b) return localPos:Distance(a:GetPos()) < localPos:Distance(b:GetPos()) end)

    for _, ent in ipairs(entities) do
        if ent:GetClass() == "ch_mining_rock" and ent:GetColor().a ~= 125 and IsValid(ent) and ent:GetMineralsLeft() > 0 and rockTypes[ent:GetMineralType()] then
            if (ent:GetModelScale() < 1.4) then
                if not (ent:GetIsDead()) then
                    table.insert(rocks, ent) 
                end
            end
        end
    end

    return rocks[1]
end

local function SmoothAim(cmd, targetPos)
    local shootPos = LocalPlayer():GetShootPos()
    local targetAng = (targetPos - shootPos):Angle()
    targetAng:Normalize()

    if lastAngles then
        targetAng = LerpAngle(0.05, lastAngles, targetAng)
    end

    cmd:SetViewAngles(targetAng)
    lastAngles = targetAng
end

local function IsPathClear(startPos, endPos, filterEnt)
    local trace = util.TraceLine({
        start = startPos,
        endpos = endPos,
        filter = function(ent) return ent ~= LocalPlayer() and ent ~= filterEnt end,
        mask = MASK_NPCWORLDSTATIC,
    })
    return not trace.Hit
end

local function FindClearPath(startPos, targetPos)
    local directions = {
        LocalPlayer():GetForward(),
        LocalPlayer():GetRight(),
        LocalPlayer():GetRight() * -1,
        Vector(0, 0, 1),
        Vector(0, 0, -1)
    }

    local bestDirection = nil
    local bestDistance = math.huge

    for _, direction in ipairs(directions) do
        local testPos = startPos + direction * 100
        if IsPathClear(startPos, testPos) then
            local distance = testPos:Distance(targetPos)
            if distance < bestDistance then
                bestDistance = distance
                bestDirection = direction
            end
        end
    end

    return bestDirection
end

local function MoveTowardsTarget(cmd, targetPos)
    local localPos = LocalPlayer():GetPos()
    local directionToTarget = (targetPos - localPos):GetNormalized()

    if IsPathClear(localPos, targetPos) then
        cmd:SetForwardMove(directionToTarget:Dot(LocalPlayer():GetForward()) * 200)
        cmd:SetSideMove(directionToTarget:Dot(LocalPlayer():GetRight()) * 200)
    else
        local clearDirection = FindClearPath(localPos, targetPos)
        if clearDirection then
            local moveDirection = (targetPos - (localPos + clearDirection * 100)):GetNormalized()
            cmd:SetForwardMove(moveDirection:Dot(LocalPlayer():GetForward()) * 200)
            cmd:SetSideMove(moveDirection:Dot(LocalPlayer():GetRight()) * 200)
        else
            cmd:SetForwardMove(200)
            cmd:SetSideMove(0)
        end
    end

    table.insert(pathLines, {localPos, targetPos})
end

local function HandleCrouchAndJump(cmd)
    if crouchCooldown > 0 then
        crouchCooldown = crouchCooldown - 1
    end

    if jumpCooldown > 0 then
        jumpCooldown = jumpCooldown - 1
    end

    if crouchCooldown <= 0 then
        if stuckCounter > 80 then
            cmd:SetButtons(bit.bor(cmd:GetButtons(), IN_DUCK))
            crouchCooldown = 300
        else
            cmd:SetButtons(bit.band(cmd:GetButtons(), bit.bnot(IN_DUCK)))
        end
    end

    if jumpCooldown <= 0 then
        local localPos = LocalPlayer():GetPos()
        local forwardTrace = util.TraceLine({
            start = localPos,
            endpos = localPos + LocalPlayer():GetForward() * 50,
            filter = LocalPlayer(),
            mask = MASK_NPCWORLDSTATIC,
        })

        if forwardTrace.Hit and not forwardTrace.StartSolid then
            local upTrace = util.TraceLine({
                start = localPos,
                endpos = localPos + Vector(0, 0, 50),
                filter = LocalPlayer(),
                mask = MASK_NPCWORLDSTATIC,
            })

            if not upTrace.Hit then
                cmd:SetButtons(bit.bor(cmd:GetButtons(), IN_JUMP))
                jumpCooldown = 300
            end
        end
    end
end

local function MakeMovementLookNatural(cmd)
    local randVariation = math.random(-3, 3)
    cmd:SetForwardMove(cmd:GetForwardMove() + randVariation)
    cmd:SetSideMove(cmd:GetSideMove() + randVariation)
end

local function GetTargetSurfacePos(target)
    local targetPos = target:GetPos()
    local directions = {
        Vector(1, 0, 0),
        Vector(-1, 0, 0),
        Vector(0, 1, 0),
        Vector(0, -1, 0),
        Vector(0, 0, 1),
        Vector(0, 0, -1)
    }

    for _, direction in ipairs(directions) do
        local trace = util.TraceLine({
            start = targetPos,
            endpos = targetPos + direction * 20,
            filter = function(ent) return ent == target end,
            mask = MASK_NPCWORLDSTATIC,
        })

        if trace.Hit then
            return trace.HitPos
        end
    end

    return targetPos
end

hook.Add("CreateMove", "automine", function(cmd)
    if GetConVar("automine"):GetInt() == 1 then
        local currentTime = CurTime()

        if not IsValid(currentTarget) or currentTarget == nil or currentTarget:GetDurabilityLeft() <= 0 or currentTarget:GetMineralsLeft() <= 0 or not rockTypes[currentTarget:GetMineralType()] then
            currentTarget = GetNearestRock()
            stuckCounter = 0
            targetSwitchTime = currentTime
        end

        if currentTarget then
            local targetPos = GetTargetSurfacePos(currentTarget)
            SmoothAim(cmd, targetPos)

            local targetDistance = LocalPlayer():GetPos():Distance(targetPos)
            if targetDistance > 70 then
                MoveTowardsTarget(cmd, targetPos)
                MakeMovementLookNatural(cmd)
                local currentPosition = LocalPlayer():GetPos()

                if lastPosition and currentPosition:Distance(lastPosition) < 10 then
                    stuckCounter = stuckCounter + 1
                else
                    stuckCounter = 0
                end

                lastPosition = currentPosition
                HandleCrouchAndJump(cmd)

                if stuckCounter > 30 or (currentTime - targetSwitchTime) > switchTimer then
                    currentTarget = GetNearestRock()
                    stuckCounter = 0
                    targetSwitchTime = currentTime
                end
            else
                cmd:SetForwardMove(0)
                cmd:SetSideMove(0)
                attackStart = attackStart + 1
                if attackStart % 2 == 0 then
                    cmd:SetButtons(bit.bor(cmd:GetButtons(), IN_ATTACK))
                else
                    cmd:SetButtons(bit.band(cmd:GetButtons(), bit.bnot(IN_ATTACK)))
                end
            end
        end
    end
end)

hook.Add("PostDrawOpaqueRenderables", "DrawPathLines", function()
    if GetConVar("automine"):GetInt() == 1 then
        for _, line in ipairs(pathLines) do
            render.DrawLine(line[1], line[2], Color(0, 255, 0), false)
        end
        pathLines = {}
    end
end)

hook.Add("PostDrawOpaqueRenderables", "DrawOreHitboxes", function()
    if GetConVar("automine"):GetInt() == 1 then
        for _, ent in ipairs(ents.GetAll()) do
            if ent:GetClass() == "ch_mining_rock" and ent:GetColor().a ~= 125 and rockTypes[ent:GetMineralType()] then
                local mins, maxs = ent:OBBMins(), ent:OBBMaxs()
                local center = ent:GetPos()
                local scale = 1.5

                render.DrawLine(center + mins * scale, center + Vector(mins.x, mins.y, maxs.z) * scale, Color(255, 0, 0), false)
                render.DrawLine(center + mins * scale, center + Vector(mins.x, maxs.y, mins.z) * scale, Color(255, 0, 0), false)
                render.DrawLine(center + maxs * scale, center + Vector(mins.x, maxs.y, maxs.z) * scale, Color(255, 0, 0), false)
                render.DrawLine(center + maxs * scale, center + Vector(maxs.x, mins.y, maxs.z) * scale, Color(255, 0, 0), false)
            end
        end
    end
end)

notification.AddLegacy("Press " .. input.GetKeyName(keybind) .. " to open the automine menu.", NOTIFY_HINT, 5)
notification.AddLegacy("Automine has been loaded.", NOTIFY_HINT, 5)