//
// Created by BONNe
// Copyright - 2023
//


package world.bentobox.bentobox.panels.customizable;


import org.bukkit.World;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import java.io.File;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.commands.CompositeCommand;
import world.bentobox.bentobox.api.localization.TextVariables;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.TemplatedPanel;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder;
import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBundle;
import world.bentobox.bentobox.util.Util;


/**
 * This class generates Island Creation Panel based on user specified file with name: "island_creation_panel.yml".
 * If file with such name is located at gamemode panels directory, then that file will be used.
 * Otherwise, file in BentoBox/panels is used.
 */
public class IslandCreationPanel
{
    // ---------------------------------------------------------------------
    // Section: Constants
    // ---------------------------------------------------------------------

    /**
     * This constant is used for button to indicate that it is Blueprint Bundle type.
     */
    private static final String BUNDLES = "BUNDLE";

    /**
     * This constant is used for button to indicate that it is previous page type.
     */
    private static final String PREVIOUS = "PREVIOUS";

    /**
     * This constant is used for button to indicate that it is next page type.
     */
    private static final String NEXT = "NEXT";

    /**
     * This constant is used for indicating that pages should contain numbering.
     */
    private static final String INDEXING = "indexing";

    /**
     * This constant stores value for SELECT action that is used in panels.
     */
    private static final String SELECT_ACTION = "SELECT";

    /**
     * This constant stores value for COMMAND action that is used in panels.
     */
    private static final String COMMANDS_ACTION = "COMMANDS";

    /**
     * This constant stores value for ERROR message that will be displayed upon failing to run creation commands.
     */
    private static final String ISLAND_CREATION_COMMANDS = "ISLAND_CREATION_COMMANDS";

    /**
     * Button reference
     */
    private static final String BUNDLE_BUTTON_REF = "panels.island_creation.buttons.bundle.";

    // ---------------------------------------------------------------------
    // Section: Variables
    // ---------------------------------------------------------------------

    /**
     * This variable allows to access plugin object.
     */
    private final BentoBox plugin;

    /**
     * This variable stores main command that was triggered.
     */
    private final CompositeCommand mainCommand;

    /**
     * This variable holds user who opens panel. Without it panel cannot be opened.
     */
    private final User user;

    /**
     * This variable holds world where panel is opened. Without it panel cannot be opened.
     */
    private final String mainLabel;

    /**
     * This variable stores filtered elements.
     */
    private final List<BlueprintBundle> elementList;

    /**
     * This variable holds current pageIndex for multi-page island choosing.
     */
    private int pageIndex;

    /**
     * The world that this command applies to
     */
    private final World world;

    /**
     * true if this panel has been called by a reset command. Changes how the count of used islands is done.
     */
    private final boolean reset;

    // ---------------------------------------------------------------------
    // Section: Constructor
    // ---------------------------------------------------------------------


    /**
     * This is internal constructor. It is used internally in current class to avoid creating objects everywhere.
     *
     * @param command CompositeCommand object
     * @param label The main command label
     * @param user User who opens panel
     * @param reset 
     */
    private IslandCreationPanel(@NonNull CompositeCommand command,
            @NonNull User user, @NonNull String label, boolean reset)
    {
        this.plugin = BentoBox.getInstance();
        this.user = user;
        this.mainLabel = label;
        this.world = command.getWorld();
        this.reset = reset;

        this.elementList = this.plugin.getBlueprintsManager().getBlueprintBundles(command.getAddon()).values().stream().
                sorted(Comparator.comparingInt(BlueprintBundle::getSlot).thenComparing(BlueprintBundle::getUniqueId))
                .filter(bundle -> !bundle.isRequirePermission() || this.user
                        .hasPermission(command.getPermissionPrefix() + "island.create." + bundle.getUniqueId()))
                .toList();

        this.mainCommand = command;
    }


    // ---------------------------------------------------------------------
    // Section: Methods
    // ---------------------------------------------------------------------


