/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program 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 Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.bausystem.features.smartplace;

import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.TileState;
import org.bukkit.block.data.*;
import org.bukkit.block.data.type.*;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.RayTraceResult;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Linked
public class SmartPlaceListener implements Listener {

    private static final Set<Material> CONTAINERS = new HashSet<>();
    private static final Set<Material> IGNORED = new HashSet<>();

    static {
        World world = Bukkit.getWorlds().get(0);
        Block block = world.getBlockAt(0, 0, 0);
        block.setType(Material.AIR);
        BlockState state = block.getState();
        for (Material material : Material.values()) {
            if (material.isLegacy()) continue;
            if (!material.isInteractable() && !material.isBlock()) continue;
            BlockData blockData = material.createBlockData();
            block.setBlockData(blockData);
            if (block.getState() instanceof TileState) {
                CONTAINERS.add(material);
            } else if (blockData instanceof Stairs) {
                CONTAINERS.add(material);
            }
            state.update(true, false);
        }
        CONTAINERS.add(Material.GRINDSTONE);
        CONTAINERS.remove(Material.COMPARATOR);
        state.update(true, false);

        IGNORED.add(Material.TNT);
        IGNORED.add(Material.REDSTONE_ORE);
        IGNORED.add(Material.BEEHIVE);
        IGNORED.add(Material.SEA_PICKLE);
        IGNORED.remove(Material.STONE);
        IGNORED.remove(Material.BARRIER);
    }

    private static final Set<Player> SMART_PLACING = new HashSet<>();
    private static final Set<Player> WAS_EXECUTED = new HashSet<>();

