package cz.jzitnik.game.ui;

import cz.jzitnik.game.Game;
import cz.jzitnik.game.crafting.CraftingRecipe;
import cz.jzitnik.game.crafting.CraftingRecipeList;
import cz.jzitnik.game.entities.items.InventoryItem;
import cz.jzitnik.tui.ScreenRenderer;
import cz.jzitnik.tui.utils.SpriteCombiner;
import cz.jzitnik.tui.SpriteList;
import cz.jzitnik.tui.utils.Numbers;
import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
 * Represents the crafting table UI where the player can combine items
 * according to recipes to create new ones.
 */
public class CraftingTable {
    private final Game game;
    private static final int ROW_AMOUNT = 3;
    private static final int COLUMN_AMOUNT = 3;
    private static final int CELL_WIDTH = 50;
    private static final int CELL_HEIGHT = 25;
    private static final int BORDER_SIZE = 2;

    private final InventoryItem[] items = new InventoryItem[ROW_AMOUNT * COLUMN_AMOUNT];
    private int size;

    /**
     * Sets the current game window to the crafting table view.
     *
     * @param ignored  Ignored parameter for compatibility.
     * @param ignored2 Ignored parameter for compatibility.
     */
    public void render(int ignored, int ignored2) {
        game.setWindow(Window.CRAFTING_TABLE);
    }

    /**
     * Picks up the result of a valid recipe if one exists in the crafting grid,
     * adds it to the inventory, and decreases the input items accordingly.
     */
    public void pickup() {
        Optional<CraftingRecipe> recipe = CraftingRecipeList.getRecipe(Arrays.stream(items)
                .map(item -> item == null ? null : item.getItem().getFirst().getId()).toArray(String[]::new));

        if (recipe.isPresent()) {
            InventoryItem item = recipe.get().getItemSupplier().get();
            game.getInventory().addItem(item);

            for (int i = 0; i < items.length; i++) {
                InventoryItem it = items[i];
                if (it == null) {
                    continue;
                }

                if (it.getAmount() == 1) {
                    items[i] = null;
                } else {
                    it.decrease();
                }
            }
        }
    }

    /**
     * Renders the crafting table UI and the inventory below it.
     *
     * @param buffer     Buffer to append rendered content to.
     * @param terminal   Terminal to determine screen dimensions.
     * @param spriteList Sprite list to fetch item visuals.
     */
    public void render(StringBuilder buffer, Terminal terminal, SpriteList spriteList) {
        int widthPixels = COLUMN_AMOUNT * (CELL_WIDTH + BORDER_SIZE) + BORDER_SIZE;
        var inventory = game.getInventory();

        int moveLeft = Math.max(0, (terminal.getWidth() / 2) - (widthPixels / 2));

        List<String> sprites = game.getInventory().getSprites(items, spriteList, inventory.getSelectedItemInv() - 50, game);

        Optional<CraftingRecipe> recipe = CraftingRecipeList.getRecipe(Arrays.stream(items)
                .map(item -> item == null ? null : item.getItem().getFirst().getId()).toArray(String[]::new));

        Optional<InventoryItem> craftedItem = recipe.map(craftingRecipe -> craftingRecipe.getItemSupplier().get());

        String[] craftedSprite = craftedItem.map(inventoryItem -> SpriteCombiner.combineTwoSprites(
                inventoryItem.getItem().getFirst().getSpriteState().isPresent()
                        ? spriteList.getSprite(inventoryItem.getItem().getFirst().getSprite())
                                .getSprite(inventoryItem.getItem().getFirst().getSpriteState().get())
                        : spriteList.getSprite(inventoryItem.getItem().getFirst().getSprite()).getSprite(),
                Numbers.getNumberSprite(inventoryItem.getAmount(), game)).split("\n")).orElse(null);

        for (int i = 0; i < ROW_AMOUNT; i++) {
            for (int j = 0; j < CELL_HEIGHT; j++) {
                buffer.append("\033[0m").append(" ".repeat(moveLeft));
                for (int k = 0; k < COLUMN_AMOUNT; k++) {
                    buffer.append("\033[38;5;231;48;5;231m▓".repeat(BORDER_SIZE));
                    if (j == 0 || j == CELL_HEIGHT - 1) {
                        buffer.append("\033[38;5;231;48;5;231m▓".repeat(CELL_WIDTH));
                    } else {
                        String sprite = sprites.get(i * COLUMN_AMOUNT + k);
                        if (sprite == null || sprite.isEmpty()) {
                            buffer.append("\033[0m ".repeat(CELL_WIDTH));
                        } else {
                            String[] spriteLines = sprite.split("\n");
                            buffer.append(spriteLines[j + 1]);
                        }
                    }
                    buffer.append("\033[38;5;231;48;5;231m▓".repeat(BORDER_SIZE));
                }

                if (i == 1) {
                    buffer.append("\033[0m ".repeat(7));
                    buffer.append("\033[38;5;231;48;5;231m▓".repeat(BORDER_SIZE));
                    if (j == 0 || j == CELL_HEIGHT - 1) {
                        buffer.append("\033[38;5;231;48;5;231m▓".repeat(CELL_WIDTH));
                    } else {
                        if (craftedItem.isEmpty()) {
                            buffer.append("\033[0m ".repeat(CELL_WIDTH));
                        } else {
                            buffer.append(craftedSprite[j + 1]);
                        }
                    }
                    buffer.append("\033[38;5;231;48;5;231m▓".repeat(BORDER_SIZE));
                }
                buffer.append("\n");
            }
        }

        buffer.append("\n".repeat(20));

        size = buffer.toString().split("\n").length;

        game.getInventory().renderFull(buffer, terminal, spriteList, false, Optional.of(size), game);
    }