    /**
     * Build method manages current panel opening. It uses BentoBox PanelAPI that is easy to use and users can get nice
     * panels.
     */
    private void build()
    {
        // Do not open gui if there is no magic sticks.
        if (this.elementList.isEmpty())
        {
            this.plugin.logError("There are no available phases for selection!");
            this.user.sendMessage("no-phases",
                    TextVariables.GAMEMODE, this.plugin.getDescription().getName());
            return;
        }

        // Start building panel.
        TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder();

        // Set main template.
        if (this.doesCustomPanelExists(this.mainCommand.getAddon(), "island_creation_panel"))
        {
            // Addon has its own island creation panel. Use it.
            panelBuilder.template("island_creation_panel", new File(this.mainCommand.getAddon().getDataFolder(), "panels"));
        }
        else
        {
            // Use default island creation panel.
            panelBuilder.template("island_creation_panel", new File(this.plugin.getDataFolder(), "panels"));
        }

        panelBuilder.user(this.user);
        panelBuilder.world(this.user.getWorld());

        // Register button builders
        panelBuilder.registerTypeBuilder(BUNDLES, this::createBundleButton);

        // Register next and previous builders
        panelBuilder.registerTypeBuilder(NEXT, this::createNextButton);
        panelBuilder.registerTypeBuilder(PREVIOUS, this::createPreviousButton);

        // Register unknown type builder.
        panelBuilder.build();
    }


    /**
     * This method returns if panel with the requested name is located in GameModeAddon folder.
     * @param addon GameModeAddon that need to be checked.
     * @param name Name of the panel.
     * @return {@code true} if panel exists, {@code false} otherwise.
     */
    private boolean doesCustomPanelExists(GameModeAddon addon, String name)
    {
        return addon.getDataFolder().exists() &&
                new File(addon.getDataFolder(), "panels").exists()
                && new File(addon.getDataFolder(), "panels" + File.separator + name + ".yml").exists();
    }


    // ---------------------------------------------------------------------
    // Section: Buttons
    // ---------------------------------------------------------------------