    public SmartPlaceListener() {
        TinyProtocol.instance.addFilter(ServerboundUseItemOnPacket.class, (player, packet) -> {
            if (!Permission.BUILD.hasPermission(player)) return packet;
            if (!Config.getInstance().get(player).getPlainValueOrDefault("smartPlace", false)) return packet;
            RayTraceResult rayTraceResult = player.rayTraceBlocks(6);
            Block block = rayTraceResult != null ? rayTraceResult.getHitBlock() : null;
            BlockFace blockFace = rayTraceResult != null ? rayTraceResult.getHitBlockFace() : null;
            boolean shouldSneak = false;
            if (block != null) {
                ItemStack itemStack = player.getInventory().getItemInMainHand();
                if (block.getType().isInteractable() || block.getType() == Material.NOTE_BLOCK) {
                    shouldSneak = true;
                }
                if (CONTAINERS.contains(block.getType())) {
                    if (itemStack.getType() == Material.TNT) {
                        if (block.getType() == Material.CHEST || block.getType() == Material.BARREL || block.getType().name().endsWith("SHULKER_BOX")) {
                            shouldSneak = false;
                        }
                    }
                } else {
                    shouldSneak = false;
                }
                if (IGNORED.contains(block.getType())) {
                    shouldSneak = false;
                }
                if (blockFace == BlockFace.DOWN && (block.getType() == Material.REPEATER || block.getType() == Material.COMPARATOR) && itemStack.getType().isSolid()) {
                    shouldSneak = true;
                }
            }
            boolean sneaking = player.isSneaking();
            if (sneaking) SMART_PLACING.add(player);
            player.setSneaking(shouldSneak || sneaking);
            Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
                SMART_PLACING.remove(player);
                player.setSneaking(sneaking);
            }, 0);
            return packet;
        });
    }

    @EventHandler
    public void onPlayerInteract(PlayerInteractEvent event) {
        if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
        if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return;
        WAS_EXECUTED.add(event.getPlayer());
        if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
        if (event.getPlayer().getGameMode() == GameMode.SPECTATOR) return;
        if (!event.getPlayer().isSneaking()) return;
        if (event.getClickedBlock().getType() != Material.REPEATER) return;
        if ((event.getItem() == null || event.getItem().getType() == Material.REPEATER)) {
            Repeater repeater = (Repeater) event.getClickedBlock().getBlockData();
            int i = repeater.getDelay() - 1;
            if (i <= 0) i += 4;
            repeater.setDelay(i);
            event.getClickedBlock().setBlockData(repeater);
            event.setCancelled(true);
        }
    }

    @EventHandler
    public void onBlockPlace(BlockPlaceEvent event) {
        if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
        if (!Config.getInstance().get(event.getPlayer()).getPlainValueOrDefault("smartPlace", false)) return;
        if (!SMART_PLACING.contains(event.getPlayer())) {
            if (CONTAINERS.contains(event.getBlockAgainst().getType())) {
                SoundGroup soundGroup = event.getBlockPlaced().getBlockData().getSoundGroup();
                event.getPlayer().playSound(event.getBlockPlaced().getLocation(), soundGroup.getPlaceSound(), soundGroup.getVolume() * 0.8F, soundGroup.getPitch() * 0.8F);
            }
            return;
        }
        BlockData blockData = event.getBlock().getBlockData();
        if (blockData instanceof Bed) {
            Bed bed = (Bed) blockData;
            Block bedHead = event.getBlock().getRelative(bed.getFacing());
            bed.setPart(Bed.Part.HEAD);
            bed.setFacing(bed.getFacing().getOppositeFace());

            bed = (Bed) bedHead.getBlockData();
            bed.setPart(Bed.Part.FOOT);
            bed.setFacing(bed.getFacing().getOppositeFace());
            bedHead.setBlockData(bed, false);
        } else if (blockData instanceof FaceAttachable && ((FaceAttachable) blockData).getAttachedFace() != FaceAttachable.AttachedFace.WALL) {
            FaceAttachable faceAttachable = (FaceAttachable) blockData;
            faceAttachable.setAttachedFace(faceAttachable.getAttachedFace() == FaceAttachable.AttachedFace.CEILING ? FaceAttachable.AttachedFace.FLOOR : FaceAttachable.AttachedFace.CEILING);
        } else if (blockData instanceof Rotatable) {
            Rotatable rotatable = (Rotatable) blockData;
            rotatable.setRotation(rotatable.getRotation().getOppositeFace());
        } else if (blockData instanceof Directional) {
            Directional directional = (Directional) blockData;
            BlockFace face = directional.getFacing().getOppositeFace();
            if (directional.getFaces().contains(face)) {
                directional.setFacing(face);
            }
        } else if (blockData instanceof MultipleFacing && !(blockData instanceof Fence)) {
            MultipleFacing multipleFacing = (MultipleFacing) blockData;
            List<BlockFace> blockFaceList = multipleFacing.getFaces()
                    .stream()
                    .map(BlockFace::getOppositeFace)
                    .collect(Collectors.toList());
            if (multipleFacing.getAllowedFaces().containsAll(blockFaceList)) {
                multipleFacing.getFaces().forEach(blockFace -> {
                    multipleFacing.setFace(blockFace, false);
                });
                blockFaceList.forEach(blockFace -> {
                    multipleFacing.setFace(blockFace, true);
                });
            }
        }
        if (blockData instanceof Stairs) {
            Stairs stairs = (Stairs) blockData;
            switch (stairs.getShape()) {
                case OUTER_LEFT:
                    stairs.setShape(Stairs.Shape.INNER_RIGHT);
                    break;
                case INNER_LEFT:
                    stairs.setShape(Stairs.Shape.OUTER_RIGHT);
                    break;
                case OUTER_RIGHT:
                    stairs.setShape(Stairs.Shape.INNER_LEFT);
                    break;
                case INNER_RIGHT:
                    stairs.setShape(Stairs.Shape.OUTER_LEFT);
                    break;
                case STRAIGHT:
                    break;
            }
        }
        if (blockData instanceof Piston) {
            Piston piston = (Piston) blockData;
            Block block = event.getBlock().getRelative(piston.getFacing().getOppositeFace());
            Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
                event.getPlayer().sendBlockChange(block.getLocation(), block.getBlockData());
            }, 2);
        } else if (blockData.getMaterial() == Material.REPEATER || blockData.getMaterial() == Material.COMPARATOR) {
            Block block = event.getBlock().getRelative(BlockFace.DOWN);
            BlockState old = block.getState();
            Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
                block.setType(Material.GLASS, true);
                old.update(true, false);
            }, 1);
        }
        event.getBlock().setBlockData(blockData, true);
    }
}
