package cz.jzitnik.tui;

import cz.jzitnik.game.Game;
import cz.jzitnik.game.ui.Window;
import lombok.AllArgsConstructor;
import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Handles mouse interactions in the terminal-based UI.
 * Supports clicking, scrolling, and moving to interact with blocks, inventory, and UI components.
 */
@AllArgsConstructor
public class MouseHandler {
    private Game game;
    private Terminal terminal;
    private ScreenRenderer screenRenderer;

    /**
     * Handles a generic mouse event by dispatching to the correct handler based on the event type.
     *
     * @param mouseEvent The mouse event received from the terminal.
     */
    public void handle(MouseEvent mouseEvent) {
        if (mouseEvent.getButton() == MouseEvent.Button.Button1 && mouseEvent.getType() == MouseEvent.Type.Pressed) {
            leftClick(mouseEvent);
            return;
        }

        if (mouseEvent.getButton() == MouseEvent.Button.Button3 && mouseEvent.getType() == MouseEvent.Type.Pressed) {
            rightClick(mouseEvent);
            return;
        }

        if (mouseEvent.getType() == MouseEvent.Type.Moved) {
            move(mouseEvent);
            return;
        }

        if (mouseEvent.getType() == MouseEvent.Type.Wheel) {
            scroll(mouseEvent);
        }
    }

    /**
     * Handles mouse scroll events to change the selected hotbar slot when in the world view.
     *
     * @param mouseEvent The mouse scroll event.
     */
    private void scroll(MouseEvent mouseEvent) {
        if (game.getWindow() == Window.WORLD) {
            if (mouseEvent.getButton() == MouseEvent.Button.WheelUp) {
                if (game.getInventory().getItemInhHandIndex() == 0) {
                    game.changeSlot(8, screenRenderer);
                } else {
                    game.changeSlot(game.getInventory().getItemInhHandIndex() - 1, screenRenderer);
                }
            }

            if (mouseEvent.getButton() == MouseEvent.Button.WheelDown) {
                if (game.getInventory().getItemInhHandIndex() == 8) {
                    game.changeSlot(0, screenRenderer);
                } else {
                    game.changeSlot(game.getInventory().getItemInhHandIndex() + 1, screenRenderer);
                }
            }
        }
    }

    /**
     * Handles left-click actions on the screen.
     * Attempts to hit or mine the block the cursor is over, depending on the game state.
     *
     * @param mouseEvent The mouse click event (left button).
     */
    private void leftClick(MouseEvent mouseEvent) {
        int mouseX = mouseEvent.getX();
        int mouseY = mouseEvent.getY();

        int[] cords = game.getPlayerCords();
        if (cords == null)
            return;

        int playerX = cords[0];
        int playerY = cords[1];

        int[] data = ScreenMovingCalculationProvider.calculate(playerX, playerY, terminal.getHeight(),
                terminal.getWidth(), game.getWorld()[0].length, game.getWorld().length);

        int startX = data[0];
        int startY = data[2];

        int blockX = startX + (mouseX / 50); // 50 chars wide per sprite
        int blockY = startY + (mouseY / 25); // 25 lines high per sprite

        if (game.isHitable(blockX, blockY, terminal)) {
            game.hit(screenRenderer, blockX, blockY);
        }

        if (game.isMineable(blockX, blockY, terminal)) {
            screenRenderer.setSelectedBlock(Optional.empty());
            game.mine(screenRenderer, blockX, blockY);
        }
    }

    /**
     * Handles right-click actions on the screen.
     * Attempts to place a block at the cursor's current location.
     *
     * @param mouseEvent The mouse click event (right button).
     */
    private void rightClick(MouseEvent mouseEvent) {
        int mouseX = mouseEvent.getX();
        int mouseY = mouseEvent.getY();

        int[] cords = game.getPlayerCords();
        if (cords == null)
            return;

        int playerX = cords[0];
        int playerY = cords[1];

        int[] data = ScreenMovingCalculationProvider.calculate(playerX, playerY, terminal.getHeight(),
                terminal.getWidth(), game.getWorld()[0].length, game.getWorld().length);

        int startX = data[0];
        int startY = data[2];

        int blockX = startX + (mouseX / 50); // 50 chars wide per sprite
        int blockY = startY + (mouseY / 25); // 25 lines high per sprite

        game.build(blockX, blockY, screenRenderer);
    }

    /**
     * Handles mouse movement events.
     * Used for highlighting mineable blocks in the world view when hovering.
     *
     * @param mouseEvent The mouse move event.
     */
    private void move(MouseEvent mouseEvent) {
        if (game.isMining() || game.getWindow() != Window.WORLD) {
            return;
        }

        int mouseX = mouseEvent.getX();
        int mouseY = mouseEvent.getY();

        int[] cords = game.getPlayerCords();
        if (cords == null)
            return;

        int playerX = cords[0];
        int playerY = cords[1];

        int[] data = ScreenMovingCalculationProvider.calculate(playerX, playerY, terminal.getHeight(),
                terminal.getWidth(), game.getWorld()[0].length, game.getWorld().length);

        int startX = data[0];
        int startY = data[2];

        int blockX = startX + (mouseX / 50); // 50 chars wide per sprite
        int blockY = startY + (mouseY / 25); // 25 lines high per sprite

        if (blockY == playerX && blockY == playerY) {
            return;
        }

        if (game.isMineable(blockX, blockY, terminal)) {

            if (screenRenderer.getSelectedBlock().isPresent()
                    && screenRenderer.getSelectedBlock().get().x == blockX
                    && screenRenderer.getSelectedBlock().get().y == blockY) {
                return;
            }

            screenRenderer.setSelectedBlock(Optional.of(new Point(blockX, blockY)));
        } else {
            if (screenRenderer.getSelectedBlock().isEmpty()) {
                return;
            }
            screenRenderer.setSelectedBlock(Optional.empty());
        }

        new Thread(() -> screenRenderer.render(game)).start();
    }
}
