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

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonReader;
import de.steamwar.core.Core;
import de.steamwar.data.CMDs;
import de.steamwar.sql.*;
import lombok.*;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;

import java.io.StringReader;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

public class SchematicSelector {

    private static final Sorting[] all_sortings = Sorting.values();
    private static final Map<Player, SelectorFilter[]> filterCache = new HashMap<>();

    @Getter
    private final Player player;
    @Getter
    private SteamwarUser user;
    @Getter
    @Setter
    private Consumer<SchematicNode> callback;
    private final SelectorTarget target;
    @Getter
    private SelectorFilter filter;
    private SchematicSelectorInjectable injectable = SchematicSelectorInjectable.DEFAULT;
    @Setter
    @Getter
    private PublicMode publicMode = PublicMode.ALL;
    @Setter
    @Getter
    private boolean singleDirOpen;
    private boolean sdoTrigger = false;
    @Getter
    @Setter
    private int depth = 0;
    private Sorting sorting = Sorting.NAME;
    private boolean invertSorting = false;
    @Getter
    private SchematicNode lastParent;

    public SchematicSelector(Player player, SelectorTarget target, Consumer<SchematicNode> callback) {
        this.player = player;
        this.user = SteamwarUser.get(player.getUniqueId());
        this.target = target;
        this.callback = callback;
        this.singleDirOpen = !target.target.dirs;
    }

    public SchematicSelector(Player player, SelectorTarget target, SchematicSelectorInjectable injectable, Consumer<SchematicNode> callback) {
        this(player, target, callback);
        this.injectable = injectable;
    }

    public void open() {
        injectable.onSelectorCreate(this);
        if(publicMode == PublicMode.PUBLIC_ONLY) {
            this.user = SteamwarUser.get(0);
        }
        openList(null);
        injectable.onSelectorOpen(this, SchematicSelectorInjectable.OpenFrom.FRESH);
    }

    public void reOpen() {
        openList(lastParent);
        injectable.onSelectorOpen(this, SchematicSelectorInjectable.OpenFrom.REOPEN);
    }

    public void reOpenDirUp() {
        depth--;
        openList(dirUp(lastParent));
        injectable.onSelectorOpen(this, SchematicSelectorInjectable.OpenFrom.REOPEN);
    }