    /**
     * Handles mouse clicks inside the crafting grid or on the output slot.
     *
     * @param mouseEvent      Mouse event with coordinates and type.
     * @param terminal        Terminal for dimension reference.
     * @param screenRenderer  Renderer to trigger screen updates.
     */
    public void click(MouseEvent mouseEvent, Terminal terminal, ScreenRenderer screenRenderer) {
        int x = mouseEvent.getX();
        int y = mouseEvent.getY();
        int widthPixels = COLUMN_AMOUNT * (CELL_WIDTH + BORDER_SIZE) + BORDER_SIZE;
        int heightPixels = ROW_AMOUNT * (CELL_HEIGHT + 1);
        int moveLeft = (terminal.getWidth() / 2) - (widthPixels / 2);

        if (x > moveLeft + widthPixels + 7 && x < moveLeft + widthPixels + 7 + CELL_WIDTH + BORDER_SIZE * 2 && y > 26
                && y < 52 && mouseEvent.getType() == MouseEvent.Type.Pressed
                && mouseEvent.getButton() == MouseEvent.Button.Button1) {
            pickup();
            screenRenderer.render(game);

            return;
        }

        if (x > moveLeft && x <= moveLeft + widthPixels && y > 0 && y < heightPixels
                && mouseEvent.getType() == MouseEvent.Type.Pressed) {
            if (mouseEvent.getType() != MouseEvent.Type.Pressed)
                return;

            int blockX = (x - moveLeft) / 52;
            int blockY = y / 26;

            InventoryClickHandler.handleItemClick(mouseEvent, game.getInventory(), items,
                    blockY * COLUMN_AMOUNT + blockX, 50, Optional.of(items));

            screenRenderer.render(game);

            return;
        }

        // TODO: Why I need to add 20 here. Like wtf
        InventoryClickHandler.click(mouseEvent, terminal, screenRenderer, game, Optional.of(size + 20),
                Optional.of(items));
    }

    /**
     * Transfers all remaining items in the crafting grid back to the inventory
     * and clears the crafting grid.
     */
    public void exit() {
        // Put all items from crafting to inv
        for (int i = 0; i < items.length; i++) {
            InventoryItem item = items[i];
            if (item == null) {
                continue;
            }

            game.getInventory().addItem(item);
            items[i] = null;
        }
    }

    /**
     * Constructor for the crafting table.
     *
     * @param game Reference to the game instance.
     */
    public CraftingTable(Game game) {
        this.game = game;
    }
}