    /**
     * Create next button panel item.
     *
     * @param template the template
     * @param slot the slot
     * @return the panel item
     */
    @Nullable
    private PanelItem createNextButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot)
    {
        int size = this.elementList.size();

        if (size <= slot.amountMap().getOrDefault(BUNDLES, 1) ||
                1.0 * size / slot.amountMap().getOrDefault(BUNDLES, 1) <= this.pageIndex + 1)
        {
            // There are no next elements
            return null;
        }

        int nextPageIndex = this.pageIndex + 2;

        PanelItemBuilder builder = new PanelItemBuilder();

        if (template.icon() != null)
        {
            ItemStack clone = template.icon().clone();

            if ((boolean) template.dataMap().getOrDefault(INDEXING, false))
            {
                clone.setAmount(nextPageIndex);
            }

            builder.icon(clone);
        }

        if (template.title() != null)
        {
            builder.name(this.user.getTranslation(this.mainCommand.getWorld(), template.title()));
        }

        if (template.description() != null)
        {
            builder.description(this.user.getTranslation(this.mainCommand.getWorld(), template.description(),
                    TextVariables.NUMBER, String.valueOf(nextPageIndex)));
        }

        // Add ClickHandler
        builder.clickHandler((panel, user, clickType, i) ->
        {
            template.actions().forEach(action -> {
                if ((clickType == action.clickType() ||
                        action.clickType() == ClickType.UNKNOWN) && NEXT.equalsIgnoreCase(action.actionType()))
                {
                    // Next button ignores click type currently.
                    this.pageIndex++;
                    this.build();
                }

            });

            // Always return true.
            return true;
        });

        // Collect tooltips.
        List<String> tooltips = template.actions().stream().
                filter(action -> action.tooltip() != null)
                .map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip()))
                .filter(text -> !text.isBlank())
                .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));

        // Add tooltips.
        if (!tooltips.isEmpty())
        {
            // Empty line and tooltips.
            builder.description("");
            builder.description(tooltips);
        }

        return builder.build();
    }


    /**
     * Create previous button panel item.
     *
     * @param template the template
     * @param slot the slot
     * @return the panel item
     */
    @Nullable
    private PanelItem createPreviousButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot)
    {
        if (this.pageIndex == 0)
        {
            // There are no next elements
            return null;
        }

        int previousPageIndex = this.pageIndex;

        PanelItemBuilder builder = new PanelItemBuilder();

        if (template.icon() != null)
        {
            ItemStack clone = template.icon().clone();

            if ((boolean) template.dataMap().getOrDefault(INDEXING, false))
            {
                clone.setAmount(previousPageIndex);
            }

            builder.icon(clone);
        }

        if (template.title() != null)
        {
            builder.name(this.user.getTranslation(this.mainCommand.getWorld(), template.title()));
        }

        if (template.description() != null)
        {
            builder.description(this.user.getTranslation(this.mainCommand.getWorld(), template.description(),
                    TextVariables.NUMBER, String.valueOf(previousPageIndex)));
        }

        // Add ClickHandler
        builder.clickHandler((panel, user, clickType, i) ->
        {
            template.actions().forEach(action -> {
                if ((clickType == action.clickType() ||
                        action.clickType() == ClickType.UNKNOWN) && PREVIOUS.equalsIgnoreCase(action.actionType()))
                {
                    // Next button ignores click type currently.
                    this.pageIndex--;
                    this.build();
                }

            });

            // Always return true.
            return true;
        });

        // Collect tooltips.
        List<String> tooltips = template.actions().stream().
                filter(action -> action.tooltip() != null)
                .map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip()))
                .filter(text -> !text.isBlank())
                .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));

        // Add tooltips.
        if (!tooltips.isEmpty())
        {
            // Empty line and tooltips.
            builder.description("");
            builder.description(tooltips);
        }

        return builder.build();
    }


    /**
     * This method creates and returns bundle button.
     *
     * @return PanelItem that represents bundle button.
     */
    @Nullable
    private PanelItem createBundleButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot)
    {
        if (this.elementList.isEmpty())
        {
            // Does not contain any blueprints.
            return null;
        }

        int index = this.pageIndex * slot.amountMap().getOrDefault(BUNDLES, 1) + slot.slot();

        BlueprintBundle blueprintBundle;

        if (index >= this.elementList.size())
        {
            // Out of index.
            blueprintBundle = null;
        }
        else
        {
            blueprintBundle = this.elementList.get(index);
        }

        if (template.dataMap().containsKey("unique_id"))
        {
            // Try to find bundle with requested ID. if not found, use already collected bundle.
            blueprintBundle = this.elementList.stream().
                    filter(bundle -> bundle.getUniqueId().equals(template.dataMap().get("unique_id"))).findFirst()
                    .orElse(blueprintBundle);
        }

        return this.createBundleButton(template, blueprintBundle);
    }


    // ---------------------------------------------------------------------
    // Section: Other methods
    // ---------------------------------------------------------------------


    /**
     * This method creates bundle button.
     *
     * @return PanelItem that allows to select bundle button
     */
    private PanelItem createBundleButton(ItemTemplateRecord template, BlueprintBundle bundle)
    {
        if (bundle == null)
        {
            // return as bundle is null. Empty button will be created.
            return null;
        }

        // Get settings for island.
        PanelItemBuilder builder = new PanelItemBuilder();

        if (template.icon() != null)
        {
            builder.icon(template.icon().clone());
        }
        else
        {
            builder.icon(bundle.getIcon());
        }

        if (template.title() != null)
        {
            builder.name(this.user.getTranslation(this.mainCommand.getWorld(), template.title(),
                    TextVariables.NAME, bundle.getDisplayName()));
        }
        else
        {
            builder.name(this.user.getTranslation(BUNDLE_BUTTON_REF + "name",
                    TextVariables.NAME, bundle.getDisplayName()));
        }

        if (template.description() != null)
        {
            builder.description(this.user.getTranslation(this.mainCommand.getWorld(), template.description(),
                    TextVariables.DESCRIPTION, String.join("\n", bundle.getDescription())));
        }
        else
        {
            builder.description(this.user.getTranslation(BUNDLE_BUTTON_REF + "description",
                    TextVariables.DESCRIPTION, String.join("\n", bundle.getDescription())));
        }
        boolean usedUp = false;
        if (plugin.getSettings().getIslandNumber() > 1) {
            // Show how many times this bundle can be used
            int maxTimes = bundle.getTimes();
            if (maxTimes > 0) {
                long uses = plugin.getIslands().getIslands(world, user).stream()
                        .filter(is -> is.getMetaData("bundle")
                                .map(mdv -> bundle.getDisplayName().equalsIgnoreCase(mdv.asString())
                                        && !(reset && is.isPrimary(user.getUniqueId()))) // If this is a reset, then ignore the use of the island being reset
                                .orElse(false))
                        .count();
                builder.description(this.user.getTranslation(BUNDLE_BUTTON_REF + "uses", TextVariables.NUMBER,
                        String.valueOf(uses), "[max]", String.valueOf(maxTimes)));
                if (uses >= maxTimes) {
                    usedUp = true;
                }
            } else {
                builder.description(this.user.getTranslation(BUNDLE_BUTTON_REF + "unlimited"));
            }
        }

        if (usedUp) {
            if (plugin.getSettings().isHideUsedBlueprints()) {
                // Do not show used up blueprints
                return null;
            }
        } else {
            List<ItemTemplateRecord.ActionRecords> actions = template.actions().stream()
                    .filter(action -> SELECT_ACTION.equalsIgnoreCase(action.actionType())
                            || COMMANDS_ACTION.equalsIgnoreCase(action.actionType()))
                    .toList();
            // Add ClickHandler
            builder.clickHandler((panel, user, clickType, i) -> {
                actions.forEach(action -> {
                    if (clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN)
                    {
                        if (SELECT_ACTION.equalsIgnoreCase(action.actionType())) {
                            user.closeInventory();
                            this.mainCommand.execute(user, this.mainLabel,
                                    Collections.singletonList(bundle.getUniqueId()));
                        } else if (COMMANDS_ACTION.equalsIgnoreCase(action.actionType())) {
                            Util.runCommands(user,
                                    Arrays.stream(action.content()
                                            .replaceAll(Pattern.quote(TextVariables.LABEL),
                                                    this.mainCommand.getTopLabel())
                                            .split("\n")).toList(),
                                    ISLAND_CREATION_COMMANDS);
                        }
                    }
                });

                // Always return true.
                return true;
            });

            // Collect tooltips.
            List<String> tooltips = actions.stream().filter(action -> action.tooltip() != null)
                    .map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip()))
                    .filter(text -> !text.isBlank())
                    .collect(Collectors.toCollection(() -> new ArrayList<>(actions.size())));

            // Add tooltips.
            if (!tooltips.isEmpty()) {
                // Empty line and tooltips.
                builder.description("");
                builder.description(tooltips);
            }
        }
        return builder.build();
    }


    // ---------------------------------------------------------------------
    // Section: Static methods
    // ---------------------------------------------------------------------


    /**
     * This method is used to open Panel outside this class. It will be much easier to open panel with single method
     * call then initializing new object.
     *
     * @param command CompositeCommand object
     * @param label The main command label
     * @param user User who opens panel
     * @param reset true if this is an island reset
     */
    public static void openPanel(@NonNull CompositeCommand command,
            @NonNull User user, @NonNull String label, boolean reset)
    {
        new IslandCreationPanel(command, user, label, reset).build();
    }


}
