/*
 *   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.loader.elements;

import de.steamwar.bausystem.BauSystem;
import de.steamwar.data.CMDs;
import de.steamwar.inventory.SWAnvilInv;
import de.steamwar.inventory.SWInventory;
import de.steamwar.inventory.SWItem;
import de.steamwar.inventory.SWListInv;
import lombok.Getter;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.*;
import org.bukkit.block.data.type.Door;
import org.bukkit.block.data.type.Switch;
import org.bukkit.entity.Player;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public abstract class LoaderInteractionElement<T extends Enum<T> & LoaderSettingsEnum> implements LoaderElement {

    @Getter
    protected final Location location;
    protected int currentShot = 0;
    protected T defaultSetting;
    protected T newSetting;
    protected T[] allSettings;
    protected List<T> elements = new ArrayList<>();
    protected List<Integer> extraPower = new ArrayList<>();
    protected List<Long> extraTicks = new ArrayList<>();
    protected int settingsGuiSize = 0;

    protected LoaderInteractionElement(Location location, T defaultSetting, T newSetting, T[] allSettings) {
        this.location = location;

        this.defaultSetting = defaultSetting;
        this.newSetting = newSetting;
        this.allSettings = allSettings;

        elements.add(defaultSetting);
        extraPower.add(0);
        extraTicks.add(0L);

        for (T element : allSettings) {
            settingsGuiSize = Math.max(element.getPos(), settingsGuiSize);
        }
        while (settingsGuiSize % 9 != 0) settingsGuiSize++;
        settingsGuiSize += 9;
    }

    @Override
    public void execute(Consumer<Long> delay) {
        if (currentShot >= elements.size()) currentShot = 0;
        if (checkBlockInWorld()) {
            BlockData blockData = location.getBlock().getBlockData();
            elements.get(currentShot).execute(location, blockData, this, extraPower.get(currentShot), extraTicks.get(currentShot), delay);
        }
        currentShot++;
        if (currentShot >= elements.size()) currentShot = 0;
    }

    @Override
    public void click(Player player, Runnable backAction) {
        List<SWListInv.SWListEntry<Integer>> entries = new ArrayList<>();
        Runnable updateRunnable = () -> {
            entries.clear();
            for (int i = 0; i < elements.size(); i++) {
                SWItem swItem = elements.get(i).menu(player, this, extraPower.get(i), extraTicks.get(i));
                swItem.setLore(Arrays.asList(BauSystem.MESSAGE.parse("LOADER_GUI_CLICK_TO_EDIT", player)));
                entries.add(new SWListInv.SWListEntry<>(swItem, i));
            }
        };
        updateRunnable.run();

        SWListInv<Integer> listInv = new SWListInv<>(player, "Interaction Settings", false, entries, (clickType, entry) -> {});
        listInv.setCallback((clickType, entry) -> {
            openIndividualSettingsMenu(player, entry, () -> {
                updateRunnable.run();
                listInv.open();
            }, () -> {
                elements.remove((int) entry);
                extraPower.remove((int) entry);
                extraTicks.remove((int) entry);
                if (elements.isEmpty()) {
                    elements.add(newSetting);
                    extraPower.add(0);
                    extraTicks.add(1L);
                }
                click(player, backAction);
            });
        });
        listInv.setItem(48, new SWItem(Material.ARROW, "§7Back", clickType -> {
            backAction.run();
        }).setCustomModelData(CMDs.BACK));
        listInv.setItem(50, new SWItem(Material.GHAST_SPAWN_EGG, "§7Insert another Setting", clickType -> {
            elements.add(defaultSetting);
            extraPower.add(0);
            extraTicks.add(1L);
            openIndividualSettingsMenu(player, elements.size() - 1, () -> {
                updateRunnable.run();
                listInv.open();
            }, () -> {
                elements.remove(elements.size() - 1);
                extraPower.remove(extraPower.size() - 1);
                extraTicks.remove(extraTicks.size() - 1);
                if (elements.isEmpty()) {
                    elements.add(newSetting);
                    extraPower.add(0);
                    extraTicks.add(1L);
                }
                click(player, backAction);
            });
        }));
        listInv.open();
    }

    private void openIndividualSettingsMenu(Player player, int index, Runnable back, Runnable delete) {
        T currentElement = elements.get(index);
        int guiSize = settingsGuiSize;
        int powerStart = guiSize - 9;
        if (currentElement.hasPower(this)) {
            guiSize += 18;
        }
        int ticksStart = guiSize - 9;
        if (currentElement.hasTicks(this)) {
            guiSize += 9;
        }

        SWInventory swInventory = new SWInventory(player, guiSize, BauSystem.MESSAGE.parse("LOADER_GUI_SETTINGS_TITLE", player));
        for (int i = guiSize - 9; i < guiSize; i++) swInventory.setItem(i, new SWItem(Material.GRAY_STAINED_GLASS_PANE, "§7", clickType -> {}));
        swInventory.setItem(guiSize - 9, new SWItem(Material.ARROW, BauSystem.MESSAGE.parse("LOADER_GUI_SETTINGS_BACK", player)).setCustomModelData(CMDs.BACK).getItemStack(), clickType -> back.run());
        swInventory.setItem(guiSize - 5, new SWItem(Material.WOODEN_AXE, BauSystem.MESSAGE.parse("LOADER_GUI_SETTINGS_COPY", player)).getItemStack(), clickType -> {
            SWAnvilInv swAnvilInv = new SWAnvilInv(player, BauSystem.MESSAGE.parse("LOADER_GUI_COPY_TITLE", player), "1");
            swAnvilInv.setCallback(s -> {
                try {
                    int count = Integer.parseInt(s);
                    if (count < 1) count = 1;
                    if (count > 65536) count = 65536;

                    int power = extraPower.get(index);
                    long ticks = extraTicks.get(index);
                    for (int i = 0; i < count; i++) {
                        elements.add(currentElement);
                        extraPower.add(power);
                        extraTicks.add(ticks);
                    }

                    if (count == 1) {
                        openIndividualSettingsMenu(player, elements.size() - 1, back, delete);
                    } else {
                        back.run();
                    }
                } catch (NumberFormatException e) {
                    back.run();
                }
            });
            swAnvilInv.open();
        });
        swInventory.setItem(guiSize - 1, new SWItem(Material.BARRIER, BauSystem.MESSAGE.parse("LOADER_GUI_SETTINGS_DELETE", player)).getItemStack(), clickType -> delete.run());

        for (T element : allSettings) {
            SWItem item = element.menu(player, this, extraPower.get(index), extraTicks.get(index));
            if (element == currentElement) {
                item.setEnchanted(true);
            }

            swInventory.setItem(element.getPos(), item.getItemStack(), clickType -> {
                elements.set(index, element);
                openIndividualSettingsMenu(player, index, back, delete);
            });
        }

        if (currentElement.hasPower(this)) {
            for (int power = 0; power < 16; power++) {
                int finalPowerPosition = power;
                if (power >= 9) finalPowerPosition++;
                SWItem powerItem = new SWItem(Material.REDSTONE, BauSystem.MESSAGE.parse("LOADER_SETTING_POWER", player, power), Arrays.asList(), false, clickType -> {});
                powerItem.getItemStack().setAmount(Math.max(power, 1));
                if (extraPower.get(index) == power) powerItem.setEnchanted(true);

                int finalPower = power;
                swInventory.setItem(finalPowerPosition + powerStart, powerItem.getItemStack(), clickType -> {
                    extraPower.set(index, finalPower);
                    openIndividualSettingsMenu(player, index, back, delete);
                });
            }
        }

        if (currentElement.hasTicks(this)) {
            swInventory.setItem(ticksStart + 3, new SWItem(SWItem.getDye(1), BauSystem.MESSAGE.parse("LOADER_SETTING_TICKS_REMOVE_ONE", player), Arrays.asList(BauSystem.MESSAGE.parse("LOADER_SETTING_TICKS_REMOVE_ONE_SHIFT", player)), false, clickType -> {}).getItemStack(), clickType -> {
                long ticks = extraTicks.get(index);
                ticks -= clickType.isShiftClick() ? 5 : 1;
                if (ticks < 1) ticks = 1;
                extraTicks.set(index, ticks);
                openIndividualSettingsMenu(player, index, back, delete);
            });

            SWItem ticksItem = new SWItem(Material.CLOCK, BauSystem.MESSAGE.parse("LOADER_SETTING_TICKS", player, extraTicks.get(index)), Arrays.asList(BauSystem.MESSAGE.parse("LOADER_GUI_CLICK_TO_EDIT", player)), false, clickType -> {});
            ticksItem.getItemStack().setAmount((int) Math.min(extraTicks.get(index), 64));
            swInventory.setItem(ticksStart + 4, ticksItem.getItemStack(), clickType -> {
                SWAnvilInv swAnvilInv = new SWAnvilInv(player, BauSystem.MESSAGE.parse("LOADER_SETTING_TICKS_NAME", player), extraTicks.get(index) + "");
                swAnvilInv.setCallback(s -> {
                    try {
                        long ticks = Long.parseLong(s);
                        if (ticks < 1) ticks = 1;
                        extraTicks.set(index, ticks);
                    } catch (NumberFormatException ignored) {
                    }
                    openIndividualSettingsMenu(player, index, back, delete);
                });
                swAnvilInv.open();
            });

            swInventory.setItem(ticksStart + 5, new SWItem(SWItem.getDye(10), BauSystem.MESSAGE.parse("LOADER_SETTING_TICKS_ADD_ONE", player), Arrays.asList(BauSystem.MESSAGE.parse("LOADER_SETTING_TICKS_ADD_ONE_SHIFT", player)), false, clickType -> {}).getItemStack(), clickType -> {
                long ticks = extraTicks.get(index);
                ticks += clickType.isShiftClick() ? 5 : 1;
                extraTicks.set(index, ticks);
                openIndividualSettingsMenu(player, index, back, delete);
            });
        }

        swInventory.open();
    }

    protected void update(BlockData blockData) {
        Block block = location.getBlock();
        if (blockData instanceof Door) {
            Door door = (Door) blockData;
            block.setBlockData(door);
            if (door.getHalf() == Bisected.Half.BOTTOM) {
                updateBlock(block.getRelative(BlockFace.UP), door.isOpen() ? 15 : 0);
            } else {
                updateBlock(block.getRelative(BlockFace.DOWN), door.isOpen() ? 15 : 0);
            }
        } else if (blockData instanceof Switch) {
            Switch sw = (Switch) blockData;
            updateBlock(block, sw);
            FaceAttachable.AttachedFace face = sw.getAttachedFace();
            if (face == FaceAttachable.AttachedFace.FLOOR) {
                updateBlock(block.getRelative(BlockFace.DOWN), sw.isPowered() ? 15 : 0);
            } else if (face == FaceAttachable.AttachedFace.CEILING) {
                updateBlock(block.getRelative(BlockFace.UP), sw.isPowered() ? 15 : 0);
            } else {
                updateBlock(block.getRelative(sw.getFacing().getOppositeFace()), sw.isPowered() ? 15 : 0);
            }
        } else if (blockData instanceof Powerable) {
            updateBlock(block, blockData);
            updateBlock(block.getRelative(BlockFace.DOWN), ((Powerable) blockData).isPowered() ? 15 : 0);
        } else if (blockData instanceof AnaloguePowerable) {
            updateBlock(block, blockData);
            updateBlock(block.getRelative(BlockFace.DOWN), ((AnaloguePowerable) blockData).getPower());
        } else {
            updateBlock(block, blockData);
        }
    }

    private void updateBlock(Block block, int powered) {
        BlockData data = block.getBlockData();
        if (data instanceof Door) {
            Door door = (Door) data;
            door.setOpen(powered > 0);
            block.setBlockData(door);
            return;
        } else if (data instanceof Powerable) {
            Powerable powerable = (Powerable) data;
            powerable.setPowered(powered > 0);
        } else if (data instanceof AnaloguePowerable) {
            AnaloguePowerable analoguePowerable = (AnaloguePowerable) data;
            analoguePowerable.setPower(powered);
        }
        updateBlock(block, data);
    }

    private void updateBlock(Block block, BlockData data) {
        block.setType(Material.BARRIER);
        block.setBlockData(data);
    }

    public abstract boolean checkBlockInWorld();

    protected final String translateItemName(String name, Player player) {
        return BauSystem.MESSAGE.parse("LOADER_SETTING_NAME", player, BauSystem.MESSAGE.parse(name, player));
    }

    public int size() {
        return elements.size();
    }
}
