package cz.jzitnik.tui.utils;


import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal;

import cz.jzitnik.game.Game;
import cz.jzitnik.game.sprites.ui.Font;
import cz.jzitnik.game.sprites.ui.Font.*;
import cz.jzitnik.tui.ScreenRenderer;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;

/**
 * Represents a simple interactive menu rendered in a terminal UI.
 * Displays a heading and a list of buttons, handles rendering and mouse interactions.
 */
@Slf4j
public class Menu {
    private Game game;
    private String headingText;
    private String[] buttonLabels;
    private boolean[] buttonsHover;
    private int btnWidth;
    private int leftPad;
    private int mainTextHeight;
    private int buttonHeight;
    private int textButtonMargin;
    private ButtonClickHandler buttonClickHandler;

    /**
     * Constructs a new Menu.
     *
     * @param game               The game instance for accessing shared resources.
     * @param headingText        The main heading to display at the top of the menu.
     * @param buttonLabels       Array of button labels.
     * @param buttonClickHandler Handler for button click actions.
     */
    public Menu(Game game, String headingText, String[] buttonLabels, ButtonClickHandler buttonClickHandler) {
        this.game = game;
        this.headingText = headingText;
        this.buttonLabels = buttonLabels;
        this.buttonsHover = new boolean[buttonLabels.length];
        this.buttonClickHandler = buttonClickHandler;
    }

    /**
     * Renders the full menu including heading and buttons into the provided buffer.
     *
     * @param buffer   The StringBuilder buffer to append rendered output to.
     * @param terminal The terminal instance, used to get terminal size.
     */
    public void render(StringBuilder buffer, Terminal terminal) {
        var font = game.getGameStates().dependencies.font;
        var width = terminal.getWidth();
        var height = terminal.getHeight();

        var heading = font.line(terminal, headingText, Size.LARGE, Align.CENTER);
        buffer.append(heading.getData());
        mainTextHeight = heading.getHeight();

        textButtonMargin = (height < 600) ? 15 : (height < 800) ? 30 : 50;
        buffer.append("\n".repeat(textButtonMargin));

        for (int i = 0; i < buttonLabels.length; i++) {
            renderButton(buffer, buttonLabels[i], width, font, terminal, buttonsHover[i]);
            buffer.append("\n".repeat(textButtonMargin / 2));
        }
    }

    /**
     * Handles mouse events related to the menu.
     * Supports hover and click detection on buttons.
     *
     * @param mouseEvent     The mouse event to handle.
     * @param terminal       The terminal instance.
     * @param screenRenderer The renderer used to re-render the screen on hover change or click.
     */
    public void handleMouse(MouseEvent mouseEvent, Terminal terminal, ScreenRenderer screenRenderer) {
        int x = mouseEvent.getX();
        int y = mouseEvent.getY();
        var type = mouseEvent.getType();

        int buttonx = x - leftPad;
        int buttony = y - (mainTextHeight + textButtonMargin);
        int margin = textButtonMargin / 2;

        boolean changed = false;

        if (buttonx > 0 && buttonx <= btnWidth) {
            for (int i = 0; i < buttonLabels.length; i++) {
                int top = i * (buttonHeight + margin);
                int bottom = top + buttonHeight;

                if (buttony > top && buttony < bottom) {
                    if (type == MouseEvent.Type.Pressed) {
                        buttonClickHandler.onClick(i, screenRenderer);
                        return;
                    }
                    if (!buttonsHover[i]) {
                        buttonsHover[i] = true;
                        changed = true;
                    }
                } else if (buttonsHover[i]) {
                    buttonsHover[i] = false;
                    changed = true;
                }
            }
        }

        if (changed) {
            screenRenderer.render(game);
        }
    }

    /**
     * Renders a single button to the buffer using the custom font renderer and styles.
     *
     * @param buffer   The buffer to append rendered content to.
     * @param txt      The text label of the button.
     * @param width    The terminal width, used for calculating centering.
     * @param font     The font rendering utility.
     * @param terminal The terminal instance.
     * @param selected Whether the button is currently hovered (for styling).
     */
    public void renderButton(StringBuilder buffer, String txt, int width, Font font, Terminal terminal, boolean selected) {
        btnWidth = Math.min(350, (int) (width * (3.0 / 4)));
        leftPad = (width - btnWidth) / 2;
        log.debug("Button width: {}px ", btnWidth);

        final String color = selected ? "[48;2;70;70;70m" : "[48;2;116;115;113m";
        var text = font.line(terminal, txt, Size.SMALL, Align.CENTER, new Custom.Width(btnWidth - 4), new Background(color));
        buttonHeight = text.getHeight() + 4;
        var lines = text.getData().split("\n");

        for (int y = 0; y < buttonHeight; y++) {
            buffer.append(" ".repeat(leftPad));
            if (y < 2 || y >= buttonHeight - 2) {
                buffer.append("\033").append(color).append(" ".repeat(btnWidth));
            } else {
                buffer.append("\033").append(color).append(" ".repeat(2));
                buffer.append(lines[y - 2]);
                buffer.append("\033").append(color).append(" ".repeat(2));
            }
            buffer.append("\033[0m\n");
        }
    }

    /**
     * Interface for handling button click actions.
     */
    public interface ButtonClickHandler {
        /**
         * Called when a button is clicked.
         *
         * @param index          The index of the clicked button.
         * @param screenRenderer The renderer used to update the screen.
         */
        void onClick(int index, ScreenRenderer screenRenderer);
    }

    /**
     * Resets all hover states on the buttons (useful when switching views).
     */
    public void reset() {
        Arrays.fill(buttonsHover, false);
    }
}
