/*
 * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
 *
 * Copyright (c) 2015 - 2025 CCBlueX
 *
 * LiquidBounce is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LiquidBounce is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
 *
 *
 */

package net.ccbluex.liquidbounce.features.module.modules.exploit

import net.ccbluex.liquidbounce.event.Sequence
import net.ccbluex.liquidbounce.event.events.*
import net.ccbluex.liquidbounce.event.handler
import net.ccbluex.liquidbounce.event.tickHandler
import net.ccbluex.liquidbounce.features.module.Category
import net.ccbluex.liquidbounce.features.module.ClientModule
import net.ccbluex.liquidbounce.render.*
import net.ccbluex.liquidbounce.render.engine.type.Color4b
import net.ccbluex.liquidbounce.utils.aiming.utils.raycast
import net.ccbluex.liquidbounce.utils.block.canStandOn
import net.ccbluex.liquidbounce.utils.block.getState
import net.ccbluex.liquidbounce.utils.client.PacketQueueManager
import net.ccbluex.liquidbounce.utils.client.chat
import net.ccbluex.liquidbounce.utils.client.markAsError
import net.ccbluex.liquidbounce.utils.client.notification
import net.ccbluex.liquidbounce.utils.entity.rotation
import net.ccbluex.liquidbounce.utils.math.toVec3d
import net.ccbluex.liquidbounce.utils.movement.DirectionalInput
import net.minecraft.block.BlockRenderType
import net.minecraft.block.FluidBlock
import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Direction
import net.minecraft.util.math.Vec3d
import net.minecraft.util.shape.VoxelShapes

/**
 * ClickTP
 *
 * Only works for Vulcan AntiCheat
 */
object ModuleClickTp : ClientModule("ClickTp", Category.EXPLOIT, aliases = arrayOf("ClickTeleport")) {

    private val range by float("Range", 60f, 10f..500f)
    private val cooldown by int("Cooldown", 20, 0..120)
    private val lagAfter by int("LagAfter", 40, 0..120)
    private val jumps by int("Jumps", 3, 1..10)

    private val startDelay by int("StartDelay", 5, 0..20)

    private var requiresLag = false
    private var requiresCollision = false
    private var requiresMovement = false

    private var highlightBlock: BlockPos? = null

    @Suppress("unused")
    private val tickHandler = tickHandler {
        val raycast = raycast(player.rotation, range = range.toDouble())
        val blockPos = raycast?.blockPos.apply {
            highlightBlock = this
        }

        if (!mc.options.pickItemKey.isPressed) {
            return@tickHandler
        }

        if (blockPos == null) {
            chat(markAsError(message("noSelection")))
            return@tickHandler
        }

        if (!isSuitable(blockPos)) {
            chat(markAsError(message("noSuitableBlock", blockPos.x, blockPos.y, blockPos.z)))
            return@tickHandler
        }

        val pos = blockPos.up().toCenterPos()

        notification("ClickTp", message("start", blockPos.x, blockPos.y, blockPos.z),
            NotificationEvent.Severity.SUCCESS)

        teleportVulcan286(pos)

        waitTicks(5)
        if (player.pos.distanceTo(blockPos.toVec3d()) < 5) {
            notification("ClickTp", message("success", blockPos.x, blockPos.y, blockPos.z),
                NotificationEvent.Severity.SUCCESS)
        } else {
            notification("ClickTp", message("failed", blockPos.x, blockPos.y, blockPos.z),
                NotificationEvent.Severity.ERROR)
        }

        waitTicks(cooldown)
    }

    /**
     * Teleport to a position by abusing Vulcan.
     */
    private suspend fun Sequence.teleportVulcan286(pos: Vec3d) {
        requiresLag = true

        // Set position and send packet
        player.setPosition(pos)
        waitTicks(startDelay)

        repeat(jumps) {
            requiresCollision = true
            player.jump()

            waitTicks(1)
            waitUntil { player.isOnGround }
            requiresCollision = false
        }

        requiresMovement = true
        waitTicks(lagAfter)
        requiresMovement = false

        requiresLag = false
    }

    @Suppress("unused")
    val renderHandler = handler<WorldRenderEvent> { event ->
        val matrixStack = event.matrixStack
        val position = highlightBlock ?: return@handler
        val state = position.getState() ?: return@handler

        if (state.renderType != BlockRenderType.MODEL || state.isAir) {
            return@handler
        }

        renderEnvironmentForWorld(matrixStack) {
            withDisabledCull {
                val color = if (isSuitable(position)) {
                    Color4b(0x20, 0xC2, 0x06)
                } else {
                    Color4b(0xD7, 0x09, 0x09)
                }

                val baseColor = color.with(a = 50)
                val transparentColor = baseColor.with(a = 0)
                val outlineColor = color.with(a = 200)

                withPositionRelativeToCamera(position.up().toVec3d()) {
                    withColor(baseColor) {
                        drawSideBox(FULL_BOX, Direction.DOWN)
                    }

                    withColor(outlineColor) {
                        drawSideBox(FULL_BOX, Direction.DOWN, onlyOutline = true)
                    }

                    drawGradientSides(0.7, baseColor, transparentColor, FULL_BOX)
                }
            }
        }
    }

    @Suppress("unused")
    val shapeHandler = handler<BlockShapeEvent> { event ->
        if (requiresCollision && event.state.block !is FluidBlock && event.pos.y < player.y) {
            event.shape = VoxelShapes.fullCube()
        }
    }

    private var swap = false

    @Suppress("unused")
    val handleMovement = handler<MovementInputEvent> {
        if (requiresMovement) {
            it.directionalInput = if (swap) DirectionalInput.LEFT else DirectionalInput.RIGHT
            swap = !swap
        }
    }

    @Suppress("unused")
    private val fakeLagHandler = handler<QueuePacketEvent> { event ->
        if (event.origin == TransferOrigin.OUTGOING && requiresLag) {
            event.action = PacketQueueManager.Action.QUEUE
        }
    }

    private fun isSuitable(blockPos: BlockPos?): Boolean {
        if (blockPos == null || !blockPos.canStandOn()) {
            return false
        }

        return !blockPos.up().canStandOn() && !blockPos.up(2).canStandOn()
    }

    override fun onDisabled() {
        requiresLag = false
        requiresCollision = false
        requiresMovement = false
        highlightBlock = null
        super.onDisabled()
    }

}