    private void openList(SchematicNode parent) {
        lastParent = parent;
        List<SchematicNode> nodes = applySorting(filter != null?getFilteredSchematics():getSchematicList(parent));

        if(sdoTrigger) {
            sdoTrigger = false;
            openList(nodes.get(0));
            return;
        }

        List<SWListInv.SWListEntry<SchematicNode>> list = new ArrayList<>();

        if(depth != 0) {
            list.add(new SWListInv.SWListEntry<>(new SWItem(Material.ARROW, Core.MESSAGE.parse("SCHEM_SELECTOR_BACK", player), clickType -> {}).setCustomModelData(CMDs.BACK), null));
        }

        for (SchematicNode node : nodes) {
            if(node.getName().equals("//copy")) continue;
            list.add(renderItem(node));
        }

        SWListInv<SchematicNode> inv = new SWListInv<>(player, MessageFormat.format(injectable.createTitle(player), target.target.getName(player), (filter == null || filter.getName() == null)?(parent == null?"/":parent.generateBreadcrumbs()):filter.getName()), false, list, (clickType, node) -> handleClick(node, parent));
        if(publicMode == PublicMode.ALL) {
            if(user.getId() == 0) {
                inv.setItem(48, new SWItem(Material.BUCKET, Core.MESSAGE.parse("SCHEM_SELECTOR_OWN", player), clickType -> {
                    this.user = SteamwarUser.get(player.getUniqueId());
                    openList(null);
                }).setCustomModelData(CMDs.Schematic.OWN_SCHEMS));
            } else {
                inv.setItem(48, new SWItem(Material.GLASS, Core.MESSAGE.parse("SCHEM_SELECTOR_PUB", player), clickType -> {
                    this.user = SteamwarUser.get(0);
                    openList(null);
                }).setCustomModelData(CMDs.Schematic.PUBLIC_SCHEMS));
            }
        }
        if(target.target.dirs) {
            inv.setItem(49, SWItem.getDye(10), Core.MESSAGE.parse("SCHEM_SELECTOR_SEL_DIR", player), clickType -> {
                player.closeInventory();
                callback.accept(parent);
            });
        }
        if(user.getId() != 0) {
            inv.setItem(50, new SWItem(Material.CHEST, Core.MESSAGE.parse("SCHEM_SELECTOR_NEW_DIR", player), clickType -> createFolderIn(parent)).setCustomModelData(CMDs.Schematic.NEW_DIR));
        }
        inv.setItem(51, new SWItem(Material.NAME_TAG, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER", player), clickType -> openFilter()).setCustomModelData(CMDs.Schematic.FILTER));
        inv.setItem(47, new SWItem(sorting.mat, Core.MESSAGE.parse("SCHEM_SELECTOR_SORTING", player), Arrays.asList(
                Core.MESSAGE.parse("SCHEM_SELECTOR_SORTING_CURRENT", player, sorting.parseName(player)),
                Core.MESSAGE.parse("SCHEM_SELECTOR_SORTING_DIRECTION", player, Core.MESSAGE.parse(invertSorting?"SCHEM_SELECTOR_SORTING_DSC":"SCHEM_SELECTOR_SORTING_ASC", player))
        ), invertSorting, click -> {
            if(click.isLeftClick()) {
                cycleSorting();
            } else {
                invertSorting = !invertSorting;
            }
            openList(parent);
        }).setCustomModelData(invertSorting ? CMDs.Schematic.SORT_DESCENDING : CMDs.Schematic.SORT_ASCENDING));

        injectable.onListRender(this, inv, parent);
        inv.open();
    }

    private SchematicNode dirUp(SchematicNode parent) {
        if(parent == null) {
            // Gracefully handle unexpected Updir in Root Folder
            depth = 0;
            return null;
        }
        if(!singleDirOpen) {
            if(NodeMember.getNodeMember(parent.getId(), user.getId()) != null) {
                return NodeMember.getNodeMember(parent.getId(), user.getId()).getParent().map(integer -> SchematicNode.byIdAndUser(user, integer)).orElse(null);
            } else {
                return getParent(parent).orElse(null);
            }
        } else {
            Optional<SchematicNode> currentParent = Optional.of(parent);
            do {
                sdoTrigger = false;
                currentParent = currentParent.flatMap(SchematicSelector::getParent);
                if(!currentParent.isPresent()) {
                    break;
                }
                getSchematicList(currentParent.get());
            } while (sdoTrigger);
            return currentParent.orElse(null);
        }
    }

    private void handleClick(SchematicNode node, SchematicNode parent) {
        if(node == null) {
            depth--;
            openList(dirUp(parent));
            return;
        }
        if(node.isDir()) {
            if(filter != null && target.target.dirs) {
                player.closeInventory();
                callback.accept(node);
                return;
            }
            filter = null;
            depth++;
            openList(node);
            return;
        }
        player.closeInventory();
        callback.accept(node);
    }

    private void cycleSorting() {
        int next = sorting.ordinal() + 1;
        if(next >= all_sortings.length) {
            next = 0;
        }
        sorting = all_sortings[next];
    }

    private List<SchematicNode> applySorting(List<SchematicNode> nodes) {
        if(sorting == Sorting.NAME && !invertSorting) {
            return nodes;
        }

        Comparator<SchematicNode> comparator = sorting.comparator;
        if(invertSorting) {
            comparator = comparator.reversed();
        }

        nodes.sort(comparator);
        return nodes;
    }

    private SWListInv.SWListEntry<SchematicNode> renderItem(SchematicNode node) {
        Material m = SWItem.getMaterial(node.getItem());

        String name = Core.MESSAGE.parse((filter != null && filter.name == null)?"SCHEM_SELECTOR_ITEM_NAME":"SCHEM_SELECTOR_ITEM_NAME_FILTER", player, node.getName());

        if(filter != null && filter.getName() != null) {
            name = name.replace(filter.getName(), Core.MESSAGE.parse("SCHEM_SELECTOR_ITEM_REPLACE", player, filter.getName()));
        }

        SWItem item = new SWItem(m, name, Collections.singletonList(node.isDir() ? (Core.MESSAGE.parse("SCHEM_SELECTOR_DIR", player)) : Core.MESSAGE.parse("SCHEM_SELECTOR_ITEM_LORE_TYPE", player, node.getSchemtype().name())), !node.isDir() && !node.getSchemtype().writeable(), click -> {
        });
        if(!node.isDir() && node.getRank() > 0) {
            item.setLore(Arrays.asList(Core.MESSAGE.parse("SCHEM_SELECTOR_ITEM_LORE_TYPE", player, node.getSchemtype().name()), Core.MESSAGE.parse("SCHEM_SELECTOR_RANK", player, node.getRank())));
        }
        return new SWListInv.SWListEntry<>(item, node);
    }

    private void addLeftCloseAction(SWAnvilInv inv, Runnable runnable) {
        AtomicBoolean wasLeft = new AtomicBoolean(false);
        inv.addCloseCallback(() -> {
            if(injectable.onAnvilInvCloseAction(this) == SchematicSelectorInjectable.AnvilInvCloseAction.REOPEN && !wasLeft.get()) {
                player.closeInventory();
                Bukkit.getScheduler().runTaskLater(Core.getInstance(), runnable, 1);
            }
        });
        inv.addLeftCallback(() -> {
            wasLeft.set(true);
            runnable.run();
        });
    }

    private void createFolderIn(SchematicNode parent) {
        SWAnvilInv inv = new SWAnvilInv(player, Core.MESSAGE.parse("SCHEM_SELECTOR_CREATE_DIR_TITLE", player));
        inv.setItem(Material.CHEST, Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_CLICK_BACK", player)), false);
        inv.setCallback(s -> {
            if(!SchematicNode.invalidSchemName(new String[] {s})) {
                if(injectable.onFolderCreate(this, s)) {
                    SchematicNode.createSchematicDirectory(user.getId(), s, parent==null?0:parent.getId());
                    openList(parent);
                }
                return;
            }
            player.closeInventory();
        });
        addLeftCloseAction(inv, this::reOpen);
        inv.open();
    }

    private void openFilter() {
        if (filter == null) {
            filter = new SelectorFilter(null, null, null, null);
        }

        SWInventory inv = new SWInventory(player, 9 * 2, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TITLE", player));
        InvCallback nameCallback = clickType -> {
            if(clickType.isRightClick()) {
                filter = filter.withName(null);
                openFilter();
            } else {
                SWAnvilInv swAnvilInv = new SWAnvilInv(player, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_ENTER_NAME", player));
                swAnvilInv.setItem(Material.NAME_TAG, Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_CLICK_BACK", player)), false);
                swAnvilInv.setCallback(s -> {
                    filter = filter.withName(s);
                    openFilter();
                });
                addLeftCloseAction(swAnvilInv, this::openFilter);
                swAnvilInv.open();
            }
        };
        if(filter.getName() == null) {
            inv.setItem(0, Material.NAME_TAG, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_NAME", player), nameCallback);
        } else {
            inv.setItem(0, Material.NAME_TAG, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_NAME", player), Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_NAME_SEARCH", player, filter.getName())), true, nameCallback);
        }

        InvCallback ownerCallback = clickType -> {
            if(clickType.isRightClick()) {
                filter = filter.withOwner(null);
                openFilter();
            } else {
                SWAnvilInv swAnvilInv = new SWAnvilInv(player, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_ENTER_OWNER", player));
                swAnvilInv.setItem(SWItem.getMaterial("SKULL_ITEM"), (byte) 3, Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_CLICK_BACK", player)), false);
                swAnvilInv.setCallback(s -> {
                    if(SteamwarUser.get(s) != null) {
                        filter = filter.withOwner(SteamwarUser.get(s).getId());
                    }
                    openFilter();
                });
                addLeftCloseAction(swAnvilInv, this::openFilter);
                swAnvilInv.open();
            }
        };
        if(filter.getOwner() == null) {
            inv.setItem(1, SWItem.getMaterial("SKULL_ITEM"), (byte) 3, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_OWNER", player), ownerCallback);
        } else {
            SteamwarUser tUser = SteamwarUser.get(filter.getOwner());
            SWItem item = SWItem.getPlayerSkull(tUser.getUserName());
            item.setName(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_OWNER", player));
            item.setEnchanted(true);
            item.setLore(Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_OWNER_SEARCH", player, tUser.getUserName())));
            item.setCallback(ownerCallback);
            inv.setItem(1, item);
        }

        if(target.target != Target.SCHEMATIC_TYPE) {
            InvCallback schemTypeCallback = clickType -> {
                if(clickType.isRightClick()) {
                    filter = filter.withType(null);
                    openFilter();
                } else {
                    List<SWListInv.SWListEntry<SchematicType>> types = new ArrayList<>();
                    SchematicType.values().forEach(schematicType -> types.add(new SWListInv.SWListEntry<>(new SWItem(SWItem.getMaterial(schematicType.getMaterial()), "§e" + schematicType.name(), Collections.emptyList(), schematicType.fightType(), n -> {}), schematicType)));
                    SWListInv<SchematicType> listInv = new SWListInv<>(player, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_SEL_TYPE", player), types, (clickType1, schematicType) -> {
                        filter = filter.withType(schematicType);
                        openFilter();
                    });
                    listInv.open();
                }
            };

            if(filter.getType() == null) {
                inv.setItem(2, SWItem.getMaterial(SchematicType.Normal.getMaterial()), Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE", player), schemTypeCallback);
            } else {
                inv.setItem(2, SWItem.getMaterial(filter.getType().getMaterial()), Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE", player), Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE_SEARCH", player, filter.getType().name())), true, schemTypeCallback);
            }
        }

        InvCallback materialCallback = clickType -> {
            if(clickType.isRightClick()) {
                filter = filter.withItem(null);
                openFilter();
            } else {
                UtilGui.openMaterialSelector(player, material -> {
                    filter = filter.withItem(material);
                    openFilter();
                });
            }
        };

        final int iSlot = target.target == Target.SCHEMATIC_TYPE?2:3;

        if(filter.getItem() == null) {
            inv.setItem(iSlot, Material.STONE, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_MAT", player), materialCallback);
        } else {
            inv.setItem(iSlot, filter.getItem(), Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_MAT", player), Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_MAT_SEARCH", player, filter.getItem().name())), true, materialCallback);
        }

        if (!filterCache.containsKey(player)) {
            filterCache.put(player, new SelectorFilter[9]);

            String cfg = UserConfig.getConfig(user.getId(), "selector:filters");

            if (cfg != null) {
                JsonArray array = Streams.parse(new JsonReader(new StringReader(cfg))).getAsJsonArray();
                for (int i = 0; i < array.size(); i++) {
                    JsonObject object = array.get(i).getAsJsonObject();
                    filterCache.get(player)[i] = new SelectorFilter(object);
                }
            }
        }

        SelectorFilter[] filters = filterCache.get(player);

        for (int i = 0; i < filters.length; i++) {
            SelectorFilter filterCached = filters[i];
            if (filterCached == null) {
                inv.setItem(i + 9, new SWItem(Material.BARRIER, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_EMPTY", player), click -> {}));
            } else {
                SWItem item = filterCached.toItemStack(player);
                item.setEnchanted(filterCached.equals(filter));
                inv.setItem(i + 9, item.getItemStack(), click -> {
                    filter = filterCached;
                    openFilter();
                });
            }
        }

        inv.setItem(7, SWItem.getDye(1), Core.MESSAGE.parse("SCHEM_SELECTOR_CANCEL", player), clickType -> {
            filter = null;
            depth = 0;
            openList(null);
        });
        inv.setItem(8, SWItem.getDye(10), Core.MESSAGE.parse("SCHEM_SELECTOR_GO", player), clickType -> {
            if (!filter.equals(filters[0])) {
                for (int i = filters.length - 1; i > 0; i--) {
                    filters[i] = filters[i-1];
                    if (filter.equals(filters[i])) {
                        filters[i] = null;
                        for (int j = i; j < filters.length - 1; j++) {
                            filters[j] = filters[j+1];
                        }
                        filters[filters.length - 1] = null;
                    }
                }

                filters[0] = filter;

                filterCache.put(player, filters);
                JsonArray array = new JsonArray();
                for (SelectorFilter f : filters) {
                    if (f != null) {
                        array.add(f.toJson());
                    }
                }

                UserConfig.updatePlayerConfig(user.getId(), "selector:filters", array.toString());
            }

            injectable.onFilterApply(this);
            depth = 0;
            openList(null);
        });

        injectable.onFilterRender(this, inv);

        inv.open();
    }

    private List<SchematicNode> getFilteredSchematics() {
        List<SchematicNode> nodes = new ArrayList<>(SchematicNode.getAll(user));
        nodes.removeIf(node -> {
            injectable.onNodeFilter(this, node);
            return !filter.matches(node);
        });
        if(target.target == Target.DIRECTORY) {
            nodes.removeIf(node -> !node.isDir());
        }
        if(target.target == Target.SCHEMATIC_TYPE) {
            nodes.removeIf(node -> node.isDir() || !node.getSchemtype().equals(target.type));
        }
        return nodes;
    }

    private List<SchematicNode> getSchematicList(SchematicNode parent) {
        List<SchematicNode> nodes = new ArrayList<>();
        switch (target.target) {
            case DIRECTORY:
                nodes.addAll(SchematicNode.list(user, Optional.ofNullable(parent).map(SchematicNode::getId).orElse(null)));
                nodes.removeIf(node -> !node.isDir());
                break;
            case SCHEMATIC_TYPE:
                nodes.addAll(SchematicNode.accessibleByUserTypeParent(user, target.type, parent==null?null:parent.getId()));
                break;
            default:
                nodes.addAll(SchematicNode.list(user, parent == null?null:parent.getId()));
        }

        if(singleDirOpen && nodes.size() == 1 && nodes.get(0).isDir()) {
            sdoTrigger = true;
        }
        return nodes;
    }

    private static Optional<SchematicNode> getParent(SchematicNode node) {
        return node.getOptionalParent().map(integer -> SchematicNode.byIdAndUser(SteamwarUser.get(node.getEffectiveOwner()), integer));
    }

    public static SelectorTarget selectSchematic() {
        return new SelectorTarget(Target.SCHEMATIC, null, -1);
    }

    public static SelectorTarget selectDirectory() {
        return new SelectorTarget(Target.DIRECTORY, null, -1);
    }

    public static SelectorTarget selectSchematicNode() {
        return new SelectorTarget(Target.SCHEMATIC_NODE, null, -1);
    }

    public static SelectorTarget selectSchematicType(SchematicType type) {
        return new SelectorTarget(Target.SCHEMATIC_TYPE, type, -1);
    }

    public static SelectorTarget selectSchematicTypeWithRank(SchematicType type, int rank) {
        return new SelectorTarget(Target.SCHEMATIC_TYPE, type, rank);
    }

    @AllArgsConstructor
    public static class SelectorTarget {

        private final Target target;
        private final SchematicType type;
        @Deprecated
        private final int rank;
    }

    @AllArgsConstructor
    private enum Target {
        SCHEMATIC("SCHEM_SELECTOR_SCHEMATIC", false),
        DIRECTORY("SCHEM_SELECTOR_DIRECTORY", true),
        SCHEMATIC_NODE("SCHEM_SELECTOR_SCHEMATIC_NODE", true),
        SCHEMATIC_TYPE("SCHEM_SELECTOR_SCHEMATIC", false);

        private final String rawName;
        private final boolean dirs;

        private String getName(Player player) {
            return Core.MESSAGE.parse(rawName, player);
        }
    }

    @Getter
    @Setter
    @AllArgsConstructor
    @With
    @EqualsAndHashCode
    public static class SelectorFilter {
        private final String name;
        private final Integer owner;
        private final SchematicType type;
        private final Material item;

        public SelectorFilter(JsonObject object) {
            this.name = object.get("name").isJsonNull() ? null : object.get("name").getAsString();
            this.owner = object.get("owner").isJsonNull() ? null : object.get("owner").getAsInt();
            this.type = object.get("type").isJsonNull() ? null : SchematicType.fromDB(object.get("type").getAsString());
            this.item = object.get("item").isJsonNull() ? null : Material.valueOf(object.get("item").getAsString());
        }

        public boolean matches(SchematicNode node) {
            boolean matches = name == null || node.getName().contains(name);

            if(owner != null && node.getOwner() != owner) {
                matches = false;
            }

            if(type != null && (node.isDir() || !node.getSchemtype().equals(type))) {
                matches = false;
            }

            if(item != null) {
                String i;
                if(node.getItem().isEmpty()) {
                    i = node.isDir()?"CHEST":"CAULDRON";
                } else {
                    i = node.getItem();
                }
                if(!item.name().equals(i)) {
                    matches = false;
                }
            }
            return matches;
        }

        public long getCount() {
            return Arrays.stream(new Object[]{name, owner, type, item}).filter(Objects::nonNull).count();
        }

        public List<String> getItemLore(Player player) {
            List<String> lore = new ArrayList<>(4);
            if(name != null) {
                lore.add(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_NAME_SEARCH", player, name));
            }
            if(owner != null) {
                lore.add(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_OWNER_SEARCH", player, SteamwarUser.get(owner).getUserName()));
            }
            if(type != null) {
                lore.add(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE_SEARCH", player, type.name()));
            }
            if(item != null) {
                lore.add(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_MAT_SEARCH", player, item.name()));
            }

            return lore;
        }

        public SWItem toItemStack(Player player) {
            long count = getCount();
            String itemName = count != 1 ? Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TITLE_MULTI", player, count) : Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TITLE_SINGLE", player);

            List<String> lore = getItemLore(player);

            if (name != null) {
                return new SWItem(Material.NAME_TAG, itemName, lore, false, click -> {});
            } else if (owner != null) {
                SWItem playerSkull = SWItem.getPlayerSkull(SteamwarUser.get(owner).getUserName());
                playerSkull.setName(itemName);
                playerSkull.setLore(lore);
                return playerSkull;
            } else if (type != null) {
                return new SWItem(SWItem.getMaterial(type.getMaterial()), itemName, lore, false, n -> {});
            } else if (item != null) {
                return new SWItem(item, itemName, lore, false, click -> {});
            } else {
                return new SWItem(Material.BARRIER, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TITLE_EMPTY", player), Collections.emptyList(), false, click -> {});
            }
        }

        public JsonObject toJson() {
            JsonObject object = new JsonObject();
            object.addProperty("name", name);
            object.addProperty("owner", owner);
            object.addProperty("type", type == null?null:type.toDB());
            object.addProperty("item", item == null?null:item.name());

            return object;
        }
    }

    public enum PublicMode {
        ALL,
        PRIVATE_ONLY,
        PUBLIC_ONLY
    }

    @AllArgsConstructor
    private enum Sorting {
        NAME(Material.PAPER, "SCHEM_SELECTOR_SORTING_NAME", Comparator.comparing(SchematicNode::getName)),
        TYPE(Material.CAULDRON, "SCHEM_SELECTOR_SORTING_TYPE", (o1, o2) -> {
            if(o1.isDir() || o2.isDir()) {
                return Boolean.compare(o1.isDir(), o2.isDir());
            } else {
                return o1.getSchemtype().name().compareTo(o2.getSchemtype().name());
            }
        }),
        LAST_UPDATED(SWItem.getMaterial("WATCH"), "SCHEM_SELECTOR_SORTING_UPDATE", Comparator.comparing(SchematicNode::getLastUpdate));

        private final Material mat;
        private final String name;
        private final Comparator<SchematicNode> comparator;

        private String parseName(Player player) {
            return Core.MESSAGE.parse(name, player);
        }
    }
}
