/*
 * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
 *
 * Copyright (c) 2015 - 2026 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.events.BlinkPacketEvent
import net.ccbluex.liquidbounce.event.events.BlockShapeEvent
import net.ccbluex.liquidbounce.event.events.MovementInputEvent
import net.ccbluex.liquidbounce.event.events.NotificationEvent
import net.ccbluex.liquidbounce.event.events.TransferOrigin
import net.ccbluex.liquidbounce.event.events.WorldRenderEvent
import net.ccbluex.liquidbounce.event.handler
import net.ccbluex.liquidbounce.event.tickHandler
import net.ccbluex.liquidbounce.event.tickUntil
import net.ccbluex.liquidbounce.event.waitTicks
import net.ccbluex.liquidbounce.features.blink.BlinkManager
import net.ccbluex.liquidbounce.features.module.ClientModule
import net.ccbluex.liquidbounce.features.module.ModuleCategories
import net.ccbluex.liquidbounce.render.FULL_BOX
import net.ccbluex.liquidbounce.render.drawBoxSide
import net.ccbluex.liquidbounce.render.drawGradientSides
import net.ccbluex.liquidbounce.render.engine.type.Color4b
import net.ccbluex.liquidbounce.render.renderEnvironment
import net.ccbluex.liquidbounce.render.withPositionRelativeToCamera
import net.ccbluex.liquidbounce.utils.block.canStandOn
import net.ccbluex.liquidbounce.utils.block.getState
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.bottomCenter
import net.ccbluex.liquidbounce.utils.movement.DirectionalInput
import net.ccbluex.liquidbounce.utils.raytracing.traceFromPlayer
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.world.level.block.LiquidBlock
import net.minecraft.world.level.block.RenderShape
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes

/**
 * ClickTP
 *
 * Only works for Vulcan AntiCheat
 */
object ModuleClickTp : ClientModule("ClickTp", ModuleCategories.EXPLOIT, aliases = listOf("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 = traceFromPlayer(player.rotation, range = range.toDouble())
        val blockPos = raycast?.blockPos.apply {
            highlightBlock = this
        }

        if (!mc.options.keyPickItem.isDown) {
            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.bottomCenter(yOffset = 1.5)

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

        teleportVulcan286(pos)

        waitTicks(5)
        if (blockPos.distToCenterSqr(player.position()) < 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 teleportVulcan286(pos: Vec3) {
        requiresLag = true

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

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

            waitTicks(1)
            tickUntil { player.onGround() }
            requiresCollision = false
        }

        requiresMovement = true
        waitTicks(lagAfter)
        requiresMovement = false

        requiresLag = false
    }

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

        if (state.renderShape != RenderShape.MODEL || state.isAir) {
            return@handler
        }

        event.renderEnvironment {
            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.above()) {
                drawBoxSide(
                    FULL_BOX,
                    Direction.DOWN,
                    baseColor,
                    outlineColor,
                )
                drawGradientSides(0.7, baseColor, transparentColor, FULL_BOX)
            }
        }
    }

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

    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<BlinkPacketEvent> { event ->
        if (event.origin == TransferOrigin.OUTGOING && requiresLag) {
            event.action = BlinkManager.Action.QUEUE
        }
    }

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

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

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

}
